Source of Truth cho công cụ semi-automatic hỗ trợ thu thập dữ liệu hình ảnh người nổi tiếng Việt Nam: tự động tìm kiếm, lọc kỹ thuật, chấm điểm, gom shortlist — người vận hành chỉ cần review và chốt final.
- Tổng quan & Mục tiêu
- Yêu cầu kỹ thuật ảnh (Hard Requirements)
- Quickstart — Cài đặt & Chạy lần đầu
- Nguyên tắc thiết kế
- Kiến trúc tổng thể
- Workflow chuẩn (6 Phase)
- Danh sách Module
- Data Model & Candidate Lifecycle
- Cấu trúc thư mục
- Scoring & Threshold Strategy
- Chiến lược batch 100 người
- Stop Rules
- Manual Review Policy
- Logging & Observability
- Ước tính chi phí & Performance
- Công nghệ & Vision Models
- Thứ tự Implement
- Rủi ro & Mitigation
- Definition of Done
- Lưu ý Pháp lý & Đạo đức
- Phụ lục — CLI Reference
- Phụ lục — Candidate States
- Phụ lục — Config mẫu
Thu thập 20–50 ảnh usable / người cho danh sách 14–100+ người nổi tiếng Việt Nam (KOLs, ca sĩ, diễn viên, vận động viên, YouTubers...).
Nếu làm thủ công hoàn toàn, bottleneck thực sự không phải khâu tìm ra kết quả, mà là:
- Mở nhiều tab, lọc ảnh lỗi / bé / mờ bằng mắt thường
- Lướt qua hàng loạt ảnh trùng cùng buổi chụp / outfit
- Tải nhầm ảnh không usable
- Quản lý metadata bị thiếu hoặc lệch
Công cụ không chịu trách nhiệm quyết định ảnh nào vào dataset. Công cụ chịu trách nhiệm các bước lặp lại và có thể tự động hóa:
| Bước tự động hóa | Ghi chú |
|---|---|
| Search ảnh theo từ khóa | Đa dạng query template |
| Thu thập link / thumbnail / metadata | Không tải gốc vội |
| Lọc sơ bộ theo tiêu chuẩn kỹ thuật | Rẻ, nhanh trước |
| Ước lượng full-body | Heuristic, không cần nhận diện danh tính |
| Loại ảnh trùng / gần trùng | pHash + clustering |
| Chấm điểm & gom shortlist | Ưu tiên ảnh đáng review trước |
Manual review vẫn là bước kiểm soát cuối cùng để:
- Xác nhận đúng người
- Loại ảnh xấu / ngữ cảnh không phù hợp
- Tránh ảnh quá giống nhau, cùng outfit / photoshoot
- Quyết định danh sách final
| Chỉ số | Mục tiêu |
|---|---|
| Scale | 100 người / batch |
| Raw candidates / người | 60–120 ảnh |
| Shortlist sau auto-filter | 20–40 ảnh |
| Final usable / người | 25–30 ảnh |
| Pipeline | Có log, metadata, resume được |
- Xác minh danh tính 100% bằng face recognition
- Auto-final selection không cần người review
- Nhận diện cùng outfit / photoshoot hoàn hảo
- Crawling không giới hạn mọi nguồn
Đây là các gate cứng — không đạt thì tự động loại, không cần xem tay:
| Tiêu chí | Yêu cầu |
|---|---|
| Độ phân giải | min(width, height) > 400 px |
| Định dạng file | .jpg, .jpeg, .png |
| Tỷ lệ khung hình | Không cắt / crop phá khung gốc |
| Độ sắc nét | Laplacian variance > ngưỡng cấu hình (mặc định: 80) |
| Đối tượng | Người nổi tiếng Việt Nam |
| Bối cảnh | Chấp nhận mọi ngữ cảnh phù hợp (sự kiện, biểu diễn, thể thao, đi biển...) |
| Số lượng / người | Tối thiểu 20 — Tối đa 50 — Mục tiêu vận hành: 25–30 |
Python >= 3.10
OS: macOS / Linux (Windows cần WSL2)
RAM: >= 8 GB (16 GB khuyến nghị nếu chạy batch lớn)
Disk: >= 20 GB free cho raw images
git clone https://github.com/Purin1410/AIO_Datasets_Tool4ReIDProject.git
cd AIO_Datasets_Tool4ReIDProject
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtKhông bao giờ để API key trong config.yaml hay commit lên Git.
cp .env.example .env
# Mở .env và điền:
# SERPAPI_KEY=your_key_here
# GOOGLE_CSE_KEY=your_key_here # nếu dùng Google CSE
# GOOGLE_CSE_CX=your_cx_id_hereFile .env.example:
SERPAPI_KEY=
GOOGLE_CSE_KEY=
GOOGLE_CSE_CX=pip install mediapipe
# Hoặc nếu dùng YOLO:
pip install ultralytics# 1. Tạo roster
cp data/roster/roster_example.csv data/roster/roster.csv
# Chỉnh sửa roster.csv theo danh sách nhân vật của bạn
# 2. Validate roster
python -m src.main validate-roster --roster data/roster/roster.csv
# 3. Harvest candidates (chạy search, CHƯA download ảnh gốc)
python -m src.main harvest --roster data/roster/roster.csv
# 4. Fast filter (thumbnail + full-body check)
python -m src.main fast-filter --batch all
# 5. Download ảnh gốc (chỉ ảnh pass sơ bộ)
python -m src.main download --only shortlisted
# 6. Strict filter (resolution, blur, dedupe)
python -m src.main strict-filter --batch all
# 7. Build review queue
python -m src.main build-review-queue --batch all
# 8. Mở UI review
streamlit run src/ui/streamlit_app.py
# 9. Export kết quả
python -m src.main export --status accepted| # | Nguyên tắc | Lý do |
|---|---|---|
| 1 | Link-first, download-later | Tránh tốn băng thông và disk cho ảnh rác |
| 2 | Cascade filtering | Filter rẻ trước, filter nặng sau; không chạy model nặng toàn bộ |
| 3 | Semi-automatic, not fully automatic | Tool tăng tốc — người vận hành vẫn là quyết định cuối |
| 4 | Review throughput > search throughput | Tăng tốc lớn nhất đến từ giảm số ảnh phải xem tay |
| 5 | Resumable & auditable | Mọi bước có log, metadata, trạng thái; có thể dừng giữa chừng và chạy tiếp |
| 6 | Backend-agnostic | Search connector phải có abstraction layer; dễ thay Google / SerpAPI |
| 7 | Config-driven, not hardcoded | Mọi threshold đều ở config.yaml; không hardcode trong code |
| 8 | Secrets in .env, never in config |
Bảo mật API key |
[Input Roster (.csv/.xlsx)]
│
▼
[roster_manager] — validate, chuẩn hóa ID, tracking status
│
▼
[query_builder] — sinh bộ queries đa dạng / nhân vật
│
▼
[search_connector] — gọi API (SerpAPI / Google CSE / ...)
│
▼
[result_store] — lưu raw results (SQLite / JSONL)
│
▼
[url_prefilter] — loại URL rác, domain blacklist, trùng sau normalize
│
▼
[thumbnail_fetcher] — tải thumbnail nhỏ (timeout ngắn)
│
▼
[fast_visual_filter] — person detection, full-body heuristic, poster rejection
│
▼
[candidate_scorer] — tổng hợp score đa chiều
│
▼
[download_manager] — tải ảnh gốc (chỉ ảnh pass sơ bộ)
│
▼
[strict_technical_filter] — format, min-side > 400, blur check
│
▼
[dedupe_engine] — exact + near-duplicate (pHash)
│
▼
[diversity_manager] — cân bằng góc chụp / bối cảnh
│
▼
[review_queue_builder] — tạo shortlist tối ưu per person
│
▼
[manual_review_ui] — grid view + phím nóng
│
▼
[exporter] — export accepted images + metadata + audit log
- Tạo / cập nhật
roster.csvvới các cột:person_id,person_name,field,aliases,target_final_count - Chạy
validate-rosterđể phát hiện ID trùng, thiếu cột - Kiểm tra
statustừng người:todo / in_progress / done / blocked
Checkpoint: Roster hợp lệ, không có ID trùng → pass Phase A.
query_buildersinh 4–8 query / người (base + full-body bias + field-specific)search_connectorgọi API lấy kết quả (mặc định 30 kết quả / query)result_storelưu raw results vào SQLite- Chưa tải ảnh gốc
Checkpoint: raw_results count / người đạt ≥ 60 → pass Phase B.
url_prefilterchuẩn hóa URL, loại domain blacklist, loại trùngthumbnail_fetchertải thumbnail nhỏ (timeout 5s, retry 2 lần)fast_visual_filterchạy person detection + full-body heuristiccandidate_scorerchấm điểm tổng hợp- Chỉ candidates score đạt ngưỡng mới vào
download queue
Checkpoint: shortlisted_count / người ≥ 30 → pass Phase C.
download_managertải ảnh gốc (concurrent, retry 3 lần, checksum)strict_technical_filterkiểm tra format, resolution, blurdedupe_engineloại exact duplicate (file hash) và near-duplicate (pHash)diversity_managergắn bucket đa dạng (góc chụp, bối cảnh, orientation)
Checkpoint: technical_pass count / người ≥ 25 → pass Phase D.
review_queue_buildertạo shortlist tối ưu (top N theo score, 1–3 ảnh / cluster)- Reviewer mở Streamlit UI, review từng ảnh với phím nóng
A / R / S / U - Gắn nhãn:
accepted,rejected_*,saved_raw,unsure - Nếu sau review chưa đủ
target_final_count, chạy fill-gap: bổ sung query mới
exporterxuất ảnh final vào thư mục export chuẩn hóa theoperson_id- Xuất
final_metadata.csvvàreview_log.csv - Sync lên Google Drive / hệ thống quản lý chung
- Cập nhật
statustrong roster →donehoặcblocked
Chức năng: Đọc, validate và tracking danh sách nhân vật.
Input: roster.csv hoặc roster.xlsx
Schema chuẩn:
person_id,person_name,field,aliases,target_final_count,status,notes
VNC001,Son Tung M-TP,singer,"Son Tung|Nguyen Thanh Tung",30,todo,
VNC002,H'Hen Nie,model,"HHen Nie|H Hen Nie",30,todo,
VNC003,Quang Hai,athlete,"Nguyen Quang Hai",25,done,Đã đủ ảnhTrách nhiệm:
- Validate schema và kiểu dữ liệu
- Loại / cảnh báo ID trùng
- Đánh dấu người đã đủ ảnh / đang làm / chưa làm / bị block
- Hỗ trợ
--personflag để chạy riêng 1 người - Hỗ trợ
--batchflag để chạy theo nhóm field
Chức năng: Sinh bộ query search đa dạng cho từng nhân vật.
Input: person_name, aliases, field
Output: Danh sách query strings
Template chuẩn:
| Nhóm | Query |
|---|---|
| Base | {name}, {name} vietnam, {name} official, {name} instagram |
| Full-body bias | {name} full body, {name} standing, {name} event, {name} red carpet |
| Singer / Performer | {name} concert, {name} performance, {name} stage |
| Actor / Actress | {name} premiere, {name} press event, {name} photoshoot |
| Athlete | {name} competition, {name} training, {name} sport event |
| Model / KOL | {name} lookbook, {name} editorial, {name} fashion week |
Lưu ý:
- Sinh query từ
aliasesđể tránh bỏ sót ảnh dùng tên khác - Cho phép override thủ công trong
roster.csvcộtcustom_queries - Không query quá rộng gây nhiễu (e.g. tên quá ngắn mà không kèm
vietnam)
Chức năng: Giao tiếp với backend image search, chuẩn hóa output.
Interface bắt buộc:
class ImageSearchProvider:
def search_images(self, query: str, limit: int) -> list[dict]:
"""
Trả về list dict theo schema chuẩn nội bộ.
KHÔNG expose raw payload của provider ra ngoài module này.
"""
...Schema output chuẩn hóa (nội bộ):
{
"query": "Son Tung M-TP full body",
"rank": 1,
"title": "...",
"source_page": "https://...",
"image_url": "https://...",
"thumbnail_url": "https://...",
"width_hint": 1200,
"height_hint": 1800,
"provider": "serpapi",
"fetched_at": "2024-01-01T10:00:00Z"
}Providers hỗ trợ:
SerpApiProvider— Google Images qua SerpAPI (ổn định, có cost)GoogleCSEProvider— Google Custom Search JSON API (free tier giới hạn)- Dễ mở rộng thêm provider mới
Trách nhiệm:
- Retry + exponential backoff khi lỗi 429 / 5xx
- Rate limit handling (xem Section 15)
- Cache raw response để tránh gọi lại API khi re-run
- Log số query đã dùng / ngày
Chức năng: Lưu trữ toàn bộ raw results và intermediate state.
Backend: SQLite (mặc định) — đủ cho scale 100 người, không cần setup server.
Bảng chính:
| Bảng | Mô tả |
|---|---|
persons |
Danh sách nhân vật + status |
queries |
Mỗi query đã chạy per person |
raw_results |
Kết quả thô từ search API |
candidates |
Kết quả sau prefilter, kèm state machine |
technical_metrics |
Kết quả strict filter per candidate |
visual_metrics |
Kết quả fast visual filter per candidate |
review_records |
Log review action của reviewer |
exports |
Lịch sử export |
Mục tiêu: Có thể dừng pipeline giữa chừng và resume mà không mất trạng thái.
Chức năng: Loại URL rác trước khi tải bất kỳ thứ gì.
Rules:
- Chỉ giữ URL có extension / content-type là
jpg,jpeg,png - Loại URL từ domain blacklist (thumbnail host chất lượng thấp, watermark nặng)
- Loại URL thumbnail cực nhỏ (suy ra từ URL params nếu có)
- Normalize URL trước khi dedupe (strip query params tracking)
- Loại URL duplicate sau normalize
Output: Danh sách URL hợp lệ để fetch thumbnail.
Chức năng: Tải thumbnail nhỏ để chạy fast visual filter mà không tốn bandwidth tải ảnh gốc.
Yêu cầu:
- Timeout: 5 giây (không đợi lâu hơn)
- Retry: tối đa 2 lần
- Cache local theo hash URL (tránh fetch lại khi re-run)
- Resize về max 480px trước khi lưu để tiết kiệm disk
Output: local_thumbnail_path, detected_width, detected_height, mime_type
Chức năng: Chạy các kiểm tra thị giác nhanh trên thumbnail.
Sub-checks:
- Có người trong ảnh không?
- Số lượng person bounding boxes
- Subject chính là person box lớn nhất
Heuristic full-body — không cần nhận diện đúng người:
| Điều kiện | Ngưỡng khuyến nghị |
|---|---|
| Person bbox height / image height | ≥ 55–70% |
| Đầu không bị cắt mất | Kiểm tra y_top của bbox |
| Chân không bị cắt quá nặng | Kiểm tra y_bottom của bbox |
| Pose: thấy shoulder + hip + knee | MediaPipe Pose landmarks |
| Pose: thấy ankle (nếu có) | Bonus score |
- Ảnh có quá nhiều text / poster / collage không?
- Subject có quá nhỏ so với khung (< 20% diện tích) không?
Output:
{
"has_person": bool,
"person_count": int,
"main_bbox_ratio": float, # height of main bbox / image height
"full_body_score": float, # 0.0 - 1.0
"composition_score": float, # 0.0 - 1.0
"fast_pass": bool
}Chức năng: Tổng hợp điểm từ nhiều tín hiệu để xếp hạng candidates.
Công thức:
candidate_score =
0.35 × full_body_score
+ 0.20 × subject_prominence_score
+ 0.15 × resolution_hint_score
+ 0.15 × source_quality_score
+ 0.10 × composition_score
+ 0.05 × query_diversity_score
Trọng số có thể điều chỉnh trong
config.yamlmụcscoring.weights.
Phân tier:
| Tier | Điều kiện | Hành động |
|---|---|---|
| A | score cao, full-body tốt | Download ngay, review trước |
| B | đủ điều kiện sơ bộ nhưng chưa mạnh | Giữ lại xem sau |
| C | không người / quá nhỏ / poster rõ ràng | Auto-reject |
Chức năng: Tải ảnh gốc cho candidates đã pass sơ bộ.
Yêu cầu:
- Concurrent download (mặc định: 4 workers, cấu hình được)
- Timeout: 20 giây / ảnh
- Retry: 3 lần với exponential backoff
- Checksum SHA256 sau download
- Không tải lại file đã có (kiểm tra checksum hoặc path)
- Ghi log lỗi HTTP status + URL cho ảnh fail
Output: local_image_path, file_size_bytes, sha256_checksum, http_status
Chức năng: Kiểm tra các yêu cầu kỹ thuật "cứng" trên ảnh gốc đã download.
Hard gates (bắt buộc):
| Check | Điều kiện pass |
|---|---|
| Format | File header là JPEG hoặc PNG (không tin vào extension) |
| Resolution | min(width, height) > 400 |
| Corrupted | Đọc được bằng Pillow / OpenCV không lỗi |
| Blur | Laplacian variance > blur_threshold (mặc định: 80) |
Soft checks (optional, ghi vào metadata):
- Watermark detection (heuristic)
- Text-heavy detection
- NSFW flag nếu cần
Output: format_ok, resolution_ok, blur_score, technical_pass
Chức năng: Loại ảnh trùng và gần trùng.
Lý do bắt buộc: Nếu không có bước này, reviewer sẽ mất thời gian vào hàng loạt ảnh cùng buổi chụp / outfit / pose.
3 mức dedupe:
| Level | Phương pháp | Mục tiêu |
|---|---|---|
| 1 — Exact | SHA256 file hash | Cùng file y hệt |
| 2 — Near-duplicate | pHash / dHash, Hamming distance ≤ 8 | Ảnh resize, crop nhẹ, nén khác nhau |
| 3 — Semantic (v2) | Image embeddings + cosine similarity clustering | Cùng buổi chụp, outfit |
Output: duplicate_group_id, is_primary_representative, duplicate_level
Chức năng: Cân bằng shortlist để không dồn quá nhiều ảnh cùng một kiểu.
Bucket phân loại:
| Dimension | Buckets |
|---|---|
| Orientation | portrait / landscape / square |
| Setting | indoor / outdoor / stage / sports / beach |
| Framing | close-up / half-body / full-body |
| Facing | left / right / front / back |
| Source | instagram / news / event / fan_page |
| Query family | base / full-body-bias / field-specific |
Output: diversity_bucket, cluster_rank (rank trong bucket, 1 = đại diện tốt nhất)
Chức năng: Tạo shortlist review tối ưu cho từng người.
Quy tắc:
- Lấy top N ảnh theo score sau khi dedupe
- Mỗi cluster chỉ lấy 1–3 ảnh đại diện
- Ưu tiên phủ đủ các diversity bucket
- Sắp xếp: Tier A trước, trong Tier A sort by score giảm dần
Stop rule khi build queue:
- Dừng thêm nếu đã có
25 ảnh Tier A + 15 ảnh Tier B, hoặc - Đã có
≥ 35 provisional passbất kể tier
Output: Ordered list of candidate_id chờ reviewer xem.
Chức năng: Giao diện review nhanh với phím nóng, chạy bằng Streamlit.
Layout:
- Grid view (4–6 ảnh / hàng)
- Click vào ảnh → preview full size + metadata (score, source, query, resolution)
Phím nóng:
| Phím | Hành động |
|---|---|
A |
Accept |
R |
Reject (hỏi lý do) |
S |
Save raw link only (chưa chắc dùng) |
U |
Unsure (để lại xem sau) |
O |
Mở source page trong browser |
D |
Download original nếu chưa có |
Bộ lọc sidebar:
- Chỉ xem: technical pass / full-body pass / unique / unresolved
- Sort by: score / source / query / resolution
Nhãn reject:
rejected_wrong_personrejected_low_qualityrejected_not_full_bodyrejected_duplicate_viberejected_bad_croprejected_bad_context
Chức năng: Xuất dữ liệu final để đưa vào Drive / metadata chung.
Output files:
| File | Mô tả |
|---|---|
accepted_images/{person_id}/ |
Thư mục ảnh final, đặt tên {person_id}_{seq:04d}.jpg |
final_metadata.csv |
Metadata đầy đủ mỗi ảnh accepted |
review_log.csv |
Toàn bộ action của reviewer |
raw_candidates.csv |
Toàn bộ candidates kèm state cuối |
audit_summary.txt |
Tóm tắt số liệu mỗi người |
Schema final_metadata.csv:
person_id,person_name,field,image_id,local_path,source_page,image_url,width,height,format,candidate_score,final_status,reviewer,reviewed_at,notesChức năng: Ghi lại mọi quyết định và lỗi quan trọng.
Log nên ghi:
- Query nào sinh ra candidate nào
- Candidate bị reject ở bước nào và lý do
- Ảnh nào được accept/reject bởi reviewer nào, lúc nào
- Download error + URL
- API quota consumed per day
Format: Structured JSON logs + human-readable summary per person.
Chức năng: Quản lý toàn bộ config hệ thống từ config.yaml.
Nguyên tắc:
- API keys không được trong config.yaml — phải từ
.env - Mọi threshold đều configurable, không hardcode
- Hỗ trợ
--configflag để chỉ định file config khác
Xem Phụ lục 23 để biết full config example.
new
└─► url_prefilter_rejected
└─► thumbnail_fetching
└─► thumbnail_failed
└─► thumbnail_ready
└─► fast_filter_rejected
└─► fast_filter_passed
└─► download_queued
└─► download_failed
└─► downloaded
└─► technical_rejected
└─► deduped_out
└─► shortlisted
└─► accepted
└─► rejected
└─► saved_raw
└─► unsure
Quy ước: Tất cả state dùng
lowercase_snake_case. Không dùng UPPERCASE trong code hay database.
| Field | Type | Ghi chú |
|---|---|---|
person_id |
TEXT PK | e.g. VNC001 |
person_name |
TEXT | Tên chính |
field |
TEXT | singer / actor / athlete / model / kol / ... |
aliases |
TEXT | Pipe-separated |
target_final_count |
INT | Default: 30 |
status |
TEXT | todo / in_progress / done / blocked |
accepted_count |
INT | Cập nhật sau mỗi review session |
notes |
TEXT | Ghi chú thủ công |
| Field | Type | Ghi chú |
|---|---|---|
candidate_id |
TEXT PK | UUID |
person_id |
TEXT FK | |
query_id |
TEXT FK | |
image_url |
TEXT | |
source_page |
TEXT | |
thumbnail_url |
TEXT | |
provider |
TEXT | serpapi / google_cse |
rank |
INT | Rank trong kết quả search |
state |
TEXT | State machine |
candidate_score |
REAL | 0.0–1.0 |
created_at |
DATETIME | |
updated_at |
DATETIME |
| Field | Type |
|---|---|
candidate_id |
TEXT FK |
width |
INT |
height |
INT |
format |
TEXT |
file_size_bytes |
INT |
blur_score |
REAL |
sha256 |
TEXT |
technical_pass |
BOOL |
| Field | Type |
|---|---|
candidate_id |
TEXT FK |
has_person |
BOOL |
person_count |
INT |
main_bbox_ratio |
REAL |
full_body_score |
REAL |
subject_prominence_score |
REAL |
composition_score |
REAL |
diversity_bucket |
TEXT |
| Field | Type |
|---|---|
candidate_id |
TEXT FK |
review_status |
TEXT |
reject_reason |
TEXT |
reviewer |
TEXT |
reviewed_at |
DATETIME |
notes |
TEXT |
project/
├── .env ← KHÔNG commit, chứa API keys
├── .env.example ← Template public, không có key thật
├── .gitignore ← Phải ignore .env và data/raw/
├── README.md
├── requirements.txt
├── config/
│ ├── config.yaml ← Cấu hình toàn bộ (không có secret)
│ └── domain_blacklist.txt ← Danh sách domain bị chặn
├── data/
│ ├── roster/
│ │ ├── roster.csv ← File quản lý nhân vật (edit trực tiếp)
│ │ └── roster_example.csv
│ ├── raw/
│ │ ├── thumbnails/ ← Cache thumbnails (có thể xóa nếu cần)
│ │ └── originals/ ← Ảnh gốc đã download
│ ├── processed/
│ │ ├── review_queue/ ← Shortlist per person (JSON)
│ │ └── exports/ ← Output cuối: ảnh + metadata
│ ├── db/
│ │ └── pipeline.db ← SQLite database chính
│ └── logs/
│ ├── harvest.log
│ ├── filter.log
│ └── audit.log
├── src/
│ ├── main.py ← CLI entry point
│ ├── pipelines/
│ │ ├── harvest_pipeline.py
│ │ ├── filter_pipeline.py
│ │ └── export_pipeline.py
│ ├── modules/
│ │ ├── roster_manager.py
│ │ ├── query_builder.py
│ │ ├── search_connector.py
│ │ ├── result_store.py
│ │ ├── url_prefilter.py
│ │ ├── thumbnail_fetcher.py
│ │ ├── fast_visual_filter.py
│ │ ├── candidate_scorer.py
│ │ ├── download_manager.py
│ │ ├── strict_technical_filter.py
│ │ ├── dedupe_engine.py
│ │ ├── diversity_manager.py
│ │ ├── review_queue_builder.py
│ │ ├── exporter.py
│ │ ├── audit_logger.py
│ │ └── config_manager.py
│ ├── providers/
│ │ ├── base.py ← Abstract ImageSearchProvider
│ │ ├── serpapi_provider.py
│ │ └── google_cse_provider.py
│ └── ui/
│ └── streamlit_app.py
├── tests/
│ ├── conftest.py
│ ├── test_roster_manager.py
│ ├── test_query_builder.py
│ ├── test_url_prefilter.py
│ ├── test_scoring.py
│ ├── test_dedupe_engine.py
│ └── fixtures/ ← Sample images và mock API responses
└── scripts/
├── init_db.py ← Tạo database lần đầu
└── reset_person.py ← Reset state của 1 người về todo
Pass/fail cứng loại quá nhiều ảnh borderline mà reviewer vẫn có thể dùng được. Score giúp:
- Ưu tiên candidate đáng xem trước (Tier A review trước Tier B)
- Giữ lại ảnh trung bình để dự phòng khi Tier A chưa đủ
- Tối ưu số ảnh phải tải và phải review
min(width, height) > 400 px → fail = loại ngay
format ∈ {jpg, jpeg, png} → fail = loại ngay
ảnh đọc được (không corrupt) → fail = loại ngay
laplacian variance > 80 → fail = loại ngay (configurable)
full_body_score × 0.35 ← trọng số cao nhất
subject_prominence × 0.20
resolution_hint × 0.15
source_quality × 0.15
composition × 0.10
query_diversity × 0.05
| Tier | Điều kiện | Hành động |
|---|---|---|
| A — Priority | score ≥ 0.65 + full_body_score ≥ 0.6 | Download ngay, review đầu tiên |
| B — Reserve | score ≥ 0.40 | Giữ lại, review sau nếu Tier A chưa đủ |
| C — Auto-reject | score < 0.40 hoặc fail hard gate | Loại, không review |
Không làm từng người từ đầu đến cuối. Thay vào đó chạy breadth-first:
| Pass | Hành động | Lý do |
|---|---|---|
| Pass 1 — Harvest all | Search toàn bộ 100 người, lưu raw results | Nhìn được coverage sớm, phát hiện ai khó tìm ngay |
| Pass 2 — Fast filter all | Thumbnail + visual filter toàn bộ | Chạy batch nhanh hơn per-person |
| Pass 3 — Build shortlists | Tạo review queue per person | Chuẩn bị cho reviewer |
| Pass 4 — Manual review | Reviewer xem shortlist | Đây là bước tốn thời gian nhất của người |
| Pass 5 — Fill-gap | Người chưa đủ → chạy thêm query bổ sung | Tập trung resource vào người khó |
Lợi ích:
- Phát hiện sớm ai thiếu ảnh để tăng cường query
- Không kẹt quá lâu vào 1 người khó tìm
- Dễ theo dõi tiến độ toàn project
≥ 35ảnh provisional pass (Tier A + B), hoặc≥ 25ảnh Tier A và≥ 10ảnh Tier B, hoặc- Reviewer đã accept đủ
target_final_count
- Người đã có buffer tốt (đủ Tier A)
- Toàn bộ query templates đã dùng hết và không có alias mới
- Sau review,
accepted_count < target_final_count × 0.8→ sinh thêm queries từ alias hoặc template khác chưa dùng
- Đúng người không? (yes / no → reject_wrong_person)
- Ảnh usable không? (yes / no → reject_low_quality / reject_bad_crop / ...)
- Quá trùng với ảnh đã accept không? (yes → reject_duplicate_vibe)
| Nhãn | Mô tả |
|---|---|
accepted |
Đưa vào dataset |
rejected_wrong_person |
Không phải người cần lấy |
rejected_low_quality |
Mờ, vỡ hạt, ánh sáng xấu |
rejected_not_full_body |
Bị cắt quá nhiều |
rejected_duplicate_vibe |
Quá giống ảnh đã accept (outfit / pose) |
rejected_bad_crop |
Crop sai, mất đầu / chân |
rejected_bad_context |
Ngữ cảnh không phù hợp |
saved_raw |
Giữ link, chưa chắc dùng |
unsure |
Cần xem lại sau |
- Tier A: ~3–5 giây / ảnh (quyết định nhanh)
- Tier B: ~5–10 giây / ảnh
- Mục tiêu: reviewer xử lý 40 ảnh shortlist / người trong 5–7 phút
- Số query / người, số raw results / query
- Số candidate pass từng gate (tỷ lệ pass rate)
- Download errors (URL + HTTP status)
- Số exact duplicate / near-duplicate loại ra
- Số ảnh accepted / rejected / saved_raw per person
- Thời gian xử lý từng phase
- API quota consumed per day
| Widget | Nội dung |
|---|---|
| Progress bar | Số người done / total |
| Table | Người đã đủ ảnh / chưa đủ / blocked |
| Bar chart | Tỷ lệ pass qua từng filter |
| Top sources | Domain nào cho ảnh chất lượng cao nhất |
| Top queries | Query template hiệu quả nhất |
| Daily API usage | Quota consumed today |
| Quy mô | Queries | Ước tính cost |
|---|---|---|
| 14 người × 6 query | 84 queries | ~$0.84 (SerpAPI: ~$0.01/query) |
| 100 người × 6 query | 600 queries | ~$6.00 |
| 100 người × 8 query (fill-gap included) | ~800 queries | ~$8.00 |
SerpAPI plan free: 100 searches/tháng. Trả phí từ $50/tháng cho 5,000 searches. Google CSE: 100 queries/ngày miễn phí, sau đó $5 / 1,000 queries.
| Phase | Thời gian ước tính |
|---|---|
| Harvest (600 queries, 30 results each) | ~15–30 phút (rate limit) |
| Fast filter (18,000 thumbnails) | ~30–60 phút |
| Download (~3,000 ảnh pass) | ~20–40 phút (4 workers) |
| Strict filter + dedupe | ~10–15 phút |
| Manual review (100 × 40 ảnh) | ~8–10 giờ (reviewer) |
| Data | Dung lượng |
|---|---|
| Thumbnails (18,000 × ~20KB) | ~360 MB |
| Original images (~3,000 × ~500KB) | ~1.5 GB |
| Final accepted (~3,000 × ~500KB) | ~1.5 GB |
| Database + logs | ~50 MB |
| Tổng | ~3.5 GB |
| Thành phần | Lựa chọn |
|---|---|
| Language | Python 3.10+ |
| HTTP | httpx (async) hoặc requests |
| Image processing | Pillow, OpenCV |
| Data | pandas, SQLite (built-in) |
| UI | Streamlit |
| Config | PyYAML, python-dotenv |
| CLI | Click hoặc argparse |
| Tác vụ | Model khuyến nghị | Ghi chú |
|---|---|---|
| Person detection | MediaPipe BlazePose (full) |
Nhẹ, nhanh, chạy CPU được |
| Person detection (alternative) | YOLOv8n (nano) |
Nếu cần bbox chính xác hơn |
| Exact dedupe | SHA256 | Built-in |
| Near-duplicate | pHash (imagehash lib) |
Hamming distance ≤ 8 |
| Semantic clustering (v2) | CLIP embeddings | Optional, chỉ cần nếu near-dup không đủ |
- Chạy được trên CPU, không cần GPU
- Detect 33 landmarks → đủ để đánh giá full-body
- Latency ~50–100ms / ảnh trên CPU thông thường
- Free, không cần API key
- Build nhanh, đủ cho grid review
- Hot reload khi develop
- Phù hợp internal tool, không cần deploy phức tạp
| # | Module | Bắt buộc |
|---|---|---|
| 1 | config_manager + .env |
✅ |
| 2 | roster_manager |
✅ |
| 3 | query_builder |
✅ |
| 4 | search_connector (SerpAPI trước) |
✅ |
| 5 | result_store (SQLite) |
✅ |
| 6 | url_prefilter |
✅ |
| 7 | thumbnail_fetcher |
✅ |
| 8 | fast_visual_filter (MediaPipe) |
✅ |
| 9 | candidate_scorer |
✅ |
| 10 | download_manager |
✅ |
| 11 | strict_technical_filter |
✅ |
| 12 | dedupe_engine (Level 1+2) |
✅ |
| 13 | review_queue_builder |
✅ |
| 14 | manual_review_ui (Streamlit) |
✅ |
| 15 | exporter |
✅ |
| # | Module | Ưu tiên |
|---|---|---|
| 16 | diversity_manager |
Cao |
| 17 | audit_logger (structured) |
Cao |
| 18 | google_cse_provider (backup) |
Trung bình |
| 19 | Dashboard Streamlit | Trung bình |
| # | Tính năng | Ghi chú |
|---|---|---|
| 20 | Semantic clustering (CLIP) | Giảm ảnh cùng vibe hơn nữa |
| 21 | Source reputation model | Học từ lịch sử review |
| 22 | Adaptive query expansion | Tự sinh query mới khi thiếu ảnh |
| 23 | Optional identity-assist ranking | Dùng face embedding để gợi ý, không quyết định |
| Rủi ro | Mức độ | Mitigation |
|---|---|---|
| Provider rate limit / thay đổi API | Cao | Cache raw responses; abstraction layer để đổi provider |
| Nhiều URL ảnh chết / bị chặn | Cao | Retry + fallback; lưu source page để reviewer tự mở |
| Full-body detector bỏ sót hoặc nhận sai | Trung bình | Chỉ dùng như soft gate; reviewer vẫn là bước cuối |
| Reviewer mất thời gian vì ảnh trùng | Trung bình | Dedupe engine tốt + diversity_manager |
| Metadata lệch với ảnh final | Trung bình | Exporter và audit_log là nguồn chuẩn; cross-check khi export |
| IP bị block khi fetch thumbnail | Trung bình | Jitter giữa requests; tôn trọng robots.txt; không brute-force |
| API key bị lộ | Cao | Luôn dùng .env; không commit secrets; .gitignore rõ ràng |
| Ảnh vi phạm bản quyền / ToS | Trung bình | Xem Section 20 |
- Có đủ
target_final_countảnh có statusaccepted - Metadata đầy đủ (không thiếu field nào trong schema)
- Ảnh final đã export ra thư mục
exports/{person_id}/ - Review log có đủ dấu vết (reviewer, timestamp, reason)
- Không còn candidate ở trạng thái
unsurequan trọng chưa xử lý - Status trong roster.csv đã cập nhật thành
done
- Mọi người trong roster đạt
donehoặcblocked(kèm lý do) -
final_metadata.csvnhất quán với roster và ảnh trên Drive - Audit log đủ để truy lại lịch sử xử lý bất kỳ ảnh nào
- Không có lỗi unresolved trong
audit.log
⚠️ Đọc trước khi sử dụng.
-
Google Images Terms of Service: Công cụ này sử dụng API chính thức (SerpAPI / Google CSE), không phải scraping trực tiếp. Tuy nhiên, ảnh thu thập được có thể có bản quyền thuộc về nhiều bên khác nhau.
-
Mục đích sử dụng: Công cụ này được thiết kế cho mục đích nghiên cứu và xây dựng dataset AI nội bộ. Không sử dụng để phân phối ảnh thương mại hoặc vi phạm bản quyền.
-
Đối tượng trong ảnh: Tôn trọng quyền riêng tư cá nhân. Không thu thập ảnh có tính chất riêng tư, nhạy cảm hoặc không phù hợp.
-
Rate limiting & politeness: Không spam API hay server. Tôn trọng
robots.txtkhi fetch thumbnail trực tiếp. Thêm jitter giữa các request. -
Lưu trữ nội bộ: Dataset thu thập chỉ nên dùng nội bộ. Không upload công khai nếu chưa xác minh quyền sở hữu ảnh.
# Validate roster
python -m src.main validate-roster --roster data/roster/roster.csv
# Harvest tất cả
python -m src.main harvest --roster data/roster/roster.csv
# Harvest 1 người
python -m src.main harvest --person VNC001
# Fast filter
python -m src.main fast-filter --batch all
python -m src.main fast-filter --person VNC001
# Download chỉ ảnh shortlisted
python -m src.main download --only shortlisted
python -m src.main download --only shortlisted --person VNC001 --workers 6
# Strict filter
python -m src.main strict-filter --batch all
# Build review queue
python -m src.main build-review-queue --batch all
python -m src.main build-review-queue --person VNC001 --shortlist-size 40
# Mở UI review
streamlit run src/ui/streamlit_app.py
# Export
python -m src.main export --status accepted
python -m src.main export --person VNC001 --status accepted
# Xem tiến độ
python -m src.main status --batch all
python -m src.main status --person VNC001
# Reset 1 người (về todo, giữ nguyên ảnh đã download)
python scripts/reset_person.py --person VNC001 --keep-downloads
# Khởi tạo database lần đầu
python scripts/init_db.pynew ← Vừa lấy từ search API
url_prefilter_rejected ← Bị loại ở URL prefilter
thumbnail_fetching ← Đang fetch thumbnail
thumbnail_failed ← Fetch thumbnail lỗi
thumbnail_ready ← Có thumbnail local
fast_filter_rejected ← Bị loại ở fast visual filter
fast_filter_passed ← Qua fast filter
download_queued ← Đang chờ download gốc
download_failed ← Download gốc lỗi
downloaded ← Đã có ảnh gốc local
technical_rejected ← Bị loại ở strict technical filter
deduped_out ← Bị loại vì duplicate
shortlisted ← Trong review queue
accepted ← Reviewer accept
rejected ← Reviewer reject (kèm reason)
saved_raw ← Giữ link, chưa chắc dùng
unsure ← Reviewer chưa quyết định
# config/config.yaml
# KHÔNG để API key ở đây — dùng .env
search:
provider: serpapi # serpapi | google_cse
per_query_limit: 30 # số kết quả / query
queries_per_person: 6 # số query / người (base + field)
cache_raw_responses: true
cache_ttl_hours: 72
filter:
min_side_px: 401 # strict: min(w,h) > 400
blur_threshold: 80 # Laplacian variance
full_body_threshold: 0.55 # full_body_score tối thiểu để pass
person_bbox_ratio_min: 0.50 # bbox height / image height
dedupe:
phash_hamming_threshold: 8 # Hamming distance ≤ 8 → near-duplicate
scoring:
weights:
full_body: 0.35
subject_prominence: 0.20
resolution_hint: 0.15
source_quality: 0.15
composition: 0.10
query_diversity: 0.05
tier_a_threshold: 0.65
tier_b_threshold: 0.40
download:
workers: 4
timeout_seconds: 20
max_retries: 3
backoff_factor: 2.0
review:
shortlist_per_person: 40
target_final_per_person: 30
stop_rules:
max_provisional_pass: 35
tier_a_sufficient: 25
tier_b_sufficient: 10
paths:
thumbnails: data/raw/thumbnails
originals: data/raw/originals
exports: data/processed/exports
db: data/db/pipeline.db
logs: data/logsĐây là công cụ tăng tốc search + sàng lọc bán tự động, thiết kế để giảm tối đa thao tác tay khi thu thập ảnh người nổi tiếng Việt Nam ở quy mô lớn. Toàn bộ pipeline chạy theo nguyên tắc link-first → cascade filter → score → shortlist, và giữ manual review là lớp kiểm soát chất lượng cuối cùng không thể bỏ qua.