From a4e7573dcae906b5e08f3f30a930ee6d90d80aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ph=C6=B0=E1=BB=9Bc=20Th=C3=A0nh?= <93478665+Zeres-Engel@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:21:47 +0700 Subject: [PATCH] YOLO crop model --- README_ID_Card_Cropper.md | 56 +++++ README_ID_Card_Processing.md | 183 -------------- config/roboflow_config.yaml | 40 +++ id_card_processor_main.py | 234 ------------------ logs/roboflow_detector.log | 66 +++++ script/id_card_cropper.py | 133 ++++++++++ src/model/__init__.py | 7 +- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 338 bytes src/model/__pycache__/__init__.cpython-39.pyc | Bin 352 -> 318 bytes .../roboflow_id_detector.cpython-313.pyc | Bin 0 -> 14131 bytes .../roboflow_id_detector.cpython-39.pyc | Bin 0 -> 9059 bytes .../__pycache__/yolo_detector.cpython-39.pyc | Bin 7104 -> 7117 bytes ...lo_detector.py => roboflow_id_detector.py} | 171 +++++++++---- 13 files changed, 420 insertions(+), 470 deletions(-) create mode 100644 README_ID_Card_Cropper.md delete mode 100644 README_ID_Card_Processing.md create mode 100644 config/roboflow_config.yaml delete mode 100644 id_card_processor_main.py create mode 100644 logs/roboflow_detector.log create mode 100644 script/id_card_cropper.py create mode 100644 src/model/__pycache__/__init__.cpython-313.pyc create mode 100644 src/model/__pycache__/roboflow_id_detector.cpython-313.pyc create mode 100644 src/model/__pycache__/roboflow_id_detector.cpython-39.pyc rename src/model/{yolo_detector.py => roboflow_id_detector.py} (62%) diff --git a/README_ID_Card_Cropper.md b/README_ID_Card_Cropper.md new file mode 100644 index 0000000..c34a2aa --- /dev/null +++ b/README_ID_Card_Cropper.md @@ -0,0 +1,56 @@ +# ID Card Cropper + +Script đơn giản để cắt ID cards từ ảnh sử dụng Roboflow API. + +## Cách sử dụng + +```bash +python id_card_cropper.py input_folder output_folder +``` + +### Ví dụ: + +```bash +# Sử dụng API key mặc định +python id_card_cropper.py data/IDcards/Archive output/cropped_cards + +# Sử dụng API key tùy chỉnh +python id_card_cropper.py data/IDcards/Archive output/cropped_cards --api-key YOUR_API_KEY +``` + +## Tham số + +- `input_folder`: Thư mục chứa ảnh cần xử lý +- `output_folder`: Thư mục lưu ID cards đã cắt +- `--api-key`: API key Roboflow (mặc định: demo key) + +## Hỗ trợ định dạng ảnh + +- JPG/JPEG +- PNG +- BMP +- TIFF + +## Kết quả + +Script sẽ: +1. Tìm tất cả ảnh trong thư mục input +2. Phát hiện ID cards trong mỗi ảnh +3. Cắt và lưu ID cards vào thư mục output +4. Đặt tên file theo format: `{tên_ảnh_gốc}_card_{số}.jpg` + +## Ví dụ kết quả + +``` +output/cropped_cards/ +├── im1__card_1.jpg +├── im5_card_1.jpg +├── im11_card_1.jpg +└── im11_card_2.jpg +``` + +## Lưu ý + +- Cần kết nối internet để sử dụng Roboflow API +- Có delay 1 giây giữa các request để tránh rate limiting +- Chỉ lưu ID cards đã cắt, không lưu ảnh gốc với bounding boxes \ No newline at end of file diff --git a/README_ID_Card_Processing.md b/README_ID_Card_Processing.md deleted file mode 100644 index 3e922a4..0000000 --- a/README_ID_Card_Processing.md +++ /dev/null @@ -1,183 +0,0 @@ -# ID Card Processing with YOLO Detection - -Hệ thống xử lý ID cards sử dụng YOLO để detect và crop, kết hợp với các phương pháp tiền xử lý để clean background và enhance chất lượng ảnh. - -## Tính năng chính - -- **YOLO Detection**: Detect và crop ID cards từ ảnh gốc -- **Background Removal**: 3 phương pháp loại bỏ background (GrabCut, Threshold, Contour) -- **Image Enhancement**: Cải thiện chất lượng ảnh cho OCR -- **Batch Processing**: Xử lý hàng loạt ảnh -- **Flexible Pipeline**: Có thể chạy từng bước riêng biệt - -## Cài đặt - -1. Cài đặt dependencies: -```bash -pip install -r requirements.txt -``` - -2. Cấu trúc thư mục: -``` -OCR/ -├── src/ -│ ├── model/ -│ │ ├── __init__.py -│ │ ├── yolo_detector.py -│ │ └── id_card_processor.py -│ └── ... -├── data/ -│ ├── IDcards/ # Thư mục chứa ảnh ID cards gốc -│ └── processed_id_cards/ # Thư mục output -├── id_card_processor_main.py -└── requirements.txt -``` - -## Sử dụng - -### 1. Full Pipeline (Detect + Preprocess) - -```bash -python id_card_processor_main.py \ - --input-dir "data/IDcards" \ - --output-dir "data/processed_id_cards" \ - --confidence 0.5 \ - --bg-removal grabcut \ - --target-size 800x600 \ - --save-annotated -``` - -### 2. Chỉ Detect và Crop - -```bash -python id_card_processor_main.py \ - --input-dir "data/IDcards" \ - --output-dir "data/processed_id_cards" \ - --detect-only \ - --save-annotated -``` - -### 3. Chỉ Preprocess (bỏ qua detection) - -```bash -python id_card_processor_main.py \ - --input-dir "data/IDcards" \ - --output-dir "data/processed_id_cards" \ - --preprocess-only \ - --bg-removal threshold \ - --target-size 800x600 -``` - -## Các tham số - -### Detection Parameters -- `--model-path`: Đường dẫn đến custom YOLO model (.pt file) -- `--confidence`: Ngưỡng confidence cho detection (default: 0.5) - -### Preprocessing Parameters -- `--bg-removal`: Phương pháp loại bỏ background - - `grabcut`: Sử dụng GrabCut algorithm (recommended) - - `threshold`: Sử dụng thresholding - - `contour`: Sử dụng contour detection - - `none`: Không loại bỏ background -- `--target-size`: Kích thước chuẩn hóa (width x height) - -### Output Options -- `--save-annotated`: Lưu ảnh với bounding boxes -- `--detect-only`: Chỉ chạy detection -- `--preprocess-only`: Chỉ chạy preprocessing - -## Output Structure - -``` -data/processed_id_cards/ -├── cropped/ # Ảnh đã được crop từ YOLO -│ ├── image1_card_1.jpg -│ ├── image1_card_2.jpg -│ └── ... -├── processed/ # Ảnh đã được preprocess -│ ├── image1_card_1_processed.jpg -│ ├── image1_card_2_processed.jpg -│ └── ... -└── annotated/ # Ảnh với bounding boxes (nếu có) - ├── image1_annotated.jpg - └── ... -``` - -## Ví dụ sử dụng - -### Ví dụ 1: Xử lý toàn bộ dataset -```bash -# Xử lý tất cả ảnh trong thư mục IDcards -python id_card_processor_main.py \ - --input-dir "data/IDcards" \ - --output-dir "data/processed_id_cards" \ - --confidence 0.6 \ - --bg-removal grabcut \ - --target-size 1024x768 \ - --save-annotated -``` - -### Ví dụ 2: Test với một vài ảnh -```bash -# Tạo thư mục test với một vài ảnh -mkdir -p data/test_images -# Copy một vài ảnh vào test_images - -# Chạy detection -python id_card_processor_main.py \ - --input-dir "data/test_images" \ - --output-dir "data/test_output" \ - --detect-only \ - --save-annotated -``` - -### Ví dụ 3: Sử dụng custom model -```bash -# Nếu bạn có custom YOLO model đã train -python id_card_processor_main.py \ - --input-dir "data/IDcards" \ - --output-dir "data/processed_id_cards" \ - --model-path "models/custom_id_card_model.pt" \ - --confidence 0.7 -``` - -## Lưu ý - -1. **YOLO Model**: Mặc định sử dụng YOLOv8n pre-trained. Nếu có custom model tốt hơn, hãy sử dụng `--model-path` - -2. **Background Removal**: - - `grabcut`: Tốt nhất cho ID cards có background phức tạp - - `threshold`: Nhanh, phù hợp với background đơn giản - - `contour`: Phù hợp với ID cards có viền rõ ràng - -3. **Performance**: - - Sử dụng GPU nếu có thể để tăng tốc độ detection - - Có thể điều chỉnh `--confidence` để cân bằng giữa precision và recall - -4. **Memory**: Với dataset lớn, có thể cần tăng memory hoặc xử lý theo batch nhỏ hơn - -## Troubleshooting - -### Lỗi thường gặp - -1. **No detections found**: - - Giảm `--confidence` xuống 0.3-0.4 - - Kiểm tra chất lượng ảnh input - -2. **Memory error**: - - Giảm batch size hoặc xử lý từng ảnh một - - Sử dụng CPU thay vì GPU - -3. **Poor background removal**: - - Thử các phương pháp khác nhau: `grabcut`, `threshold`, `contour` - - Điều chỉnh parameters trong code - -### Debug mode - -```bash -python id_card_processor_main.py \ - --input-dir "data/IDcards" \ - --output-dir "data/processed_id_cards" \ - --log-level DEBUG -``` \ No newline at end of file diff --git a/config/roboflow_config.yaml b/config/roboflow_config.yaml new file mode 100644 index 0000000..ab18e2c --- /dev/null +++ b/config/roboflow_config.yaml @@ -0,0 +1,40 @@ +# Roboflow ID Card Detection Configuration + +# API Configuration +api: + key: "Pkz4puRA0Cy3xMOuNoNr" # Your Roboflow API key + model_id: "french-card-id-detect" + version: 3 + confidence: 0.5 + timeout: 30 # seconds + +# Processing Configuration +processing: + input_dir: "data/IDcards" + output_dir: "output/roboflow_detections" + save_annotated: true + delay_between_requests: 1.0 # seconds + padding: 10 # pixels around detected cards + +# Supported image formats +supported_formats: + - ".jpg" + - ".jpeg" + - ".png" + - ".bmp" + - ".tiff" + +# Logging configuration +logging: + level: "INFO" # DEBUG, INFO, WARNING, ERROR + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + handlers: + - type: "file" + filename: "logs/roboflow_detector.log" + - type: "console" + +# Performance settings +performance: + batch_size: 1 # Process one image at a time due to API limits + max_retries: 3 + retry_delay: 2.0 # seconds \ No newline at end of file diff --git a/id_card_processor_main.py b/id_card_processor_main.py deleted file mode 100644 index 5dabff9..0000000 --- a/id_card_processor_main.py +++ /dev/null @@ -1,234 +0,0 @@ -""" -Main script for ID Card Processing with YOLO Detection -""" -import argparse -import sys -from pathlib import Path -from typing import Dict, Any -import logging - -# Add src to path for imports -sys.path.append(str(Path(__file__).parent / "src")) - -from src.model.yolo_detector import YOLODetector -from src.model.id_card_processor import IDCardProcessor -from src.utils import setup_logging - -def parse_arguments(): - """Parse command line arguments""" - parser = argparse.ArgumentParser(description="ID Card Processing with YOLO Detection") - - parser.add_argument( - "--input-dir", - type=str, - required=True, - help="Input directory containing ID card images" - ) - - parser.add_argument( - "--output-dir", - type=str, - default="data/processed_id_cards", - help="Output directory for processed images" - ) - - parser.add_argument( - "--model-path", - type=str, - help="Path to custom YOLO model (.pt file)" - ) - - parser.add_argument( - "--confidence", - type=float, - default=0.5, - help="Confidence threshold for YOLO detection" - ) - - parser.add_argument( - "--detect-only", - action="store_true", - help="Only detect and crop ID cards, skip preprocessing" - ) - - parser.add_argument( - "--preprocess-only", - action="store_true", - help="Skip detection, directly preprocess images" - ) - - parser.add_argument( - "--bg-removal", - type=str, - default="grabcut", - choices=["grabcut", "threshold", "contour", "none"], - help="Background removal method" - ) - - parser.add_argument( - "--target-size", - type=str, - default="800x600", - help="Target size for normalization (width x height)" - ) - - parser.add_argument( - "--save-annotated", - action="store_true", - help="Save annotated images with bounding boxes" - ) - - parser.add_argument( - "--log-level", - type=str, - default="INFO", - choices=["DEBUG", "INFO", "WARNING", "ERROR"], - help="Logging level" - ) - - return parser.parse_args() - -def parse_size(size_str: str) -> tuple: - """Parse size string like '800x600' to tuple (800, 600)""" - try: - width, height = map(int, size_str.split('x')) - return (width, height) - except ValueError: - print(f"Invalid size format: {size_str}. Expected format: widthxheight") - sys.exit(1) - -def main(): - """Main function""" - args = parse_arguments() - - # Setup logging - logging_config = {"level": args.log_level} - logger = setup_logging(logging_config.get("level", "INFO")) - logger.info("Starting ID Card Processing") - - # Parse paths - input_dir = Path(args.input_dir) - output_dir = Path(args.output_dir) - - # Check if input directory exists - if not input_dir.exists(): - logger.error(f"Input directory does not exist: {input_dir}") - sys.exit(1) - - # Create output directory - output_dir.mkdir(parents=True, exist_ok=True) - - # Parse target size - target_size = parse_size(args.target_size) - - # Initialize YOLO detector - logger.info("Initializing YOLO detector...") - yolo_detector = YOLODetector( - model_path=args.model_path, - confidence=args.confidence - ) - - # Initialize ID card processor - logger.info("Initializing ID card processor...") - id_processor = IDCardProcessor(yolo_detector) - - if args.detect_only: - # Only detect and crop ID cards - logger.info("Running YOLO detection only...") - results = yolo_detector.batch_process( - input_dir, - output_dir / "cropped", - save_annotated=args.save_annotated - ) - - print("\n" + "="*50) - print("YOLO DETECTION RESULTS") - print("="*50) - print(f"Total images: {results['total_images']}") - print(f"Processed images: {results['processed_images']}") - print(f"Total detections: {results['total_detections']}") - print(f"Total cropped: {results['total_cropped']}") - print(f"Output directory: {output_dir / 'cropped'}") - print("="*50) - - elif args.preprocess_only: - # Skip detection, directly preprocess - logger.info("Running preprocessing only...") - results = id_processor.batch_process_id_cards( - input_dir, - output_dir / "processed", - detect_first=False, - remove_bg=args.bg_removal != "none", - enhance=True, - normalize=True, - target_size=target_size - ) - - print("\n" + "="*50) - print("PREPROCESSING RESULTS") - print("="*50) - print(f"Total images: {results['total_images']}") - print(f"Processed images: {results['processed_images']}") - print(f"Output directory: {output_dir / 'processed'}") - print("="*50) - - else: - # Full pipeline: detect + preprocess - logger.info("Running full pipeline: detection + preprocessing...") - - # Step 1: Detect and crop ID cards - logger.info("Step 1: Detecting and cropping ID cards...") - detection_results = yolo_detector.batch_process( - input_dir, - output_dir / "cropped", - save_annotated=args.save_annotated - ) - - # Step 2: Preprocess cropped images - cropped_dir = output_dir / "cropped" - if cropped_dir.exists(): - logger.info("Step 2: Preprocessing cropped ID cards...") - preprocessing_results = id_processor.batch_process_id_cards( - cropped_dir, - output_dir / "processed", - detect_first=False, - remove_bg=args.bg_removal != "none", - enhance=True, - normalize=True, - target_size=target_size - ) - else: - logger.warning("No cropped images found, preprocessing original images") - preprocessing_results = id_processor.batch_process_id_cards( - input_dir, - output_dir / "processed", - detect_first=False, - remove_bg=args.bg_removal != "none", - enhance=True, - normalize=True, - target_size=target_size - ) - - # Print summary - print("\n" + "="*50) - print("FULL PIPELINE RESULTS") - print("="*50) - print("DETECTION PHASE:") - print(f" - Total images: {detection_results['total_images']}") - print(f" - Processed images: {detection_results['processed_images']}") - print(f" - Total detections: {detection_results['total_detections']}") - print(f" - Total cropped: {detection_results['total_cropped']}") - print("\nPREPROCESSING PHASE:") - print(f" - Total images: {preprocessing_results['total_images']}") - print(f" - Processed images: {preprocessing_results['processed_images']}") - print(f"\nOutput directories:") - print(f" - Cropped images: {output_dir / 'cropped'}") - print(f" - Processed images: {output_dir / 'processed'}") - if args.save_annotated: - print(f" - Annotated images: {output_dir / 'cropped'}") - print("="*50) - - logger.info("ID Card Processing completed successfully") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/logs/roboflow_detector.log b/logs/roboflow_detector.log new file mode 100644 index 0000000..d53fb77 --- /dev/null +++ b/logs/roboflow_detector.log @@ -0,0 +1,66 @@ +2025-08-05 19:29:51,215 - model.roboflow_id_detector - INFO - Initialized Roboflow ID detector with model: french-card-id-detect/3 +2025-08-05 19:29:51,215 - __main__ - INFO - Starting batch processing... +2025-08-05 19:29:51,215 - __main__ - INFO - Input directory: data\IDcards\Archive +2025-08-05 19:29:51,215 - __main__ - INFO - Output directory: output\roboflow_detections +2025-08-05 19:29:51,217 - model.roboflow_id_detector - INFO - Processing 15 images from data\IDcards\Archive and subdirectories +2025-08-05 19:29:51,217 - model.roboflow_id_detector - INFO - Processing 1/15: im10.png +2025-08-05 19:29:53,563 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im10.png +2025-08-05 19:29:53,563 - model.roboflow_id_detector - WARNING - No ID cards detected in im10.png +2025-08-05 19:29:54,567 - model.roboflow_id_detector - INFO - Processing 2/15: im11.png +2025-08-05 19:29:56,871 - model.roboflow_id_detector - INFO - Found 2 ID card detections in im11.png +2025-08-05 19:29:56,897 - model.roboflow_id_detector - INFO - Saved cropped image to output\roboflow_detections\im11_card_1.jpg +2025-08-05 19:29:56,906 - model.roboflow_id_detector - INFO - Saved cropped image to output\roboflow_detections\im11_card_2.jpg +2025-08-05 19:29:56,920 - model.roboflow_id_detector - INFO - Processed im11.png: 2 cards cropped +2025-08-05 19:29:57,928 - model.roboflow_id_detector - INFO - Processing 3/15: im12.png +2025-08-05 19:30:00,037 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im12.png +2025-08-05 19:30:00,037 - model.roboflow_id_detector - WARNING - No ID cards detected in im12.png +2025-08-05 19:30:01,039 - model.roboflow_id_detector - INFO - Processing 4/15: im13.png +2025-08-05 19:30:04,856 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im13.png +2025-08-05 19:30:04,856 - model.roboflow_id_detector - WARNING - No ID cards detected in im13.png +2025-08-05 19:30:05,860 - model.roboflow_id_detector - INFO - Processing 5/15: im14.png +2025-08-05 19:30:08,314 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im14.png +2025-08-05 19:30:08,314 - model.roboflow_id_detector - WARNING - No ID cards detected in im14.png +2025-08-05 19:30:09,327 - model.roboflow_id_detector - INFO - Processing 6/15: im15.png +2025-08-05 19:30:11,459 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im15.png +2025-08-05 19:30:11,460 - model.roboflow_id_detector - WARNING - No ID cards detected in im15.png +2025-08-05 19:30:12,468 - model.roboflow_id_detector - INFO - Processing 7/15: im1_.png +2025-08-05 19:30:19,295 - model.roboflow_id_detector - INFO - Found 1 ID card detections in im1_.png +2025-08-05 19:30:19,535 - model.roboflow_id_detector - INFO - Saved cropped image to output\roboflow_detections\im1__card_1.jpg +2025-08-05 19:30:19,847 - model.roboflow_id_detector - INFO - Processed im1_.png: 1 cards cropped +2025-08-05 19:30:20,874 - model.roboflow_id_detector - INFO - Processing 8/15: im2.png +2025-08-05 19:30:23,435 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im2.png +2025-08-05 19:30:23,436 - model.roboflow_id_detector - WARNING - No ID cards detected in im2.png +2025-08-05 19:30:24,439 - model.roboflow_id_detector - INFO - Processing 9/15: im3.png +2025-08-05 19:30:26,301 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im3.png +2025-08-05 19:30:26,301 - model.roboflow_id_detector - WARNING - No ID cards detected in im3.png +2025-08-05 19:30:27,302 - model.roboflow_id_detector - INFO - Processing 10/15: im4.png +2025-08-05 19:30:29,921 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im4.png +2025-08-05 19:30:29,921 - model.roboflow_id_detector - WARNING - No ID cards detected in im4.png +2025-08-05 19:30:30,931 - model.roboflow_id_detector - INFO - Processing 11/15: im5.png +2025-08-05 19:30:33,293 - model.roboflow_id_detector - INFO - Found 1 ID card detections in im5.png +2025-08-05 19:30:33,313 - model.roboflow_id_detector - INFO - Saved cropped image to output\roboflow_detections\im5_card_1.jpg +2025-08-05 19:30:33,335 - model.roboflow_id_detector - INFO - Processed im5.png: 1 cards cropped +2025-08-05 19:30:34,336 - model.roboflow_id_detector - INFO - Processing 12/15: im6.png +2025-08-05 19:30:37,885 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im6.png +2025-08-05 19:30:37,886 - model.roboflow_id_detector - WARNING - No ID cards detected in im6.png +2025-08-05 19:30:38,894 - model.roboflow_id_detector - INFO - Processing 13/15: im7.png +2025-08-05 19:30:40,956 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im7.png +2025-08-05 19:30:40,956 - model.roboflow_id_detector - WARNING - No ID cards detected in im7.png +2025-08-05 19:30:41,964 - model.roboflow_id_detector - INFO - Processing 14/15: im8.png +2025-08-05 19:30:45,927 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im8.png +2025-08-05 19:30:45,927 - model.roboflow_id_detector - WARNING - No ID cards detected in im8.png +2025-08-05 19:30:46,935 - model.roboflow_id_detector - INFO - Processing 15/15: im9.png +2025-08-05 19:30:49,393 - model.roboflow_id_detector - INFO - Found 0 ID card detections in im9.png +2025-08-05 19:30:49,393 - model.roboflow_id_detector - WARNING - No ID cards detected in im9.png +2025-08-05 19:30:49,393 - model.roboflow_id_detector - INFO - Batch processing completed: +2025-08-05 19:30:49,394 - model.roboflow_id_detector - INFO - - Total images: 15 +2025-08-05 19:30:49,394 - model.roboflow_id_detector - INFO - - Processed: 3 +2025-08-05 19:30:49,394 - model.roboflow_id_detector - INFO - - Total detections: 4 +2025-08-05 19:30:49,394 - model.roboflow_id_detector - INFO - - Total cropped: 4 +2025-08-05 19:30:49,395 - __main__ - INFO - Batch processing completed! +2025-08-05 19:30:49,395 - __main__ - INFO - - Total images: 15 +2025-08-05 19:30:49,395 - __main__ - INFO - - Processed: 3 +2025-08-05 19:30:49,395 - __main__ - INFO - - Total detections: 4 +2025-08-05 19:30:49,395 - __main__ - INFO - - Total cropped: 4 +2025-08-05 19:30:49,396 - __main__ - INFO - Processing summary saved to: output\roboflow_detections\processing_summary.txt +2025-08-05 19:30:49,398 - __main__ - INFO - Processing completed successfully! diff --git a/script/id_card_cropper.py b/script/id_card_cropper.py new file mode 100644 index 0000000..5dc78ef --- /dev/null +++ b/script/id_card_cropper.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Simple ID Card Cropper using Roboflow API +Input: folder containing images +Output: folder with cropped ID cards +""" +import sys +import yaml +from pathlib import Path +import logging +import argparse + +# Add src to path +sys.path.append(str(Path(__file__).parent / "src")) + +from model.roboflow_id_detector import RoboflowIDDetector + +def setup_logging(): + """Setup basic logging""" + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' + ) + +def crop_id_cards(input_folder: str, output_folder: str, api_key: str = "Pkz4puRA0Cy3xMOuNoNr"): + """ + Crop ID cards from all images in input folder + + Args: + input_folder: Path to input folder containing images + output_folder: Path to output folder for cropped ID cards + api_key: Roboflow API key + """ + logger = logging.getLogger(__name__) + + # Convert to Path objects + input_path = Path(input_folder) + output_path = Path(output_folder) + + # Check if input folder exists + if not input_path.exists(): + logger.error(f"Input folder not found: {input_folder}") + return False + + # Create output folder + output_path.mkdir(parents=True, exist_ok=True) + + # Initialize detector + detector = RoboflowIDDetector( + api_key=api_key, + model_id="french-card-id-detect", + version=3, + confidence=0.5 + ) + + # Get all image files + image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff'} + image_files = [] + + for file_path in input_path.rglob('*'): + if file_path.is_file() and file_path.suffix.lower() in image_extensions: + image_files.append(file_path) + + if not image_files: + logger.error(f"No images found in {input_folder}") + return False + + logger.info(f"Found {len(image_files)} images to process") + + # Process each image + total_cropped = 0 + + for i, image_path in enumerate(image_files, 1): + logger.info(f"Processing {i}/{len(image_files)}: {image_path.name}") + + # Detect ID cards + detections = detector.detect_id_cards(image_path) + + if not detections: + logger.warning(f"No ID cards detected in {image_path.name}") + continue + + # Crop each detected ID card + for j, detection in enumerate(detections): + bbox = detection['bbox'] + + # Create output filename + stem = image_path.stem + suffix = f"_card_{j+1}.jpg" + output_file = output_path / f"{stem}{suffix}" + + # Crop ID card + cropped = detector.crop_id_card(image_path, bbox, output_file) + + if cropped is not None: + total_cropped += 1 + logger.info(f" ✓ Cropped card {j+1} to {output_file.name}") + + # Add delay between requests + if i < len(image_files): + import time + time.sleep(1.0) + + logger.info(f"Processing completed! Total ID cards cropped: {total_cropped}") + return True + +def main(): + """Main function""" + parser = argparse.ArgumentParser(description='Crop ID cards from images using Roboflow API') + parser.add_argument('input_folder', help='Input folder containing images') + parser.add_argument('output_folder', help='Output folder for cropped ID cards') + parser.add_argument('--api-key', default="Pkz4puRA0Cy3xMOuNoNr", + help='Roboflow API key (default: demo key)') + + args = parser.parse_args() + + # Setup logging + setup_logging() + + # Process images + success = crop_id_cards(args.input_folder, args.output_folder, args.api_key) + + if success: + print(f"\n✓ Successfully processed images from '{args.input_folder}'") + print(f"✓ Cropped ID cards saved to '{args.output_folder}'") + else: + print(f"\n✗ Failed to process images") + return 1 + + return 0 + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/src/model/__init__.py b/src/model/__init__.py index ecb9162..bdef87a 100644 --- a/src/model/__init__.py +++ b/src/model/__init__.py @@ -1,8 +1,7 @@ """ -Model module for YOLO-based ID card detection and cropping +Model module for Roboflow-based ID card detection and cropping """ -from .yolo_detector import YOLODetector -from .id_card_processor import IDCardProcessor +from .roboflow_id_detector import RoboflowIDDetector -__all__ = ['YOLODetector', 'IDCardProcessor'] \ No newline at end of file +__all__ = ['RoboflowIDDetector'] \ No newline at end of file diff --git a/src/model/__pycache__/__init__.cpython-313.pyc b/src/model/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7e494eee2bd02668f0f74449f0002de015f1715 GIT binary patch literal 338 zcmXv~u}Z{15Z&EGE<~;oL9lV#6l!H7DuMwyMGJ%7!jjAG<$~*On9U()=O)a+jig?We8A)GjfS92oQ?0 zU>`$JhRNFFX?$gbRP^46nUZv3fZmv~nJDwHJI<{Xbd=H}2SJ5wWYJVc(_9N$fT`cdD-zsptlCCt`p+}~fh%1nVLRYln1b_m6Z+Y_5_~zZr+H4U6z8v~^Wxj%vqN@k zC0M4**<7+z+PlWoY?OL6_FQTSd1HWCD40_(72~|pRm1sSJ#V9<d5h`O03uiW1Fb4z;Q#;t literal 0 HcmV?d00001 diff --git a/src/model/__pycache__/__init__.cpython-39.pyc b/src/model/__pycache__/__init__.cpython-39.pyc index 231b3dd8b9512918438d45b615e8fac91c63120f..232e1913c3eeb167304b6da57570e38f81a51a99 100644 GIT binary patch delta 226 zcmaFBw2z55k(ZZ?0SGdFP0Uz2kyqA63dl)eh+;@#Okv7l%w>vVVq{2RPG^i_PGJdV z&}6N0;PTB+NzGBn%}*)KNmWS8FH#7~Ps&fr$uFN+pjR&h<9NEbq?V*6m*f`}F#*l= z(`32D9v`2QpBx{5OQZ;@BR(@FJ_T7BNKIl+PJH}Ih9WkgHZbwa*4Zj1v^ce>I3_Q> tv@$iuCAB!aB)=fW(88M6m$b zEGew%Oi`>UY{3kg>{T{gzWFJsISRS?DWy573TgR83X%Rk{u9&m>Uls6m(-HfzYLPCVnT~kMT&}J^3qEyQ)66Gi?d7e Y3u648gC+|ys&cUbRk5(}F!C?~03Xdr>Hq)$ diff --git a/src/model/__pycache__/roboflow_id_detector.cpython-313.pyc b/src/model/__pycache__/roboflow_id_detector.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78cb388affa8f7fd19b4ab140d633bb548b4c351 GIT binary patch literal 14131 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 literal 0 HcmV?d00001 diff --git a/src/model/__pycache__/roboflow_id_detector.cpython-39.pyc b/src/model/__pycache__/roboflow_id_detector.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3138a0d968bb717d25b31c9883c1ffbad87a4a8f GIT binary patch literal 9059 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 literal 0 HcmV?d00001 diff --git a/src/model/__pycache__/yolo_detector.cpython-39.pyc b/src/model/__pycache__/yolo_detector.cpython-39.pyc index a67f54a06b6400e5a637a096bd6ebf4e3efd2097..300f3a968306aa66582af7ff3e98276e055c5787 100644 GIT binary patch delta 135 zcmX?Le%72bk(ZZ?0SH!XV`3qD5Ws9^xgg%It&?gche3702YImsY06xTF?mm*f}3_&Wz}Ue9t< zNWLfE^{Kh4N^Yjd$=0V89@ str: + """ + Encode image to base64 + + Args: + image_path: Path to image file + + Returns: + Base64 encoded image string + """ + try: + with open(image_path, "rb") as image_file: + encoded_string = base64.b64encode(image_file.read()).decode('utf-8') + return encoded_string + except Exception as e: + self.logger.error(f"Error encoding image {image_path}: {e}") + return None + + def _make_api_request(self, image_data: str, image_name: str = "image.jpg") -> Optional[Dict]: + """ + Make API request to Roboflow + + Args: + image_data: Base64 encoded image data + image_name: Name of the image file + + Returns: + API response as dictionary + """ + try: + headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + } + + params = { + 'api_key': self.api_key, + 'name': image_name + } + + response = requests.post( + self.api_url, + params=params, + data=image_data, + headers=headers, + timeout=30 + ) + + if response.status_code == 200: + return response.json() + else: + self.logger.error(f"API request failed with status {response.status_code}: {response.text}") + return None + + except Exception as e: + self.logger.error(f"Error making API request: {e}") + return None def detect_id_cards(self, image_path: Path) -> List[Dict[str, Any]]: """ - Detect ID cards in an image + Detect ID cards in an image using Roboflow API Args: image_path: Path to image file @@ -49,39 +107,50 @@ class YOLODetector: List of detection results with bounding boxes """ try: - # Load image - image = cv2.imread(str(image_path)) - if image is None: - self.logger.error(f"Could not load image: {image_path}") + # Encode image + image_data = self._encode_image(image_path) + if not image_data: return [] - # Run detection - results = self.model(image, conf=self.confidence) + # Make API request + response = self._make_api_request(image_data, image_path.name) + if not response: + return [] detections = [] - for result in results: - boxes = result.boxes - if boxes is not None: - for box in boxes: - # Get coordinates - x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() - confidence = float(box.conf[0]) - class_id = int(box.cls[0]) - class_name = self.model.names[class_id] - - detection = { - 'bbox': [int(x1), int(y1), int(x2), int(y2)], - 'confidence': confidence, - 'class_id': class_id, - 'class_name': class_name, - 'area': (x2 - x1) * (y2 - y1) - } - detections.append(detection) - # Sort by confidence and area (prefer larger, more confident detections) + # Parse predictions from response + if 'predictions' in response: + for prediction in response['predictions']: + # Check confidence threshold + if prediction.get('confidence', 0) < self.confidence: + continue + + # Extract bounding box coordinates + x = prediction.get('x', 0) + y = prediction.get('y', 0) + width = prediction.get('width', 0) + height = prediction.get('height', 0) + + # Convert to [x1, y1, x2, y2] format + x1 = int(x - width / 2) + y1 = int(y - height / 2) + x2 = int(x + width / 2) + y2 = int(y + height / 2) + + detection = { + 'bbox': [x1, y1, x2, y2], + 'confidence': prediction.get('confidence', 0), + 'class_id': prediction.get('class_id', 0), + 'class_name': prediction.get('class', 'id_card'), + 'area': width * height + } + detections.append(detection) + + # Sort by confidence and area detections.sort(key=lambda x: (x['confidence'], x['area']), reverse=True) - self.logger.info(f"Found {len(detections)} detections in {image_path.name}") + self.logger.info(f"Found {len(detections)} ID card detections in {image_path.name}") return detections except Exception as e: @@ -201,7 +270,7 @@ class YOLODetector: return result def batch_process(self, input_dir: Path, output_dir: Path, - save_annotated: bool = False) -> Dict[str, Any]: + save_annotated: bool = False, delay: float = 1.0) -> Dict[str, Any]: """ Process all images in a directory and subdirectories @@ -209,6 +278,7 @@ class YOLODetector: input_dir: Input directory containing images output_dir: Output directory for cropped images save_annotated: Whether to save annotated images + delay: Delay between API requests (seconds) Returns: Batch processing results @@ -216,11 +286,10 @@ class YOLODetector: # Create output directory output_dir.mkdir(parents=True, exist_ok=True) - # Get all image files recursively from input directory and subdirectories + # Get all image files recursively image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff'} image_files = [] - # Recursively find all image files for file_path in input_dir.rglob('*'): if file_path.is_file() and file_path.suffix.lower() in image_extensions: image_files.append(file_path) @@ -255,6 +324,10 @@ class YOLODetector: results['processed_images'] += 1 results['total_detections'] += len(result['detections']) results['total_cropped'] += len(result['cropped_paths']) + + # Add delay between requests to avoid rate limiting + if i < len(image_files) - 1: # Don't delay after the last image + time.sleep(delay) # Summary self.logger.info(f"Batch processing completed:")