diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..0a9aa63568dc3e4512780691636f6e63874ea9ff 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,30 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +examples/Fold_1.png filter=lfs diff=lfs merge=lfs -text +examples/Fold_2.png filter=lfs diff=lfs merge=lfs -text +examples/Fold_3.png filter=lfs diff=lfs merge=lfs -text +examples/Holes_1.png filter=lfs diff=lfs merge=lfs -text +examples/Holes_2.png filter=lfs diff=lfs merge=lfs -text +examples/Holes_3.png filter=lfs diff=lfs merge=lfs -text +examples/Holes_4.png filter=lfs diff=lfs merge=lfs -text +examples/Spots_1.png filter=lfs diff=lfs merge=lfs -text +examples/Spots_2.png filter=lfs diff=lfs merge=lfs -text +examples/Spots_3.png filter=lfs diff=lfs merge=lfs -text +examples/Spots_4.png filter=lfs diff=lfs merge=lfs -text +examples/Spots_5.png filter=lfs diff=lfs merge=lfs -text +examples/vinyl_cuff_1.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_cuff_2.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_cuff_3.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_cuff_4.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_cuff_5.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_foreign_object_1.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_foreign_object_2.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_foreign_object_3.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_foreign_object_4.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_foreign_object_5.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_hole_1.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_hole_2.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_hole_3.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_hole_4.jpg filter=lfs diff=lfs merge=lfs -text +examples/vinyl_hole_5.jpg filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index 82acebfd60c03abf40fec8b7667372e3a58825a1..2d89233bdc2f20c8a9f0f73224b30a1adf16e278 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,48 @@ --- title: Glove Defect Detection -emoji: ๐Ÿฆ€ -colorFrom: indigo -colorTo: yellow +emoji: ๐Ÿงค +colorFrom: blue +colorTo: indigo sdk: gradio -sdk_version: 6.12.0 +sdk_version: 4.44.0 app_file: app.py pinned: false license: mit --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +# ๐Ÿงค Glove Defect Detection System + +An image processing app that automatically **classifies safety glove types** and **detects surface defects** โ€” no deep learning required, purely classical computer vision (HSV segmentation + morphological operations). + +## How it works + +1. **Upload** a photo of a glove (rubber, cloth, nitrile, or vinyl). +2. The app **classifies the glove type** by analysing dominant HSV colour features. +3. It then runs the **type-specific defect detector** and returns an annotated image. + +## Supported glove types & defects + +| Glove Type | Detected Defects | +|---------------|---------------------------------------------| +| Rubber | Holes ยท Discoloration ยท Folds | +| Cloth | Missing Fingers ยท Holes ยท Stains | +| Nitrile | Spots ยท Holes ยท Tears | +| Vinyl | Palm Holes ยท Cuff Tears ยท Foreign Objects | + +## Technical stack + +- **OpenCV** โ€” image preprocessing, morphological operations, contour drawing +- **scikit-image** โ€” connected-component region properties (area, eccentricity, etc.) +- **Gradio** โ€” web interface + +## Project + +IPPR (Image Processing & Pattern Recognition) Assignment +Asia Pacific University of Technology & Innovation (APU) + +| Contributor | Module | +|------------------------------------------|--------------------------------------------| +| Chiew Wen Qian (TP084377) | Rubber detector ยท Code & GUI integration | +| Farouk Elouzzani (TP075438) | Glove type classifier ยท Vinyl detector | +| Puteri Hannah binti Mohamed Daud Ibrahim (TP077710) | Nitrile detector | +| Wendy Koh Xin Hui (TP077721) | Cloth detector | diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..3566798f78454f8ac3158ad4ae4aecbd2ddbecbb --- /dev/null +++ b/app.py @@ -0,0 +1,217 @@ +""" +Glove Defect Detection โ€” Gradio app entry point. + +Upload a glove image โ†’ automatic type classification โ†’ defect analysis. +""" + +import numpy as np +import gradio as gr + +from detectors.glove_type_detector import detect_glove_type +from detectors.rubber_detection import detect_rubber +from detectors.cloth_detection import detect_cloth +from detectors.nitrile_detection import detect_nitrile +from detectors.vinyl_detection import detect_vinyl + +DETECTORS = { + "Rubber Glove": detect_rubber, + "Cloth Glove": detect_cloth, + "Nitrile Glove": detect_nitrile, + "Vinyl Glove": detect_vinyl, +} + + +def analyse(image: np.ndarray): + if image is None: + return None, "โ€”", "โ€”" + img = image.astype(np.uint8) + _, glove_type = detect_glove_type(img) + detector = DETECTORS.get(glove_type, detect_rubber) + result_img, raw_status = detector(img) + label = "โœ… Passed" if raw_status == "Passed" else "โš ๏ธ Defective" + return result_img, glove_type, label + + +def load_from_gallery(evt: gr.SelectData): + """Pass the clicked gallery image into the input component.""" + val = evt.value + # Gradio 4+ returns {"image": {"path": ...}, "caption": ...} for (path, caption) tuples + if isinstance(val, dict): + img = val.get("image", val) + if isinstance(img, dict): + return img.get("path") or img.get("url") + return img + return val + + +# โ”€โ”€ Sample image lists โ€” (file_path, caption) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Corrected groupings per dataset naming convention: +# DC_* โ†’ Rubber + Discoloration +# Fold_* โ†’ Rubber + Fold +# Original_* โ†’ Rubber + Normal +# PH_* โ†’ Rubber + Hole (Palm) +# H(*) โ†’ Cloth + Hole +# MF(*) โ†’ Cloth + Missing Finger +# S(*) โ†’ Cloth + Stain +# Holes_* โ†’ Nitrile + Hole +# Normal_* โ†’ Nitrile + Normal +# Spots_* โ†’ Nitrile + Spot +# Tear_* โ†’ Nitrile + Tear +# vinyl_hole_* โ†’ Vinyl + Palm Hole +# vinyl_cuff_* โ†’ Vinyl + Cuff Tear +# vinyl_foreign_* โ†’ Vinyl + Foreign Object + +RUBBER = [ + ("examples/Original_1.jpeg", "Normal"), + ("examples/Original_2.jpeg", "Normal"), + ("examples/Original_3.jpeg", "Normal"), + ("examples/PH_1.jpeg", "Hole"), + ("examples/PH_2.jpeg", "Hole"), + ("examples/PH_3.jpeg", "Hole"), + ("examples/DC_1.jpeg", "Discoloration"), + ("examples/DC_2.jpeg", "Discoloration"), + ("examples/Fold_1.png", "Fold"), + ("examples/Fold_2.png", "Fold"), + ("examples/Fold_3.png", "Fold"), +] + +CLOTH = [ + ("examples/H(2).jpeg", "Hole"), + ("examples/H(3).jpeg", "Hole"), + ("examples/H(5).jpeg", "Hole"), + ("examples/H(6).jpeg", "Hole"), + ("examples/H(7).jpeg", "Hole"), + ("examples/MF(2).jpeg", "Missing Finger"), + ("examples/MF(3).jpeg", "Missing Finger"), + ("examples/MF(4).jpeg", "Missing Finger"), + ("examples/MF(5).jpeg", "Missing Finger"), + ("examples/MF(6).jpeg", "Missing Finger"), + ("examples/MF(7).jpeg", "Missing Finger"), + ("examples/S(1).jpeg", "Stain"), + ("examples/S(7).jpeg", "Stain"), + ("examples/S(8).jpeg", "Stain"), + ("examples/S(9).jpeg", "Stain"), + ("examples/S(10).jpeg", "Stain"), +] + +NITRILE = [ + ("examples/Normal_1.jpg", "Normal"), + ("examples/Normal_2.jpg", "Normal"), + ("examples/Holes_1.png", "Hole"), + ("examples/Holes_2.png", "Hole"), + ("examples/Holes_3.png", "Hole"), + ("examples/Holes_4.png", "Hole"), + ("examples/Spots_1.png", "Spot"), + ("examples/Spots_2.png", "Spot"), + ("examples/Spots_3.png", "Spot"), + ("examples/Spots_4.png", "Spot"), + ("examples/Spots_5.png", "Spot"), + ("examples/Tear_1.jpg", "Tear"), + ("examples/Tear_2.jpg", "Tear"), + ("examples/Tear_3.jpg", "Tear"), +] + +VINYL = [ + ("examples/vinyl_hole_1.jpg", "Palm Hole"), + ("examples/vinyl_hole_2.jpg", "Palm Hole"), + ("examples/vinyl_hole_3.jpg", "Palm Hole"), + ("examples/vinyl_hole_4.jpg", "Palm Hole"), + ("examples/vinyl_hole_5.jpg", "Palm Hole"), + ("examples/vinyl_cuff_1.jpg", "Cuff Tear"), + ("examples/vinyl_cuff_2.jpg", "Cuff Tear"), + ("examples/vinyl_cuff_3.jpg", "Cuff Tear"), + ("examples/vinyl_cuff_4.jpg", "Cuff Tear"), + ("examples/vinyl_cuff_5.jpg", "Cuff Tear"), + ("examples/vinyl_foreign_object_1.jpg", "Foreign Object"), + ("examples/vinyl_foreign_object_2.jpg", "Foreign Object"), + ("examples/vinyl_foreign_object_3.jpg", "Foreign Object"), + ("examples/vinyl_foreign_object_4.jpg", "Foreign Object"), + ("examples/vinyl_foreign_object_5.jpg", "Foreign Object"), +] + + +# โ”€โ”€ UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +with gr.Blocks( + title="Glove Defect Inspector", + theme=gr.themes.Soft(primary_hue="blue"), +) as demo: + + gr.Markdown( + """ + # ๐Ÿงค Glove Defect Detection System + Upload a photo of a safety glove OR pick one from the sample gallery below. + The system will automatically identify the **glove type** and scan for **defects**. + + | Glove Type | Detectable Defects | + |------------|-----------------------------------------------| + | Rubber | Holes ยท Discoloration ยท Folds | + | Cloth | Missing Fingers ยท Holes ยท Stains | + | Nitrile | Spots ยท Holes ยท Tears | + | Vinyl | Palm Holes ยท Cuff Tears ยท Foreign Objects | + """ + ) + + with gr.Row(): + with gr.Column(scale=1): + inp = gr.Image( + type="numpy", + label="Input Image", + sources=["upload", "webcam"], + height=340, + ) + + with gr.Column(scale=1): + out_img = gr.Image( + type="numpy", + label="Annotated Result", + height=340, + ) + + with gr.Row(): + out_type = gr.Textbox(label="Detected Glove Type", interactive=False) + out_status = gr.Textbox(label="Defect Status", interactive=False) + + inp.change(fn=analyse, inputs=inp, outputs=[out_img, out_type, out_status]) + + # โ”€โ”€ Sample gallery โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + gr.Markdown("### ๐Ÿ–ผ๏ธ Sample Images | Click any thumbnail to load & analyse it") + + with gr.Accordion("๐ŸŸข Rubber Glove (11 samples)", open=False): + rubber_gallery = gr.Gallery( + value=RUBBER, columns=6, height=200, + show_label=False, allow_preview=False, object_fit="cover", + ) + + with gr.Accordion("โšช Cloth Glove (16 samples)", open=False): + cloth_gallery = gr.Gallery( + value=CLOTH, columns=6, height=200, + show_label=False, allow_preview=False, object_fit="cover", + ) + + with gr.Accordion("๐ŸŸฃ Nitrile Glove (14 samples)", open=False): + nitrile_gallery = gr.Gallery( + value=NITRILE, columns=6, height=200, + show_label=False, allow_preview=False, object_fit="cover", + ) + + with gr.Accordion("โšซ Vinyl Glove (15 samples)", open=False): + vinyl_gallery = gr.Gallery( + value=VINYL, columns=6, height=200, + show_label=False, allow_preview=False, object_fit="cover", + ) + + # Clicking any gallery thumbnail โ†’ loads image into inp โ†’ auto-analyses + for gal in [rubber_gallery, cloth_gallery, nitrile_gallery, vinyl_gallery]: + gal.select(fn=load_from_gallery, outputs=inp) + + gr.Markdown( + """ + --- + **Project**: IPPR Assignment โ€” Asia Pacific University (APU) + **Team**: Chiew Wen Qian ยท Farouk Elouzzani ยท Puteri Hannah ยท Wendy Koh Xin Hui + """ + ) + +if __name__ == "__main__": + demo.launch() diff --git a/detectors/__init__.py b/detectors/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c79d7fd9ea5c7304a8b429dd76c1b0f98026a497 --- /dev/null +++ b/detectors/__init__.py @@ -0,0 +1 @@ +# detectors package diff --git a/detectors/__pycache__/__init__.cpython-312.pyc b/detectors/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0a324142e7cae112cbaaf640eaeeb72370efb58 Binary files /dev/null and b/detectors/__pycache__/__init__.cpython-312.pyc differ diff --git a/detectors/__pycache__/cloth_detection.cpython-312.pyc b/detectors/__pycache__/cloth_detection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df577a2f0a08a1d65383ec67eac537b760d3df48 Binary files /dev/null and b/detectors/__pycache__/cloth_detection.cpython-312.pyc differ diff --git a/detectors/__pycache__/glove_type_detector.cpython-312.pyc b/detectors/__pycache__/glove_type_detector.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f92c147194756e94cb5ca13d9162b5b6f3b421b1 Binary files /dev/null and b/detectors/__pycache__/glove_type_detector.cpython-312.pyc differ diff --git a/detectors/__pycache__/nitrile_detection.cpython-312.pyc b/detectors/__pycache__/nitrile_detection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f907e51c03b8dd9a9bbef0bfcd90e26aaff87640 Binary files /dev/null and b/detectors/__pycache__/nitrile_detection.cpython-312.pyc differ diff --git a/detectors/__pycache__/rubber_detection.cpython-312.pyc b/detectors/__pycache__/rubber_detection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3379555c7d60dd45f7e4d0bc2089f315ffcc05f8 Binary files /dev/null and b/detectors/__pycache__/rubber_detection.cpython-312.pyc differ diff --git a/detectors/__pycache__/utils.cpython-312.pyc b/detectors/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b79da7a88e400353970cabffe5550ac3235ab5a6 Binary files /dev/null and b/detectors/__pycache__/utils.cpython-312.pyc differ diff --git a/detectors/__pycache__/vinyl_detection.cpython-312.pyc b/detectors/__pycache__/vinyl_detection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..254a62de91976f24937a04c76c9f5adbd3515fd6 Binary files /dev/null and b/detectors/__pycache__/vinyl_detection.cpython-312.pyc differ diff --git a/detectors/cloth_detection.py b/detectors/cloth_detection.py new file mode 100644 index 0000000000000000000000000000000000000000..49b488930f48d72c0a2529e890f6e233fd23d8b9 --- /dev/null +++ b/detectors/cloth_detection.py @@ -0,0 +1,116 @@ +""" +Cloth glove defect detector โ€” ported from Cloth_Detection.m (Wendy Koh Xin Hui). + +Detects: + MISSING FINGER โ€” long, eccentric skin-coloured region (torn/missing finger) + HOLE โ€” smaller skin-coloured spot inside the glove + STAIN โ€” orange-yellow patch with low eccentricity +""" + +import cv2 +import numpy as np +from skimage.measure import regionprops, label as sk_label +from .utils import fill_holes, remove_small_blobs, get_hsv, disk_kernel, rect_kernel + + +def detect_cloth(img_rgb: np.ndarray): + """ + Parameters + ---------- + img_rgb : np.ndarray (H, W, 3) uint8 RGB + + Returns + ------- + result : np.ndarray โ€” annotated RGB image + status : str โ€” 'Passed' or 'Defective' + """ + rows, cols = img_rgb.shape[:2] + defect_status = "Passed" + + # โ”€โ”€ Glove mask via Otsu โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY) + _, glove_mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + glove_mask = fill_holes(glove_mask) + + H, S, V = get_hsv(img_rgb) + + # โ”€โ”€ Pipeline A: stain detection (orange-yellow H โˆˆ [0.07, 0.17]) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + stain_raw = ( + (H >= 0.07) & (H <= 0.17) & (S >= 0.40) & (V >= 0.30) + ).astype(np.uint8) * 255 + stain_raw = stain_raw & glove_mask + se_stain = disk_kernel(2) + stain_mask = cv2.dilate(cv2.erode(stain_raw, se_stain), se_stain) + stain_mask = remove_small_blobs(stain_mask, 60) + + # โ”€โ”€ Pipeline B: skin / hole detection (pink-red H โˆˆ [0.0, 0.06]) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + skin_raw = ( + (H >= 0.0) & (H <= 0.06) & (S >= 0.25) & (S <= 0.70) & (V >= 0.15) + ).astype(np.uint8) * 255 + se_clean = disk_kernel(3) + cleaned = cv2.dilate(cv2.erode(skin_raw & glove_mask, se_clean), se_clean) + # Close with a tall rectangle to bridge finger-width gaps + se_finger = rect_kernel(150, 20) + finger_mask = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, se_finger) + finger_mask = finger_mask & ~stain_mask # stains must not bleed into finger mask + + # โ”€โ”€ Annotate โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + result = img_rgb.copy() + + # Finger / hole regions + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats( + finger_mask, connectivity=8 + ) + for i in range(1, n_labels): + bx = stats[i, cv2.CC_STAT_LEFT] + by = stats[i, cv2.CC_STAT_TOP] + bw = stats[i, cv2.CC_STAT_WIDTH] + bh = stats[i, cv2.CC_STAT_HEIGHT] + area = stats[i, cv2.CC_STAT_AREA] + + comp = (labels == i).astype(np.uint8) * 255 + lf = sk_label(comp > 0) + props = regionprops(lf) + if not props: + continue + ecc = props[0].eccentricity + ratio = bh / max(bw, 1) + + if (bh > 80 or ratio > 1.6) and ecc > 0.85: + cv2.rectangle(result, (bx, by), (bx + bw, by + bh), (255, 0, 0), 4) + cv2.putText( + result, "MISSING FINGER", (bx, max(by - 25, 12)), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2, cv2.LINE_AA, + ) + defect_status = "Defective" + elif area > 80: + cv2.rectangle(result, (bx, by), (bx + bw, by + bh), (255, 255, 0), 2) + cv2.putText( + result, "HOLE", (bx, max(by - 20, 12)), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2, cv2.LINE_AA, + ) + defect_status = "Defective" + + # Stain regions + n_s, labs_s, stats_s, _ = cv2.connectedComponentsWithStats( + stain_mask, connectivity=8 + ) + for j in range(1, n_s): + comp = (labs_s == j).astype(np.uint8) * 255 + lf = sk_label(comp > 0) + props = regionprops(lf) + if not props: + continue + if props[0].eccentricity < 0.85: + bx = stats_s[j, cv2.CC_STAT_LEFT] + by = stats_s[j, cv2.CC_STAT_TOP] + bw = stats_s[j, cv2.CC_STAT_WIDTH] + bh = stats_s[j, cv2.CC_STAT_HEIGHT] + cv2.rectangle(result, (bx, by), (bx + bw, by + bh), (255, 128, 0), 3) + cv2.putText( + result, "STAIN", (bx, by + bh + 20), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 128, 0), 2, cv2.LINE_AA, + ) + defect_status = "Defective" + + return result, defect_status diff --git a/detectors/glove_type_detector.py b/detectors/glove_type_detector.py new file mode 100644 index 0000000000000000000000000000000000000000..26c3b8acfab316f1294d7ee0c771d90a22ce7aed --- /dev/null +++ b/detectors/glove_type_detector.py @@ -0,0 +1,83 @@ +""" +Glove type classifier โ€” ported from GloveTypeDetector.m (Farouk Elouzzani). + +Pipeline: + 1. Otsu threshold on grayscale โ†’ binary foreground mask + 2. Invert mask if the border is mostly foreground (background/foreground swap) + 3. Fill holes + keep largest blob + 4. Extract HSV from foreground pixels, classify by dominant hue/saturation/value + Green โ†’ Rubber Glove + White โ†’ Cloth Glove + Purple โ†’ Nitrile Glove + Black โ†’ Vinyl Glove +""" + +import cv2 +import numpy as np +from .utils import fill_holes, keep_largest_blob, get_hsv + +GLOVE_TYPES = { + 1: "Rubber Glove", + 2: "Cloth Glove", + 3: "Nitrile Glove", + 4: "Vinyl Glove", +} + + +def detect_glove_type(img_rgb: np.ndarray): + """ + Classify the glove type in img_rgb. + + Parameters + ---------- + img_rgb : np.ndarray (H, W, 3) uint8 RGB + + Returns + ------- + masked_glove : np.ndarray โ€” input image with background zeroed out + glove_type : str โ€” one of the GLOVE_TYPES values + """ + if img_rgb.ndim == 2: + img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_GRAY2RGB) + + # --- Foreground / background separation via Otsu --- + gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY) + _, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + + # If the border pixels are mostly bright, the glove is the dark object โ†’ invert + border = np.concatenate( + [mask[0, :], mask[-1, :], mask[:, 0], mask[:, -1]] + ) + if np.sum(border > 0) > len(border) / 2: + mask = cv2.bitwise_not(mask) + + mask = fill_holes(mask) + mask = keep_largest_blob(mask, n=1) + + # --- HSV feature extraction from foreground --- + H, S, V = get_hsv(img_rgb) + fg = mask > 0 + if not np.any(fg): + fg = np.ones_like(mask, dtype=bool) + + fH, fS, fV = H[fg], S[fg], V[fg] + + is_black = fV < 0.25 + is_white = (fS < 0.10) & ~is_black + is_purple = (fH > 0.60) & (fH < 0.90) & ~is_white & ~is_black + is_green = (fH > 0.20) & (fH < 0.55) & ~is_white & ~is_black + + # Default class = 2 (Cloth / white) + classes = np.full(len(fH), 2, dtype=np.int32) + classes[is_green] = 1 + classes[is_white] = 2 + classes[is_purple] = 3 + classes[is_black] = 4 + + counts = {k: int(np.sum(classes == k)) for k in [1, 2, 3, 4]} + dominant = max(counts, key=counts.get) + + masked = img_rgb.copy() + masked[~fg] = 0 + + return masked, GLOVE_TYPES[dominant] diff --git a/detectors/nitrile_detection.py b/detectors/nitrile_detection.py new file mode 100644 index 0000000000000000000000000000000000000000..ab42ff90bc944362f2eabcc4a95963f70d533879 --- /dev/null +++ b/detectors/nitrile_detection.py @@ -0,0 +1,112 @@ +""" +Nitrile glove defect detector โ€” ported from Nitrile_Detection.m +(Puteri Hannah binti Mohamed Daud Ibrahim). + +Detects: + SPOT โ€” dark enclosed region (brightness < 0.25) + HOLE โ€” bright enclosed gap away from the palm edge + TEAR โ€” bright gap that touches the glove edge AND lies within the palm ROI +""" + +import cv2 +import numpy as np +from .utils import ( + fill_holes, keep_largest_blob, remove_small_blobs, + im_dilate, get_hsv, resize_to_height, disk_kernel, +) + + +def detect_nitrile(img_rgb: np.ndarray): + """ + Parameters + ---------- + img_rgb : np.ndarray (H, W, 3) uint8 RGB + + Returns + ------- + result : np.ndarray โ€” annotated RGB image + status : str โ€” 'Passed' or 'Defective' + """ + img = resize_to_height(img_rgb, 800) + rows, cols = img.shape[:2] + + blurred = cv2.GaussianBlur(img, (0, 0), 1.5) + H, S, V = get_hsv(blurred) + + # โ”€โ”€ Glove segmentation (purple H โˆˆ [0.55, 0.95]) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + raw_mask = ( + (H > 0.55) & (H < 0.95) & (S > 0.10) & (V > 0.15) + ).astype(np.uint8) * 255 + raw_mask = remove_small_blobs(raw_mask, 500) + + solid = fill_holes(raw_mask) + silhouette = keep_largest_blob(solid, n=1) + + # โ”€โ”€ Palm ROI: circle centred on the glove, radius = 23 % of image width โ”€โ”€ + palm_roi = np.zeros((rows, cols), dtype=bool) + n_lbl, lbl, stats, centroids = cv2.connectedComponentsWithStats( + silhouette, connectivity=8 + ) + if n_lbl > 1: + cx = float(centroids[1, 0]) + cy = float(centroids[1, 1]) + Y, X = np.mgrid[0:rows, 0:cols] + palm_roi = np.sqrt((X - cx) ** 2 + (Y - cy) ** 2) < (cols * 0.23) + + # โ”€โ”€ Defect masks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Internal gaps: fully enclosed holes within the silhouette + internal = (silhouette > 0) & ~(raw_mask > 0) + + # Edge tears: skin-tone pixels in a 15-px halo around the glove, inside palm ROI + skin_mask = ((H < 0.1) | (H > 0.90)) & (S > 0.15) & (V > 0.15) + halo = im_dilate(silhouette, 15) & ~(silhouette > 0) + edge_tears = skin_mask & (halo > 0) & palm_roi + + all_defects = (internal | edge_tears).astype(np.uint8) * 255 + all_defects = remove_small_blobs(all_defects, 40) + + # โ”€โ”€ Classification & annotation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + glove_boundary = cv2.Canny(silhouette, 100, 200) + thick_boundary = im_dilate(glove_boundary, 12) + + n_lbl, labels, stats, _ = cv2.connectedComponentsWithStats( + all_defects, connectivity=8 + ) + + result = img.copy() + any_defect = False + + for k in range(1, n_lbl): + idx = np.where(labels == k) + bb = stats[k] + bx, by = bb[cv2.CC_STAT_LEFT], bb[cv2.CC_STAT_TOP] + bw, bh = bb[cv2.CC_STAT_WIDTH], bb[cv2.CC_STAT_HEIGHT] + + # Centroid bounds-check + cx_d = int(np.mean(idx[1])) + cy_d = int(np.mean(idx[0])) + cx_d = min(max(cx_d, 0), cols - 1) + cy_d = min(max(cy_d, 0), rows - 1) + + brightness = float(np.median(V[idx])) + + if brightness < 0.25: + label, color = "SPOT", (255, 255, 0) + else: + touches_edge = bool( + np.any(thick_boundary[idx]) or np.any(edge_tears[idx]) + ) + in_palm = bool(palm_roi[cy_d, cx_d]) + if touches_edge and in_palm: + label, color = "TEAR", (255, 50, 50) + else: + label, color = "HOLE", (255, 0, 255) + + any_defect = True + cv2.rectangle(result, (bx, by), (bx + bw, by + bh), color, 3) + cv2.putText( + result, label, (bx, max(by - 8, 12)), + cv2.FONT_HERSHEY_SIMPLEX, 0.65, color, 2, cv2.LINE_AA, + ) + + return result, "Defective" if any_defect else "Passed" diff --git a/detectors/rubber_detection.py b/detectors/rubber_detection.py new file mode 100644 index 0000000000000000000000000000000000000000..c8eb9f4f6b7849672dca898ffa1a3ee4481f1e58 --- /dev/null +++ b/detectors/rubber_detection.py @@ -0,0 +1,119 @@ +""" +Rubber glove defect detector โ€” ported from Rubber_Detection.m (Chiew Wen Qian). + +Detects: + HOLE โ€” enclosed regions whose colour does not match the glove + DISCOLORATION โ€” abnormally vivid or dark patches inside the glove + FOLD โ€” elongated dark valleys with a sharp brightness gradient +""" + +import cv2 +import numpy as np +from skimage.measure import regionprops, label as sk_label +from .utils import ( + fill_holes, keep_largest_blob, remove_small_blobs, + im_close, im_erode, clean_mask, get_hsv, + resize_to_max, draw_labeled_regions, +) + + +def detect_rubber(img_rgb: np.ndarray): + """ + Parameters + ---------- + img_rgb : np.ndarray (H, W, 3) uint8 RGB + + Returns + ------- + result : np.ndarray โ€” annotated RGB image + status : str โ€” 'Passed' or 'Defective' + """ + img = resize_to_max(img_rgb, 800) + rows, cols = img.shape[:2] + + # โ”€โ”€ 1. HSV segmentation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + blurred = cv2.GaussianBlur(img, (0, 0), 1.5) + H, S, V = get_hsv(blurred) + + raw_glove = ( + (H > 0.40) & (H < 0.68) & (S > 0.08) & (V > 0.20) & (V < 0.92) + ).astype(np.uint8) * 255 + raw_glove = remove_small_blobs(raw_glove, 800) + + glove_mask = im_close(raw_glove, 6) + glove_mask = keep_largest_blob(glove_mask) + glove_mask = fill_holes(glove_mask) + safe_zone = im_erode(glove_mask, 8) + + sz = safe_zone > 0 + Vs, Ss = V[sz], S[sz] + medV = float(np.median(Vs)) if Vs.size else 0.5 + medS = float(np.median(Ss)) if Ss.size else 0.2 + stdV = float(np.std(Vs)) if Vs.size else 0.05 + + # โ”€โ”€ 2. Hole detection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Enclosed regions = filled glove minus raw glove (dark interior gaps) + enclosed = (fill_holes(raw_glove) > 0) & ~(raw_glove > 0) + enclosed = enclosed & (im_erode(glove_mask, 8) > 0) + enclosed = remove_small_blobs(enclosed.astype(np.uint8) * 255, 30) + + labeled = sk_label(enclosed > 0) + props = regionprops(labeled) + + holes_bool = np.zeros((rows, cols), dtype=bool) + for p in props: + if p.area < 80 or p.eccentricity >= 0.98: + continue + r_idx, c_idx = p.coords[:, 0], p.coords[:, 1] + rH = float(H[r_idx, c_idx].mean()) + rS = float(S[r_idx, c_idx].mean()) + rV = float(V[r_idx, c_idx].mean()) + + is_glove_color = (0.40 < rH < 0.68) and rS > 0.08 and (0.20 < rV < 0.92) + is_dark_stain = (rV < medV - 0.12) and (rS < medS) + if is_glove_color or is_dark_stain: + continue + holes_bool[r_idx, c_idx] = True + + holes_mask = clean_mask(holes_bool.astype(np.uint8) * 255, 6, 80) + + # โ”€โ”€ 3. Discoloration detection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + vivid = (S > medS + 0.18) & sz + dark_mark = (V < medV - max(3.5 * stdV, 0.15)) & (S < medS) & sz + dcol_bool = (vivid | dark_mark) & ~(holes_mask > 0) + dcol_mask = clean_mask(dcol_bool.astype(np.uint8) * 255, 4, 120) + + # โ”€โ”€ 4. Fold detection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + interior = im_erode(glove_mask, 20) > 0 + Vs_smooth = cv2.GaussianBlur(V.astype(np.float32), (0, 0), 1.5) + neigh_avg = cv2.blur(Vs_smooth, (25, 25)) + valley = (Vs_smooth < neigh_avg - 0.04) & interior + + gx = cv2.Sobel(Vs_smooth, cv2.CV_32F, 1, 0, ksize=3) + gy = cv2.Sobel(Vs_smooth, cv2.CV_32F, 0, 1, ksize=3) + grad = np.sqrt(gx ** 2 + gy ** 2) + g_max = grad.max() + grad_norm = grad / g_max if g_max > 0 else grad + sharp_edge = grad_norm > 0.18 + + fold_cand = ( + valley & sharp_edge & interior + & ~(holes_mask > 0) & ~(dcol_mask > 0) + ) + fold_cand = clean_mask(fold_cand.astype(np.uint8) * 255, 14, 500) + + lf = sk_label(fold_cand > 0) + fp = regionprops(lf) + folds_bool = np.zeros((rows, cols), dtype=bool) + for p in fp: + if p.eccentricity > 0.80 and p.area > 500: + folds_bool[p.coords[:, 0], p.coords[:, 1]] = True + + # โ”€โ”€ 5. Annotate โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + result = img.copy() + found = False + found = draw_labeled_regions(result, holes_mask, (255, 50, 50), "HOLE", 40) or found + found = draw_labeled_regions(result, dcol_mask, (255, 255, 0), "DISCOLORATION", 100) or found + found = draw_labeled_regions(result, folds_bool.astype(np.uint8)*255, (0, 255, 255), "FOLD", 400) or found + + return result, "Defective" if found else "Passed" diff --git a/detectors/utils.py b/detectors/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..d4133f2a482f2861c1d6fb89bc27fe5a01772f74 --- /dev/null +++ b/detectors/utils.py @@ -0,0 +1,170 @@ +""" +Shared image-processing helpers used across all glove detectors. +All functions accept and return uint8 binary masks (0 / 255) unless noted. +HSV values are normalised to [0, 1] throughout. +""" + +import cv2 +import numpy as np + + +# โ”€โ”€ Structuring elements โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def disk_kernel(r: int) -> np.ndarray: + """Disk-shaped SE โ€” equivalent to MATLAB strel('disk', r).""" + return cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * r + 1, 2 * r + 1)) + + +def rect_kernel(rows: int, cols: int) -> np.ndarray: + """Rectangular SE โ€” equivalent to MATLAB strel('rectangle', [rows, cols]).""" + return cv2.getStructuringElement(cv2.MORPH_RECT, (cols, rows)) + + +# โ”€โ”€ Morphological wrappers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def im_close(mask: np.ndarray, r: int) -> np.ndarray: + return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, disk_kernel(r)) + + +def im_open(mask: np.ndarray, r: int) -> np.ndarray: + return cv2.morphologyEx(mask, cv2.MORPH_OPEN, disk_kernel(r)) + + +def im_erode(mask: np.ndarray, r: int) -> np.ndarray: + return cv2.erode(mask, disk_kernel(r)) + + +def im_dilate(mask: np.ndarray, r: int) -> np.ndarray: + return cv2.dilate(mask, disk_kernel(r)) + + +# โ”€โ”€ Binary mask helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def fill_holes(binary_mask: np.ndarray) -> np.ndarray: + """Fill enclosed holes โ€” equivalent to MATLAB imfill(mask, 'holes').""" + h, w = binary_mask.shape + flood = binary_mask.copy() + flood_mask = np.zeros((h + 2, w + 2), np.uint8) + cv2.floodFill(flood, flood_mask, (0, 0), 255) + return binary_mask | cv2.bitwise_not(flood) + + +def keep_largest_blob(binary_mask: np.ndarray, n: int = 1) -> np.ndarray: + """Keep the n largest connected components โ€” equivalent to bwareafilt(mask, n).""" + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats( + binary_mask, connectivity=8 + ) + if n_labels <= 1: + return binary_mask + areas = stats[1:, cv2.CC_STAT_AREA] + top_labels = np.argsort(areas)[::-1][:n] + 1 + result = np.zeros_like(binary_mask) + for lbl in top_labels: + result[labels == lbl] = 255 + return result + + +def remove_small_blobs(binary_mask: np.ndarray, min_area: int) -> np.ndarray: + """Remove components smaller than min_area โ€” equivalent to bwareaopen.""" + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats( + binary_mask, connectivity=8 + ) + result = np.zeros_like(binary_mask) + for i in range(1, n_labels): + if stats[i, cv2.CC_STAT_AREA] >= min_area: + result[labels == i] = 255 + return result + + +def imclearborder(binary_mask: np.ndarray) -> np.ndarray: + """Remove blobs touching the image border โ€” equivalent to MATLAB imclearborder.""" + n_labels, labels, _, _ = cv2.connectedComponentsWithStats( + binary_mask, connectivity=8 + ) + border_labels = set() + for edge in (labels[0, :], labels[-1, :], labels[:, 0], labels[:, -1]): + border_labels.update(np.unique(edge).tolist()) + border_labels.discard(0) + result = binary_mask.copy() + for lbl in border_labels: + result[labels == lbl] = 0 + return result + + +def clean_mask(mask: np.ndarray, disk_r: int, min_area: int) -> np.ndarray: + """im_close then remove_small_blobs โ€” matches MATLAB cleanMask helper.""" + return remove_small_blobs(im_close(mask, disk_r), min_area) + + +# โ”€โ”€ Colour / resize helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def get_hsv(img_rgb: np.ndarray): + """ + Convert RGB image to HSV and return H, S, V as float32 arrays in [0, 1]. + OpenCV uses Hโˆˆ[0,179], Sโˆˆ[0,255], Vโˆˆ[0,255] โ€” we normalise here. + """ + hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV).astype(np.float32) + return hsv[:, :, 0] / 179.0, hsv[:, :, 1] / 255.0, hsv[:, :, 2] / 255.0 + + +def resize_to_max(img: np.ndarray, max_px: int = 800) -> np.ndarray: + """Resize so that the longer side equals max_px (preserves aspect ratio).""" + h, w = img.shape[:2] + if h >= w: + new_h, new_w = max_px, max(1, int(w * max_px / h)) + else: + new_h, new_w = max(1, int(h * max_px / w)), max_px + return cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + + +def resize_to_height(img: np.ndarray, target_h: int = 800) -> np.ndarray: + """Resize so height == target_h โ€” matches imresize(img, [800, NaN]).""" + h, w = img.shape[:2] + new_w = max(1, int(w * target_h / h)) + return cv2.resize(img, (new_w, target_h), interpolation=cv2.INTER_LINEAR) + + +def resize_to_width(img: np.ndarray, target_w: int = 800) -> np.ndarray: + """Resize so width == target_w โ€” matches imresize(img, scale) by width.""" + h, w = img.shape[:2] + new_h = max(1, int(h * target_w / w)) + return cv2.resize(img, (target_w, new_h), interpolation=cv2.INTER_LINEAR) + + +# โ”€โ”€ Drawing helper โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def draw_labeled_regions( + img: np.ndarray, + mask: np.ndarray, + color: tuple, + label: str, + min_area: int, +) -> bool: + """ + Draw a coloured outline + text label for every region in mask that is + larger than min_area. img is modified in place (RGB uint8). + Returns True if at least one region was drawn. + """ + if mask is None or not np.any(mask > 0): + return False + if mask.dtype != np.uint8: + mask = (mask > 0).astype(np.uint8) * 255 + + n_labels, labels, stats, _ = cv2.connectedComponentsWithStats( + mask, connectivity=8 + ) + found = False + for i in range(1, n_labels): + if stats[i, cv2.CC_STAT_AREA] < min_area: + continue + found = True + comp = ((labels == i).astype(np.uint8) * 255) + perim = im_dilate(comp, 3) - comp + img[perim > 0] = color + x = stats[i, cv2.CC_STAT_LEFT] + y = max(stats[i, cv2.CC_STAT_TOP] - 8, 12) + cv2.putText( + img, label, (x, y), + cv2.FONT_HERSHEY_SIMPLEX, 0.65, color, 2, cv2.LINE_AA, + ) + return found diff --git a/detectors/vinyl_detection.py b/detectors/vinyl_detection.py new file mode 100644 index 0000000000000000000000000000000000000000..cbeca74ea5371e3c9b6cb2fc87c195931c1533f3 --- /dev/null +++ b/detectors/vinyl_detection.py @@ -0,0 +1,111 @@ +""" +Vinyl glove defect detector โ€” ported from Vinyl_Detection.m (Farouk Elouzzani). + +Detects (blobs with area > 800 px after morphological cleaning): + PALM HOLE โ€” skin-coloured, circular (circularity > 0.8) + CUFF TEAR โ€” skin-coloured, elongated (circularity โ‰ค 0.8) + FOREIGN OBJ โ€” non-skin-coloured anomaly +""" + +import math +import cv2 +import numpy as np +from skimage.measure import regionprops, label as sk_label +from .utils import imclearborder, get_hsv, resize_to_width, im_open, im_close + + +def detect_vinyl(img_rgb: np.ndarray): + """ + Parameters + ---------- + img_rgb : np.ndarray (H, W, 3) uint8 RGB + + Returns + ------- + result : np.ndarray โ€” annotated RGB image + status : str โ€” 'Passed' or 'Defective' + """ + img = resize_to_width(img_rgb, 800) + rows, cols = img.shape[:2] + + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + H, S, V = get_hsv(img) + + # โ”€โ”€ Masks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Foreground: non-white pixels; Glove: dark (V < 0.35 = black vinyl body) + fg_mask = (gray < 210).astype(np.uint8) * 255 + glove_mask = (V < 0.35).astype(np.uint8) * 255 + + # Defects are foreground pixels that are NOT the dark glove but have colour + defect_mask = fg_mask & ~glove_mask & ((S > 0.15).astype(np.uint8) * 255) + + # โ”€โ”€ Morphological refinement โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + defect_mask = im_open(defect_mask, 3) + defect_mask = im_close(defect_mask, 3) + defect_mask = imclearborder(defect_mask) + + # โ”€โ”€ Blob analysis โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + lf = sk_label(defect_mask > 0) + props = regionprops(lf) + + result = img.copy() + defect_status = "Passed" + + for p in props: + if p.area <= 800: + continue + + defect_status = "Defective" + bx, by, bx2, by2 = ( + p.bbox[1], p.bbox[0], p.bbox[3], p.bbox[2] + ) + bw, bh = bx2 - bx, by2 - by + label_x = bx + bw // 2 + label_y = max(by - 40, 15) + + # Per-blob colour sampling + r_idx, c_idx = p.coords[:, 0], p.coords[:, 1] + meanH = float(H[r_idx, c_idx].mean()) + meanS = float(S[r_idx, c_idx].mean()) + meanV = float(V[r_idx, c_idx].mean()) + + is_skin = ( + 0.0 <= meanH <= 0.12 + and 0.20 <= meanS <= 0.70 + and 0.30 <= meanV <= 1.00 + ) + circularity = ( + (4 * math.pi * p.area) / (p.perimeter ** 2) + if p.perimeter > 0 else 0.0 + ) + + if is_skin: + if circularity > 0.8: + label, outline, bg = "PALM HOLE", (255, 255, 0), (0, 0, 0) + else: + label, outline, bg = "CUFF TEAR", (255, 50, 50), (255, 255, 255) + else: + label, outline, bg = "FOREIGN OBJ", (50, 255, 50), (0, 0, 0) + + # Draw boundary contour + comp = (lf == p.label).astype(np.uint8) * 255 + contours, _ = cv2.findContours(comp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + cv2.drawContours(result, contours, -1, outline, 3) + + # Draw label with background box + font = cv2.FONT_HERSHEY_SIMPLEX + font_scale = 0.6 + thickness = 2 + (tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness) + tx = label_x - tw // 2 + ty = label_y + cv2.rectangle(result, (tx - 2, ty - th - 2), (tx + tw + 2, ty + 2), bg, -1) + cv2.putText(result, label, (tx, ty), font, font_scale, outline, thickness, cv2.LINE_AA) + + if defect_status == "Passed": + cv2.putText( + result, "NO DEFECTS DETECTED", (20, 40), + cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv2.LINE_AA, + ) + + return result, defect_status diff --git a/examples/DC_1.jpeg b/examples/DC_1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e9644d1e7d22e9ac3607f2f667d0b0e98a421db1 Binary files /dev/null and b/examples/DC_1.jpeg differ diff --git a/examples/DC_2.jpeg b/examples/DC_2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fa0e87e298a0fc24796b74e48f48f19181eb71d6 Binary files /dev/null and b/examples/DC_2.jpeg differ diff --git a/examples/Fold_1.png b/examples/Fold_1.png new file mode 100644 index 0000000000000000000000000000000000000000..6b4009b80c6ab13fbad8efc403ae861500e1fbd0 --- /dev/null +++ b/examples/Fold_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c043246b23c4c52d57811dee286be67233ed38fa46a8438faa2e4dddebcd2c54 +size 4612910 diff --git a/examples/Fold_2.png b/examples/Fold_2.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6224320773679bf5beb5b688ab90c9a108876c --- /dev/null +++ b/examples/Fold_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0ab3407af03c4c840360b654fe9e1cba0d954716166558e9411cfb4ece6d65e +size 4860215 diff --git a/examples/Fold_3.png b/examples/Fold_3.png new file mode 100644 index 0000000000000000000000000000000000000000..62b4e12c4835544918b5258e08316153edd4995b --- /dev/null +++ b/examples/Fold_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28aed4fa85c0ca4f431c28c86cd8bc11dfd31c98b4f2a89066a01d465aea8f87 +size 4991271 diff --git a/examples/H(2).jpeg b/examples/H(2).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fdeb257f9cf87714a1b3216e417dfe4ea66865d2 Binary files /dev/null and b/examples/H(2).jpeg differ diff --git a/examples/H(3).jpeg b/examples/H(3).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..216dffee3fa3c44aca1f1a806c28ea406d2ef92a Binary files /dev/null and b/examples/H(3).jpeg differ diff --git a/examples/H(5).jpeg b/examples/H(5).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..3399859624b947787a26ad474d42e52bf1a7142c Binary files /dev/null and b/examples/H(5).jpeg differ diff --git a/examples/H(6).jpeg b/examples/H(6).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9b13eff852984e9a6a605c3db7f7ef10d65e815e Binary files /dev/null and b/examples/H(6).jpeg differ diff --git a/examples/H(7).jpeg b/examples/H(7).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4e4cd5b6d3658375947fb72ff45e3eef938abdd6 Binary files /dev/null and b/examples/H(7).jpeg differ diff --git a/examples/Holes_1.png b/examples/Holes_1.png new file mode 100644 index 0000000000000000000000000000000000000000..7bcfb21fdd8e952b0e38be26a198cb7027586f91 --- /dev/null +++ b/examples/Holes_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4655841992af8ed026f1d0152aa454390e3186d20b21bdefe46ff08622c4278 +size 4921753 diff --git a/examples/Holes_2.png b/examples/Holes_2.png new file mode 100644 index 0000000000000000000000000000000000000000..862686315b7dce3a89c7523095f2f07ce3cec0c3 --- /dev/null +++ b/examples/Holes_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ea48fc1dd362b41691e1b89ec2cc0e4b15cf2459be6418d551b8d96ca8fb370 +size 4924463 diff --git a/examples/Holes_3.png b/examples/Holes_3.png new file mode 100644 index 0000000000000000000000000000000000000000..750b0897988e49730b5b37095945869b9a1b6dc5 --- /dev/null +++ b/examples/Holes_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8b39670e8d4c4e0a8f62ba06d43b66d9d51f0c344e9c60031f657d2b2f8288 +size 4691979 diff --git a/examples/Holes_4.png b/examples/Holes_4.png new file mode 100644 index 0000000000000000000000000000000000000000..eb0231cc3c141dee1ad7044bdff602da2ae1c413 --- /dev/null +++ b/examples/Holes_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31a5110ea6a3e0ba2e78dfb3fe490b2001ac51a60ddc878544a84229a9af1282 +size 980945 diff --git a/examples/MF(2).jpeg b/examples/MF(2).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..33fddf430b41af1608854a021a27662e141168d5 Binary files /dev/null and b/examples/MF(2).jpeg differ diff --git a/examples/MF(3).jpeg b/examples/MF(3).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1249e122e0ce155a32803c4b2f0831fa5dcd3bc8 Binary files /dev/null and b/examples/MF(3).jpeg differ diff --git a/examples/MF(4).jpeg b/examples/MF(4).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..afaadd97fc08ab783389655ff1f8c624b6fcca7c Binary files /dev/null and b/examples/MF(4).jpeg differ diff --git a/examples/MF(5).jpeg b/examples/MF(5).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7c7bfc9a68803a11f320c2234d7f51bc8d831ac5 Binary files /dev/null and b/examples/MF(5).jpeg differ diff --git a/examples/MF(6).jpeg b/examples/MF(6).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..11448374118c1ae5d31e4cff39790caa507820f5 Binary files /dev/null and b/examples/MF(6).jpeg differ diff --git a/examples/MF(7).jpeg b/examples/MF(7).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..741ad1a9aa14a7a1d57b4abd71d4cfbdb2355952 Binary files /dev/null and b/examples/MF(7).jpeg differ diff --git a/examples/Normal_1.jpg b/examples/Normal_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7c3686d4ae3b71bf00e1cbd2a90f7687fb2b097d Binary files /dev/null and b/examples/Normal_1.jpg differ diff --git a/examples/Normal_2.jpg b/examples/Normal_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..131461f52387b04480727a73f439425a7c5d61fc Binary files /dev/null and b/examples/Normal_2.jpg differ diff --git a/examples/Original_1.jpeg b/examples/Original_1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2fea69fb11dc795149d9a4812e762dbdbd092567 Binary files /dev/null and b/examples/Original_1.jpeg differ diff --git a/examples/Original_2.jpeg b/examples/Original_2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5061fab4c6210747c7b1ff6f10ed11ec1ea801b0 Binary files /dev/null and b/examples/Original_2.jpeg differ diff --git a/examples/Original_3.jpeg b/examples/Original_3.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c2190081be25331a11a236d7fbbdd77b81c9d842 Binary files /dev/null and b/examples/Original_3.jpeg differ diff --git a/examples/PH_1.jpeg b/examples/PH_1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7fafaa2c8eb247c87d0fb1f310dd9d971bae4c3f Binary files /dev/null and b/examples/PH_1.jpeg differ diff --git a/examples/PH_2.jpeg b/examples/PH_2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f35bf8dca87263b7d192f078ac4ca809ce9e6b1a Binary files /dev/null and b/examples/PH_2.jpeg differ diff --git a/examples/PH_3.jpeg b/examples/PH_3.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ad27f20fd5f786ee4cdb0a13f1ed22d534fbac56 Binary files /dev/null and b/examples/PH_3.jpeg differ diff --git a/examples/S(1).jpeg b/examples/S(1).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d858a9dd8f51f5b6c77fb01955f0a64472144f52 Binary files /dev/null and b/examples/S(1).jpeg differ diff --git a/examples/S(10).jpeg b/examples/S(10).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..3b31375809e0f85e0533c9d5a2d945ec4593f454 Binary files /dev/null and b/examples/S(10).jpeg differ diff --git a/examples/S(7).jpeg b/examples/S(7).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..14747c544bedd6f2a8e2ceb089fa419ec83d57a3 Binary files /dev/null and b/examples/S(7).jpeg differ diff --git a/examples/S(8).jpeg b/examples/S(8).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c6ed077af3a03c05b628ec3bc814a1f4deb33b81 Binary files /dev/null and b/examples/S(8).jpeg differ diff --git a/examples/S(9).jpeg b/examples/S(9).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d920a3ceb2d671add7772b90f8a634f4c3850d50 Binary files /dev/null and b/examples/S(9).jpeg differ diff --git a/examples/Spots_1.png b/examples/Spots_1.png new file mode 100644 index 0000000000000000000000000000000000000000..651d6749bdad43d8b22ddaf362433d4a498c2255 --- /dev/null +++ b/examples/Spots_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3fc0172b7ff9e5f2b8eaf036d4d1784840bd13f7d1caadd1693aa63fc14554e +size 1184423 diff --git a/examples/Spots_2.png b/examples/Spots_2.png new file mode 100644 index 0000000000000000000000000000000000000000..08a4752e539655062816d79353fe828291d94638 --- /dev/null +++ b/examples/Spots_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5f5bd0ce1e75602bf9ffdc23ebf8c24d1f0832b211e72a6289a54a9fb0cd5fc +size 1252963 diff --git a/examples/Spots_3.png b/examples/Spots_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b42958315e1f3ac979105f0283c74eb23d0845f7 --- /dev/null +++ b/examples/Spots_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c15bb820212c199ba25efa224fcf3134da9f869824d4165c4a5ce195bf2548f +size 1197789 diff --git a/examples/Spots_4.png b/examples/Spots_4.png new file mode 100644 index 0000000000000000000000000000000000000000..155a26bf32f7f1d0fb735831d855db9715f5c7ca --- /dev/null +++ b/examples/Spots_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cf6346e5f726907aa8be244c5d3ebc682b59372fe4c8336152bcd8e0de3b9af +size 5142778 diff --git a/examples/Spots_5.png b/examples/Spots_5.png new file mode 100644 index 0000000000000000000000000000000000000000..cc7ce37fb1e3fc21ba0d77b8eba6d1cd74d4aeba --- /dev/null +++ b/examples/Spots_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17558c973c15f56dd40123de98e430c6d7955604bd1f3297021f26314aa4e71d +size 4814926 diff --git a/examples/Tear_1.jpg b/examples/Tear_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8797191215aa02f3887ff0873af25ee601500f36 Binary files /dev/null and b/examples/Tear_1.jpg differ diff --git a/examples/Tear_2.jpg b/examples/Tear_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f24a78262861c0fff357b8f96b96fd5237852caf Binary files /dev/null and b/examples/Tear_2.jpg differ diff --git a/examples/Tear_3.jpg b/examples/Tear_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bea40027ce81cef8644f762f62fbfba898afa290 Binary files /dev/null and b/examples/Tear_3.jpg differ diff --git a/examples/vinyl_cuff_1.jpg b/examples/vinyl_cuff_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d25439454aeabe1bffe5ee9c24ef655b4e5d224 --- /dev/null +++ b/examples/vinyl_cuff_1.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f350941d571a557cc3c403124ccef1580b391477b4f3689450c16a09226652eb +size 901074 diff --git a/examples/vinyl_cuff_2.jpg b/examples/vinyl_cuff_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..232f426b1eaafe993792e7dfebcd8fadb7d81502 --- /dev/null +++ b/examples/vinyl_cuff_2.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19d72b6c7d33c78b0f9348279b4c4469f596f18ea95aab628ce67b80a9591bab +size 862472 diff --git a/examples/vinyl_cuff_3.jpg b/examples/vinyl_cuff_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..92b65eb48d2f10405851918c90fb0381f8b5724c --- /dev/null +++ b/examples/vinyl_cuff_3.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b85be62e72698612090fb7fff2cf7af7d3976c58af467227ea8ccde47bf4b322 +size 971212 diff --git a/examples/vinyl_cuff_4.jpg b/examples/vinyl_cuff_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9f3fe5554c41d6d0af51b5353b674311c5c4d6db --- /dev/null +++ b/examples/vinyl_cuff_4.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c225016ec9c143596fb0b002bb2088701c0939efc2604c01613a542f1ca90430 +size 895998 diff --git a/examples/vinyl_cuff_5.jpg b/examples/vinyl_cuff_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..23b715249c189ef82d7cbe87e8977c0ea42625bd --- /dev/null +++ b/examples/vinyl_cuff_5.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11c0b79eea07d56992ef24096746667fc9874b9f2e85f313ffcb4221cee232c9 +size 888588 diff --git a/examples/vinyl_foreign_object_1.jpg b/examples/vinyl_foreign_object_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..97d001dac8d35b4ca46470552faafc7570a3b1fc --- /dev/null +++ b/examples/vinyl_foreign_object_1.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b771ea51aeb35774daa3c8e2ff2d90d67b98f307511f0ed9c4dc4f6f7e02b14 +size 946221 diff --git a/examples/vinyl_foreign_object_2.jpg b/examples/vinyl_foreign_object_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab9a0798763b0392859ac1456a2fd3df7b9cb605 --- /dev/null +++ b/examples/vinyl_foreign_object_2.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96d23faaf7ce5bcadee0f25d61c3919e424ff177988c61bb9dded025e09c5698 +size 917824 diff --git a/examples/vinyl_foreign_object_3.jpg b/examples/vinyl_foreign_object_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8b08661c4eaee18f9a84d4a145549de5a9075c7e --- /dev/null +++ b/examples/vinyl_foreign_object_3.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cbe99e0080b7df9739d4a3ca0864c841bd10eae7771fdcd2e462c1799d361d3 +size 905186 diff --git a/examples/vinyl_foreign_object_4.jpg b/examples/vinyl_foreign_object_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ac4031760cdffa6a5de12684375f0417baab4ea --- /dev/null +++ b/examples/vinyl_foreign_object_4.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ce60edc34854504cdb2ca79b8619286d364b9c64f92840fa18d46adf314618a +size 927319 diff --git a/examples/vinyl_foreign_object_5.jpg b/examples/vinyl_foreign_object_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9b62d2685671e123a4600445d4e82df4e7d19248 --- /dev/null +++ b/examples/vinyl_foreign_object_5.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b720a483b33c255c3407bc6c28bf2554348dc6c65905af53fa043f41ac51c700 +size 901007 diff --git a/examples/vinyl_hole_1.jpg b/examples/vinyl_hole_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..88cdd0358eb123df2d6589cd521ffef0a1a19f9c --- /dev/null +++ b/examples/vinyl_hole_1.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735b7f603dfddc38dc146336873b8cd74ed641263da49dcd328deea448455330 +size 862320 diff --git a/examples/vinyl_hole_2.jpg b/examples/vinyl_hole_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a52805ced40c70fd3facc0f003ff3ed1daccd42 --- /dev/null +++ b/examples/vinyl_hole_2.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbca5d8c4f146efe936f7ea0611bc4b4efe93b8b85cd42107444d0d1f08c2613 +size 832911 diff --git a/examples/vinyl_hole_3.jpg b/examples/vinyl_hole_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6143c4d9dc8b6aef0322e810a74eb33b6952905a --- /dev/null +++ b/examples/vinyl_hole_3.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1af01b77c4ab59235b586e0c5d42934c4ce96a1fbed41a90ed0078532ea4bb36 +size 836327 diff --git a/examples/vinyl_hole_4.jpg b/examples/vinyl_hole_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4f7d195eaa20b2eb580ff0e315c9b8d691c10cb --- /dev/null +++ b/examples/vinyl_hole_4.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9e665da84332452be8b44dc6a687509e8b15c5720b9c9d270a5afd181b010ac +size 891664 diff --git a/examples/vinyl_hole_5.jpg b/examples/vinyl_hole_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4deccfb62ee9f45d52b1de0039b817b3d909408f --- /dev/null +++ b/examples/vinyl_hole_5.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f87a79a631813319dd8c55564d47aaf4b67dd7603a46b5f640d87d9151f48d6 +size 875977 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..0a289be97910fc6d3d84d5d7775c12664f0548db --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +gradio>=4.0.0 +opencv-python-headless>=4.8.0 +scikit-image>=0.21.0 +numpy>=1.24.0