Spaces:
Running
Running
| import cv2 | |
| import numpy as np | |
| import os | |
| import sys | |
| import json | |
| from collections import Counter | |
| from inference_sdk import InferenceHTTPClient | |
| # ───────────────────────────────────────────── | |
| # CONFIGURATION | |
| # ───────────────────────────────────────────── | |
| ROBOFLOW_API_KEY = "bVPbU8TisRCASiURr0lb" | |
| MODEL_ID = "printed-circuit-board/3" | |
| CONFIDENCE_THRESH = 0.3 | |
| OVERLAP_THRESH = 0.3 | |
| LABEL_COLORS = { | |
| "resistor": (0, 255, 0), | |
| "capacitor": (255, 0, 0), | |
| "inductor": (0, 0, 255), | |
| "diode": (255, 255, 0), | |
| "led": (0, 255, 255), | |
| "ic": (255, 0, 255), | |
| "transistor": (128, 255, 0), | |
| "connector": (0, 128, 255), | |
| "jumper": (255, 128, 0), | |
| "emi_filter": (128, 0, 255), | |
| "button": (0, 255, 128), | |
| "clock": (255, 0, 128), | |
| "transformer": (128, 128, 0), | |
| "potentiometer": (0, 128, 128), | |
| "heatsink": (128, 0, 128), | |
| "fuse": (200, 200, 0), | |
| "ferrite_bead": (0, 200, 200), | |
| "buzzer": (200, 0, 200), | |
| "display": (100, 200, 255), | |
| "battery": (255, 200, 100), | |
| } | |
| DEFAULT_COLOR = (255, 255, 255) | |
| def get_client(): | |
| return InferenceHTTPClient( | |
| api_url="https://serverless.roboflow.com", | |
| api_key=ROBOFLOW_API_KEY | |
| ) | |
| def run_detection(image_path): | |
| print(f"[->] Sending image to Roboflow API...") | |
| client = get_client() | |
| result = client.infer(image_path, model_id=MODEL_ID) | |
| detections = [] | |
| predictions = result.get("predictions", []) | |
| img_w = result.get("image", {}).get("width", 1) | |
| img_h = result.get("image", {}).get("height", 1) | |
| for pred in predictions: | |
| confidence = pred.get("confidence", 0) | |
| if confidence < CONFIDENCE_THRESH: | |
| continue | |
| label = pred.get("class", "unknown").lower() | |
| cx = pred.get("x", 0) | |
| cy = pred.get("y", 0) | |
| w = pred.get("width", 0) | |
| h = pred.get("height", 0) | |
| x1 = max(0, int(cx - w / 2)) | |
| y1 = max(0, int(cy - h / 2)) | |
| x2 = min(img_w, int(cx + w / 2)) | |
| y2 = min(img_h, int(cy + h / 2)) | |
| detections.append({ | |
| 'label': label, | |
| 'confidence': round(confidence, 3), | |
| 'bbox': (x1, y1, x2, y2) | |
| }) | |
| print(f"[OK] Roboflow returned {len(predictions)} predictions, " | |
| f"{len(detections)} above {CONFIDENCE_THRESH:.0%} confidence") | |
| return detections | |
| def detect_traces(img): | |
| hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |
| lower_copper = np.array([10, 50, 50]) | |
| upper_copper = np.array([30, 255, 255]) | |
| copper_mask = cv2.inRange(hsv, lower_copper, upper_copper) | |
| lower_silver = np.array([0, 0, 180]) | |
| upper_silver = np.array([180, 30, 255]) | |
| silver_mask = cv2.inRange(hsv, lower_silver, upper_silver) | |
| trace_mask = cv2.bitwise_or(copper_mask, silver_mask) | |
| kernel = np.ones((2, 2), np.uint8) | |
| trace_mask = cv2.morphologyEx(trace_mask, cv2.MORPH_OPEN, kernel, iterations=1) | |
| trace_mask = cv2.morphologyEx(trace_mask, cv2.MORPH_CLOSE, kernel, iterations=1) | |
| return trace_mask | |
| def draw_detections(img, detections, trace_mask=None): | |
| output = img.copy() | |
| if trace_mask is not None: | |
| trace_overlay = np.zeros_like(output) | |
| trace_overlay[trace_mask > 0] = (255, 100, 0) | |
| output = cv2.addWeighted(output, 1.0, trace_overlay, 0.3, 0) | |
| for det in detections: | |
| x1, y1, x2, y2 = det['bbox'] | |
| label = det['label'] | |
| conf = det['confidence'] | |
| color = LABEL_COLORS.get(label, DEFAULT_COLOR) | |
| cv2.rectangle(output, (x1, y1), (x2, y2), color, 2) | |
| text = f"{label} {conf:.0%}" | |
| font = cv2.FONT_HERSHEY_SIMPLEX | |
| font_scale = 0.45 | |
| thickness = 1 | |
| (tw, th), _ = cv2.getTextSize(text, font, font_scale, thickness) | |
| cv2.rectangle(output, | |
| (x1, max(0, y1 - th - 8)), | |
| (x1 + tw + 6, y1), | |
| color, -1) | |
| cv2.putText(output, text, | |
| (x1 + 3, y1 - 4), | |
| font, font_scale, (0, 0, 0), thickness, cv2.LINE_AA) | |
| return output | |
| def print_summary(detections): | |
| counts = Counter(d['label'] for d in detections) | |
| print("\n-- Detection Summary ---------------------") | |
| for label, count in sorted(counts.items(), key=lambda x: -x[1]): | |
| avg_conf = np.mean([d['confidence'] for d in detections | |
| if d['label'] == label]) | |
| print(f" {label:<20} x{count} avg conf: {avg_conf:.0%}") | |
| print(f" {'TOTAL':<20} x{len(detections)}") | |
| print("------------------------------------------\n") | |
| def save_json(detections, output_path): | |
| data = { | |
| "total_components": len(detections), | |
| "components": [ | |
| {**d, "bbox": list(d["bbox"])} | |
| for d in detections | |
| ] | |
| } | |
| with open(output_path, "w") as f: | |
| json.dump(data, f, indent=2) | |
| print(f"[OK] Results saved as JSON: {output_path}") | |
| def detect_components(image_path, save_output=True, show_traces=True): | |
| print(f"\n{'='*50}") | |
| print(f" PCB Component Detector") | |
| print(f" Image: {image_path}") | |
| print(f"{'='*50}\n") | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| print(f"[X] Could not load image: {image_path}") | |
| return [] | |
| print(f"[OK] Image loaded: {img.shape[1]}x{img.shape[0]} px") | |
| detections = run_detection(image_path) | |
| if not detections: | |
| print("[!] No components detected. Try a clearer PCB image.") | |
| return [] | |
| trace_mask = None | |
| if show_traces: | |
| trace_mask = detect_traces(img) | |
| trace_px = np.count_nonzero(trace_mask) | |
| print(f"[OK] Traces detected: {trace_px} pixels") | |
| print_summary(detections) | |
| if save_output: | |
| base, ext = os.path.splitext(image_path) | |
| annotated = draw_detections(img, detections, trace_mask) | |
| img_out = base + "_detected" + ext | |
| cv2.imwrite(img_out, annotated) | |
| print(f"[OK] Annotated image saved: {img_out}") | |
| json_out = base + "_results.json" | |
| save_json(detections, json_out) | |
| return detections | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 2: | |
| test_image = "sample 5.jpg" | |
| print(f"No image specified. Using default: {test_image}") | |
| else: | |
| test_image = sys.argv[1] | |
| detections = detect_components(test_image) |