Instructions to use therealestcoder/paint_defect_detector with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- timm
How to use therealestcoder/paint_defect_detector with timm:
import timm model = timm.create_model("hf_hub:therealestcoder/paint_defect_detector", pretrained=True) - Notebooks
- Google Colab
- Kaggle
| """Подготовка датасета: нарезка панелей кузова на патчи и разбиение train/val. | |
| Из исходных фото 4000x1846 (плоские панели — образцы окраски) автоматически | |
| вырезается область панели (по яркости/контрасту), затем нарезаются перекрытые | |
| патчи 512x512. Дефектные образцы → класс 1, образцы без дефектов → класс 0. | |
| Запуск: python -m src.prepare_data | |
| """ | |
| from __future__ import annotations | |
| import shutil | |
| import random | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| from . import config as C | |
| def imread_unicode(path: Path) -> np.ndarray | None: | |
| """cv2.imread не понимает Cyrillic-пути на Windows — обходим через np.fromfile.""" | |
| try: | |
| data = np.fromfile(str(path), dtype=np.uint8) | |
| if data.size == 0: | |
| return None | |
| return cv2.imdecode(data, cv2.IMREAD_COLOR) | |
| except Exception: | |
| return None | |
| def imwrite_unicode(path: Path, img: np.ndarray, params=None) -> bool: | |
| ext = path.suffix or ".jpg" | |
| ok, buf = cv2.imencode(ext, img, params or []) | |
| if not ok: | |
| return False | |
| buf.tofile(str(path)) | |
| return True | |
| def crop_panel(bgr: np.ndarray) -> np.ndarray: | |
| """Вырезает прямоугольник панели из светлого фона. | |
| На исходных фото панель окраски лежит на белом столе. Бинаризуем изображение | |
| по Оцу, берём наибольший контур и вырезаем его bounding box. | |
| """ | |
| gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY) | |
| blur = cv2.GaussianBlur(gray, (9, 9), 0) | |
| # Панель темнее белого фона -> инвертированный Оцу | |
| _, th = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) | |
| th = cv2.morphologyEx(th, cv2.MORPH_OPEN, np.ones((15, 15), np.uint8)) | |
| contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| if not contours: | |
| return bgr | |
| c = max(contours, key=cv2.contourArea) | |
| x, y, w, h = cv2.boundingRect(c) | |
| # отступаем внутрь, чтобы не зацепить край/тень/наклейку | |
| pad = int(0.04 * min(w, h)) | |
| x1, y1 = max(0, x + pad), max(0, y + pad) | |
| x2, y2 = min(bgr.shape[1], x + w - pad), min(bgr.shape[0], y + h - pad) | |
| if x2 - x1 < 200 or y2 - y1 < 200: | |
| return bgr | |
| return bgr[y1:y2, x1:x2] | |
| def slice_patches(panel: np.ndarray, size: int, stride: int) -> list[np.ndarray]: | |
| """Нарезает панель на квадратные патчи с заданным шагом.""" | |
| h, w = panel.shape[:2] | |
| if h < size or w < size: | |
| # маленькая панель: один центральный ресайз | |
| return [cv2.resize(panel, (size, size))] | |
| patches = [] | |
| ys = list(range(0, h - size + 1, stride)) | |
| xs = list(range(0, w - size + 1, stride)) | |
| if ys[-1] != h - size: | |
| ys.append(h - size) | |
| if xs[-1] != w - size: | |
| xs.append(w - size) | |
| for y in ys: | |
| for x in xs: | |
| patches.append(panel[y:y + size, x:x + size]) | |
| return patches | |
| def main(val_ratio: float = 0.2, seed: int = C.SEED) -> None: | |
| random.seed(seed) | |
| src_pairs = [ | |
| (C.SRC_DEFECT, "defect"), | |
| (C.SRC_CLEAN, "clean"), | |
| ] | |
| # пересобираем выходные каталоги | |
| if C.DATA_PATCHES.exists(): | |
| shutil.rmtree(C.DATA_PATCHES) | |
| for split in ("train", "val"): | |
| for cls in ("defect", "clean"): | |
| (C.DATA_PATCHES / split / cls).mkdir(parents=True, exist_ok=True) | |
| # также продублируем оригиналы в data/raw для удобства | |
| C.DATA_RAW.mkdir(parents=True, exist_ok=True) | |
| for src_dir, cls in src_pairs: | |
| out = C.DATA_RAW / cls | |
| out.mkdir(exist_ok=True) | |
| for f in src_dir.iterdir(): | |
| if f.suffix.lower() in {".jpg", ".jpeg", ".png"}: | |
| shutil.copy2(f, out / f.name) | |
| total = {"train": 0, "val": 0} | |
| for src_dir, cls in src_pairs: | |
| files = [f for f in src_dir.iterdir() | |
| if f.suffix.lower() in {".jpg", ".jpeg", ".png"}] | |
| random.shuffle(files) | |
| n_val = max(1, int(len(files) * val_ratio)) | |
| val_files = set(files[:n_val]) | |
| for f in files: | |
| split = "val" if f in val_files else "train" | |
| img = imread_unicode(f) | |
| if img is None: | |
| print(f"[skip] не удалось прочитать {f}") | |
| continue | |
| panel = crop_panel(img) if C.PANEL_CROP else img | |
| patches = slice_patches(panel, C.PATCH_SIZE, C.PATCH_STRIDE) | |
| stem = f.stem | |
| for i, p in enumerate(patches): | |
| out_path = C.DATA_PATCHES / split / cls / f"{stem}_{i:03d}.jpg" | |
| imwrite_unicode(out_path, p, [cv2.IMWRITE_JPEG_QUALITY, 92]) | |
| total[split] += 1 | |
| print(f"[{split}/{cls}] {f.name}: {len(patches)} патчей") | |
| print(f"\nИтого патчей: train={total['train']} val={total['val']}") | |
| print(f"Готовый датасет: {C.DATA_PATCHES}") | |
| if __name__ == "__main__": | |
| main() | |