File size: 5,432 Bytes
0d3fe75 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """Подготовка датасета: нарезка панелей кузова на патчи и разбиение 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()
|