therealestcoder commited on
Commit
0d3fe75
·
verified ·
1 Parent(s): 545e859

Upload src\prepare_data.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src//prepare_data.py +135 -0
src//prepare_data.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Подготовка датасета: нарезка панелей кузова на патчи и разбиение train/val.
2
+
3
+ Из исходных фото 4000x1846 (плоские панели — образцы окраски) автоматически
4
+ вырезается область панели (по яркости/контрасту), затем нарезаются перекрытые
5
+ патчи 512x512. Дефектные образцы → класс 1, образцы без дефектов → класс 0.
6
+
7
+ Запуск: python -m src.prepare_data
8
+ """
9
+ from __future__ import annotations
10
+ import shutil
11
+ import random
12
+ from pathlib import Path
13
+
14
+ import cv2
15
+ import numpy as np
16
+
17
+ from . import config as C
18
+
19
+
20
+ def imread_unicode(path: Path) -> np.ndarray | None:
21
+ """cv2.imread не понимает Cyrillic-пути на Windows — обходим через np.fromfile."""
22
+ try:
23
+ data = np.fromfile(str(path), dtype=np.uint8)
24
+ if data.size == 0:
25
+ return None
26
+ return cv2.imdecode(data, cv2.IMREAD_COLOR)
27
+ except Exception:
28
+ return None
29
+
30
+
31
+ def imwrite_unicode(path: Path, img: np.ndarray, params=None) -> bool:
32
+ ext = path.suffix or ".jpg"
33
+ ok, buf = cv2.imencode(ext, img, params or [])
34
+ if not ok:
35
+ return False
36
+ buf.tofile(str(path))
37
+ return True
38
+
39
+
40
+ def crop_panel(bgr: np.ndarray) -> np.ndarray:
41
+ """Вырезает прямоугольник панели из светлого фона.
42
+
43
+ На исходных фото панель окраски лежит на белом столе. Бинаризуем изображение
44
+ по Оцу, берём наибольший контур и вырезаем его bounding box.
45
+ """
46
+ gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
47
+ blur = cv2.GaussianBlur(gray, (9, 9), 0)
48
+ # Панель темнее белого фона -> инвертированный Оцу
49
+ _, th = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
50
+ th = cv2.morphologyEx(th, cv2.MORPH_OPEN, np.ones((15, 15), np.uint8))
51
+ contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
52
+ if not contours:
53
+ return bgr
54
+ c = max(contours, key=cv2.contourArea)
55
+ x, y, w, h = cv2.boundingRect(c)
56
+ # отступаем внутрь, чтобы не зацепить край/тень/наклейку
57
+ pad = int(0.04 * min(w, h))
58
+ x1, y1 = max(0, x + pad), max(0, y + pad)
59
+ x2, y2 = min(bgr.shape[1], x + w - pad), min(bgr.shape[0], y + h - pad)
60
+ if x2 - x1 < 200 or y2 - y1 < 200:
61
+ return bgr
62
+ return bgr[y1:y2, x1:x2]
63
+
64
+
65
+ def slice_patches(panel: np.ndarray, size: int, stride: int) -> list[np.ndarray]:
66
+ """Нарезает панель на квадратные патчи с заданным шагом."""
67
+ h, w = panel.shape[:2]
68
+ if h < size or w < size:
69
+ # маленькая панель: один центральный ресайз
70
+ return [cv2.resize(panel, (size, size))]
71
+ patches = []
72
+ ys = list(range(0, h - size + 1, stride))
73
+ xs = list(range(0, w - size + 1, stride))
74
+ if ys[-1] != h - size:
75
+ ys.append(h - size)
76
+ if xs[-1] != w - size:
77
+ xs.append(w - size)
78
+ for y in ys:
79
+ for x in xs:
80
+ patches.append(panel[y:y + size, x:x + size])
81
+ return patches
82
+
83
+
84
+ def main(val_ratio: float = 0.2, seed: int = C.SEED) -> None:
85
+ random.seed(seed)
86
+ src_pairs = [
87
+ (C.SRC_DEFECT, "defect"),
88
+ (C.SRC_CLEAN, "clean"),
89
+ ]
90
+
91
+ # пересобираем выходные каталоги
92
+ if C.DATA_PATCHES.exists():
93
+ shutil.rmtree(C.DATA_PATCHES)
94
+ for split in ("train", "val"):
95
+ for cls in ("defect", "clean"):
96
+ (C.DATA_PATCHES / split / cls).mkdir(parents=True, exist_ok=True)
97
+
98
+ # также продублируем оригиналы в data/raw для удобства
99
+ C.DATA_RAW.mkdir(parents=True, exist_ok=True)
100
+ for src_dir, cls in src_pairs:
101
+ out = C.DATA_RAW / cls
102
+ out.mkdir(exist_ok=True)
103
+ for f in src_dir.iterdir():
104
+ if f.suffix.lower() in {".jpg", ".jpeg", ".png"}:
105
+ shutil.copy2(f, out / f.name)
106
+
107
+ total = {"train": 0, "val": 0}
108
+ for src_dir, cls in src_pairs:
109
+ files = [f for f in src_dir.iterdir()
110
+ if f.suffix.lower() in {".jpg", ".jpeg", ".png"}]
111
+ random.shuffle(files)
112
+ n_val = max(1, int(len(files) * val_ratio))
113
+ val_files = set(files[:n_val])
114
+
115
+ for f in files:
116
+ split = "val" if f in val_files else "train"
117
+ img = imread_unicode(f)
118
+ if img is None:
119
+ print(f"[skip] не удалось прочитать {f}")
120
+ continue
121
+ panel = crop_panel(img) if C.PANEL_CROP else img
122
+ patches = slice_patches(panel, C.PATCH_SIZE, C.PATCH_STRIDE)
123
+ stem = f.stem
124
+ for i, p in enumerate(patches):
125
+ out_path = C.DATA_PATCHES / split / cls / f"{stem}_{i:03d}.jpg"
126
+ imwrite_unicode(out_path, p, [cv2.IMWRITE_JPEG_QUALITY, 92])
127
+ total[split] += 1
128
+ print(f"[{split}/{cls}] {f.name}: {len(patches)} патчей")
129
+
130
+ print(f"\nИтого патчей: train={total['train']} val={total['val']}")
131
+ print(f"Готовый датасет: {C.DATA_PATCHES}")
132
+
133
+
134
+ if __name__ == "__main__":
135
+ main()