armeta_hackaton / qr /qr_extraction.py
bekzhanK1's picture
Clean deployment for HF Spaces - code only
0256284
"""Extract QR codes from images and save labeled images and JSON data."""
# ----------------------------------------------
# --- Author : Ahmet Ozlu
# --- Mail : ahmetozlu93@gmail.com
# --- Date : 17th September 2018
# --- Modified : QR code extraction only
# ----------------------------------------------
import cv2
import numpy as np
import json
import os
from pathlib import Path
def detect_qr_codes(img_original):
"""
Detect QR codes in an image using multiple preprocessing approaches.
Parameters:
-----------
img_original : numpy.ndarray
Original BGR image
Returns:
--------
list
List of QR code dictionaries with 'x', 'y', 'width', 'height', 'data', 'points'
"""
qr_detector = cv2.QRCodeDetector()
qr_codes = []
seen_qr_boxes = set()
def add_qr_code(qr_points, info, seen_set):
"""Helper function to add QR code if not already detected"""
if qr_points is None or len(qr_points) == 0:
return False
qr_points = qr_points.astype(int)
x_coords = qr_points[:, 0]
y_coords = qr_points[:, 1]
x_min, x_max = int(x_coords.min()), int(x_coords.max())
y_min, y_max = int(y_coords.min()), int(y_coords.max())
# Check if we've already detected this QR code (within 10 pixels tolerance)
box_key = (x_min // 10, y_min // 10, x_max // 10, y_max // 10)
if box_key in seen_set:
return False
seen_set.add(box_key)
qr_codes.append({
'x': x_min,
'y': y_min,
'width': x_max - x_min,
'height': y_max - y_min,
'data': info if info else '',
'points': qr_points.tolist()
})
return True
# Try multiple preprocessing approaches for better QR code detection
test_images = [("original", img_original)]
gray = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)
test_images.append(("grayscale", gray))
# Apply CLAHE (Contrast Limited Adaptive Histogram Equalization)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
gray_clahe = clahe.apply(gray)
test_images.append(("clahe", gray_clahe))
# Add thresholded versions
_, thresh1 = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
_, thresh2 = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
test_images.append(("binary", thresh1))
test_images.append(("otsu", thresh2))
# Add inverted versions (QR codes might be white on black)
test_images.append(("inverted", cv2.bitwise_not(gray)))
test_images.append(("inverted_clahe", cv2.bitwise_not(gray_clahe)))
# Try detection on each preprocessed image
for img_name, test_img in test_images:
if len(qr_codes) > 0:
print(f" QR code detected using: {img_name}")
break # Stop if we found QR codes
# Ensure image is in correct format (3-channel for color, 1-channel for grayscale)
if len(test_img.shape) == 2:
# Grayscale - convert to 3-channel for detection
test_img_3ch = cv2.cvtColor(test_img, cv2.COLOR_GRAY2BGR)
else:
test_img_3ch = test_img
# Try detectAndDecodeMulti first (for multiple QR codes)
try:
retval, decoded_info, points, straight_qrcode = qr_detector.detectAndDecodeMulti(
test_img_3ch)
if retval and points is not None:
# Handle both single and multiple QR codes
if isinstance(decoded_info, str):
decoded_info = [decoded_info]
points = [points]
for info, qr_points in zip(decoded_info, points):
if add_qr_code(qr_points, info, seen_qr_boxes):
print(f" QR code detected using: {img_name} (multi)")
except Exception as e:
pass
# Try single QR code detection as fallback
if len(qr_codes) == 0:
try:
retval, decoded_info, points, straight_qrcode = qr_detector.detectAndDecode(
test_img_3ch)
if retval and points is not None and len(points) > 0:
if add_qr_code(points, decoded_info, seen_qr_boxes):
print(f" QR code detected using: {img_name} (single)")
except Exception as e:
pass
return qr_codes
def process_image_no_save(input_path):
"""
Process a single image and detect QR codes without saving images or JSON files.
Parameters:
-----------
input_path : str
Path to input image
Returns:
--------
dict
Dictionary with detection results (no files saved)
"""
# Read the input image
img_original = cv2.imread(input_path)
if img_original is None:
print(f"Error: Could not read image {input_path}")
return None
# Detect QR codes
qr_codes = detect_qr_codes(img_original)
# Prepare QR codes for JSON
qr_codes_json = []
for i, qr in enumerate(qr_codes):
qr_json = {
"id": i + 1,
"x": qr['x'],
"y": qr['y'],
"width": qr['width'],
"height": qr['height'],
"data": qr['data']
}
# Optionally include corner points if needed
if 'points' in qr and len(qr['points']) > 0:
qr_json['corner_points'] = qr['points']
qr_codes_json.append(qr_json)
# Create output JSON structure
output_json = {
"image": Path(input_path).name,
"image_dimensions": {
"width": img_original.shape[1],
"height": img_original.shape[0]
},
"qr_codes": {
"count": len(qr_codes_json),
"items": qr_codes_json
}
}
return output_json
def process_image(input_path, output_folder='labelled', json_folder='outputs'):
"""
Process a single image and detect QR codes.
Parameters:
-----------
input_path : str
Path to input image
output_folder : str
Folder to save labeled images
json_folder : str
Folder to save JSON files
Returns:
--------
dict
Dictionary with detection results
"""
# Get filename without extension
filename = Path(input_path).stem
file_ext = Path(input_path).suffix
print(f"\n{'='*60}")
print(f"Processing: {Path(input_path).name}")
print(f"{'='*60}")
# Read the input image
img_original = cv2.imread(input_path)
if img_original is None:
print(f"Error: Could not read image {input_path}")
return None
# Detect QR codes
qr_codes = detect_qr_codes(img_original)
print(f"Found {len(qr_codes)} QR code(s)")
# Create labeled image
labeled_img = img_original.copy()
# Draw QR codes in blue color
for i, qr in enumerate(qr_codes):
# Draw bounding box
cv2.rectangle(labeled_img, (qr['x'], qr['y']),
(qr['x'] + qr['width'], qr['y'] + qr['height']),
(255, 0, 0), 2) # Blue color (BGR format)
# Draw QR code points/polygon
if len(qr['points']) >= 4:
pts = np.array(qr['points'], np.int32)
cv2.polylines(labeled_img, [pts], True, (255, 0, 0), 2)
# Add label
cv2.putText(labeled_img, f"QR {i+1}", (qr['x'], qr['y'] - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
# Add QR data text (if not too long)
if qr['data'] and len(qr['data']) < 50:
cv2.putText(labeled_img, qr['data'][:30],
(qr['x'], qr['y'] + qr['height'] + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
# Create output folders
os.makedirs(output_folder, exist_ok=True)
os.makedirs(json_folder, exist_ok=True)
# Save labeled image
output_image_path = os.path.join(
output_folder, f'qr_labelled_{filename}{file_ext}')
cv2.imwrite(output_image_path, labeled_img)
# Prepare QR codes for JSON
qr_codes_json = []
for i, qr in enumerate(qr_codes):
qr_json = {
"id": i + 1,
"x": qr['x'],
"y": qr['y'],
"width": qr['width'],
"height": qr['height'],
"data": qr['data']
}
# Optionally include corner points if needed
if 'points' in qr and len(qr['points']) > 0:
qr_json['corner_points'] = qr['points']
qr_codes_json.append(qr_json)
# Create output JSON
output_json = {
"image": Path(input_path).name,
"image_dimensions": {
"width": img_original.shape[1],
"height": img_original.shape[0]
},
"qr_codes": {
"count": len(qr_codes_json),
"items": qr_codes_json
}
}
# Save JSON
output_json_path = os.path.join(
json_folder, f'qr_detection_{filename}.json')
with open(output_json_path, 'w') as f:
json.dump(output_json, f, indent=2)
# Print summary
print(f"βœ“ Found {len(qr_codes_json)} QR code(s)")
print(f"βœ“ Labeled image saved: {output_image_path}")
print(f"βœ“ Detection data saved: {output_json_path}")
return output_json
def process_folder(input_folder='inputs', output_folder='labelled', json_folder='outputs'):
"""
Process all images in the input folder.
Parameters:
-----------
input_folder : str
Folder containing input images
output_folder : str
Folder to save labeled images
json_folder : str
Folder to save JSON files
"""
# Create output folders
os.makedirs(output_folder, exist_ok=True)
os.makedirs(json_folder, exist_ok=True)
# Supported image formats
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif']
# Get all image files
input_path = Path(input_folder)
if not input_path.exists():
print(f"Error: Input folder '{input_folder}' does not exist!")
return
image_files = [f for f in input_path.iterdir()
if f.is_file() and f.suffix.lower() in image_extensions]
if not image_files:
print(f"No image files found in '{input_folder}'")
return
print(f"\n{'='*60}")
print(f"Found {len(image_files)} image(s) to process")
print(f"{'='*60}\n")
# Process each image
all_results = []
for i, image_file in enumerate(image_files, 1):
print(f"\n[{i}/{len(image_files)}] Processing: {image_file.name}")
try:
result = process_image(
str(image_file),
output_folder=output_folder,
json_folder=json_folder
)
if result:
all_results.append(result)
except Exception as e:
print(f"βœ— Error processing {image_file.name}: {str(e)}")
continue
# Save summary JSON with all results
if all_results:
summary_path = os.path.join(json_folder, 'qr_detection_summary.json')
summary = {
"total_images": len(all_results),
"total_qr_codes": sum(r['qr_codes']['count'] for r in all_results),
"images": all_results
}
with open(summary_path, 'w') as f:
json.dump(summary, f, indent=2)
print(f"\n{'='*60}")
print(f"PROCESSING COMPLETE")
print(f"{'='*60}")
print(f"βœ“ Processed {len(all_results)} image(s)")
print(f"βœ“ Total QR codes detected: {summary['total_qr_codes']}")
print(f"βœ“ Summary saved: {summary_path}")
print(f"βœ“ Labeled images saved in: {output_folder}/")
print(f"βœ“ JSON files saved in: {json_folder}/")
if __name__ == "__main__":
# Process all images in the 'inputs' folder
process_folder(
input_folder='inputs',
output_folder='labelled',
json_folder='outputs'
)