pipeline.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. #!/usr/bin/env python3
  2. """
  3. 起重机挂钩数据集生成全流程
  4. ============================
  5. Step 1: 视频抽帧 → Step 2: Engagement筛选 → Step 3: Ready注入
  6. → Step 4: 质量过滤 → Step 5: 高阈值重推理 → Step 6: 质量排序 → Step 7: CVAT导出
  7. 用法:
  8. python pipeline.py # 运行全部步骤
  9. python pipeline.py --skip-extract # 跳过抽帧
  10. python pipeline.py --skip-extract --skip-select # 跳过多个步骤
  11. """
  12. import os
  13. import sys
  14. import re
  15. import time
  16. import shutil
  17. import zipfile
  18. import subprocess
  19. import numpy as np
  20. from pathlib import Path
  21. from concurrent.futures import ThreadPoolExecutor
  22. from config import *
  23. # ============================================================
  24. # Step 1: 视频抽帧 (FFmpeg NVDEC硬件加速)
  25. # ============================================================
  26. def step1_extract():
  27. print("=" * 60)
  28. print("Step 1: 视频抽帧 (FFmpeg NVDEC)")
  29. print("=" * 60)
  30. if EXTRACTED_DIR.exists():
  31. print(f" 已存在 {EXTRACTED_DIR},跳过抽帧")
  32. return
  33. videos = []
  34. for mp4 in VIDEO_DIR.rglob('*.mp4'):
  35. parts = mp4.parts
  36. try:
  37. cam_idx = parts.index(VIDEO_DIR_NAME)
  38. cam_id = parts[cam_idx + 1]
  39. cam_name = f'cam{cam_id.zfill(2)}'
  40. date = parts[cam_idx + 2]
  41. except (IndexError, ValueError):
  42. continue
  43. videos.append((mp4, cam_name, date))
  44. print(f" 找到 {len(videos)} 个视频")
  45. EXTRACTED_DIR.mkdir(parents=True, exist_ok=True)
  46. total_frames = 0
  47. for i, (mp4, cam_name, date) in enumerate(videos, 1):
  48. out_dir = EXTRACTED_DIR / cam_name
  49. out_dir.mkdir(exist_ok=True)
  50. stem = mp4.stem
  51. pattern = str(out_dir / f'{cam_name}_{date}_{stem}_%06d.jpg')
  52. cmd = ['ffmpeg', '-hide_banner', '-loglevel', 'error',
  53. '-hwaccel', 'nvdec', '-c:v', 'hevc_cuvid',
  54. '-i', str(mp4),
  55. '-qmin', '1', '-qmax', '1', '-vsync', '0', pattern]
  56. print(f" [{i}/{len(videos)}] {cam_name}/{date}/{stem}...", end=' ', flush=True)
  57. t0 = time.time()
  58. subprocess.run(cmd, check=True)
  59. n_frames = len(list(out_dir.glob(f'{cam_name}_{date}_{stem}_*.jpg')))
  60. for j in range(1, n_frames + 1):
  61. lbl = out_dir / f'{cam_name}_{date}_{stem}_{j:06d}.txt'
  62. if not lbl.exists():
  63. lbl.touch()
  64. elapsed = time.time() - t0
  65. total_frames += n_frames
  66. print(f"{n_frames} frames in {elapsed:.0f}s")
  67. print(f"\n 抽帧完成: {total_frames} 帧")
  68. # ============================================================
  69. # Step 2: Engagement评分 + 去重 + 排序选取
  70. # ============================================================
  71. def _score_and_pos(dets):
  72. """评分:hoist+ear共现=0.7, 仅ear=0.4, 中心距离奖励, 多hoist惩罚"""
  73. if len(dets) == 0:
  74. return 0
  75. has_hoist = np.any(dets[:, 0] == 0)
  76. has_ear = np.any(dets[:, 0] == 1)
  77. n_hoist = int(np.sum(dets[:, 0] == 0))
  78. score = 0.7 if (has_hoist and has_ear) else (0.4 if has_ear else 0.0)
  79. avg_xc = np.mean(dets[:, 1])
  80. avg_yc = np.mean(dets[:, 2])
  81. center_dist = ((avg_xc - 0.5)**2 + (avg_yc - 0.5)**2) ** 0.5
  82. score += max(0, 0.3 - center_dist) * 0.5
  83. if n_hoist > 1:
  84. score -= (n_hoist - 1) * 0.1
  85. return max(0, score)
  86. def _fix_duplicates(labels):
  87. """去重:ear保留最小框,hoist保留最大框"""
  88. for cls_id, keep_func in [(1, 'min'), (0, 'max')]:
  89. cls_mask = labels[:, 0] == cls_id
  90. if np.sum(cls_mask) <= 1:
  91. continue
  92. cls_dets = labels[cls_mask]
  93. areas = cls_dets[:, 3] * cls_dets[:, 4]
  94. keep_idx = np.argmin(areas) if keep_func == 'min' else np.argmax(areas)
  95. keep_mask = np.ones(len(labels), dtype=bool)
  96. cls_indices = np.where(cls_mask)[0]
  97. for ri in range(len(cls_dets)):
  98. if ri != keep_idx:
  99. keep_mask[cls_indices[ri]] = False
  100. labels = labels[keep_mask]
  101. return labels
  102. def step2_select():
  103. print("\n" + "=" * 60)
  104. print("Step 2: Engagement评分 + 去重 + 选取engaged帧")
  105. print("=" * 60)
  106. if OUTPUT_DIR.exists():
  107. shutil.rmtree(str(OUTPUT_DIR))
  108. cameras = sorted([d.name for d in EXTRACTED_IMG_DIR.iterdir() if d.is_dir()])
  109. all_kept = {}
  110. for cam_name in cameras:
  111. t0 = time.time()
  112. lbl_dir = EXTRACTED_LBL_DIR / cam_name
  113. # 读取所有标签
  114. labels = {}
  115. with os.scandir(str(lbl_dir)) as it:
  116. for entry in it:
  117. if not entry.name.endswith('.txt'):
  118. continue
  119. with open(entry.path) as f:
  120. data = f.read().strip()
  121. stem = entry.name[:-4]
  122. if data:
  123. rows = []
  124. for line in data.split('\n'):
  125. parts = line.split()
  126. if len(parts) >= 5:
  127. rows.append([float(x) for x in parts[:5]])
  128. labels[stem] = np.array(rows) if rows else np.zeros((0, 5))
  129. else:
  130. labels[stem] = np.zeros((0, 5))
  131. # 评分
  132. scores = {name: _score_and_pos(dets) for name, dets in labels.items()}
  133. # 去重
  134. for name, dets in labels.items():
  135. if len(dets) > 0:
  136. labels[name] = _fix_duplicates(dets)
  137. # 按分数排序
  138. ranked = sorted(scores.keys(), key=lambda n: scores[n], reverse=True)
  139. # 筛选
  140. selected = []
  141. last_seq = -999
  142. for name in ranked:
  143. if scores[name] < SCORE_THRESH:
  144. continue
  145. dets = labels[name]
  146. if not np.any((dets[:, 0] == 0) | (dets[:, 0] == 1)):
  147. continue
  148. m = re.search(r'_(\d{6})$', name)
  149. seq = int(m.group(1)) if m else 0
  150. if abs(seq - last_seq) < SEQ_GAP or seq == last_seq:
  151. continue
  152. selected.append(name)
  153. last_seq = seq
  154. # 创建输出目录 + 符号链接
  155. out_img = OUTPUT_DIR / 'images' / cam_name
  156. out_lbl = OUTPUT_DIR / 'labels' / cam_name
  157. out_img.mkdir(parents=True, exist_ok=True)
  158. out_lbl.mkdir(parents=True, exist_ok=True)
  159. img_dir = EXTRACTED_IMG_DIR / cam_name
  160. for name in selected:
  161. si = img_dir / f'{name}.jpg'
  162. sl = EXTRACTED_LBL_DIR / cam_name / f'{name}.txt'
  163. di = out_img / f'czzt_{name}.jpg'
  164. dl = out_lbl / f'czzt_{name}.txt'
  165. if si.exists() and not di.exists():
  166. os.symlink(str(si.resolve()), str(di))
  167. if sl.exists() and not dl.exists():
  168. os.symlink(str(sl.resolve()), str(dl))
  169. all_kept[cam_name] = len(selected)
  170. print(f" {cam_name}: {len(selected)} images in {time.time() - t0:.1f}s")
  171. total = sum(all_kept.values())
  172. print(f"\n Step 2 完成: {total} images")
  173. return all_kept
  174. # ============================================================
  175. # Step 3: 注入 ready 标签 (YOLOv5推理)
  176. # ============================================================
  177. def _calc_iou(boxA, boxB):
  178. x1 = max(boxA[0], boxB[0]); y1 = max(boxA[1], boxB[1])
  179. x2 = min(boxA[2], boxB[2]); y2 = min(boxA[3], boxB[3])
  180. inter = max(0, x2-x1) * max(0, y2-y1)
  181. areaA = (boxA[2]-boxA[0]) * (boxA[3]-boxA[1])
  182. areaB = (boxB[2]-boxB[0]) * (boxB[3]-boxB[1])
  183. union = areaA + areaB - inter
  184. return inter / union if union > 0 else 0
  185. def step3_inject_ready(cam_name):
  186. import torch
  187. import cv2
  188. img_dir = OUTPUT_DIR / 'images' / cam_name
  189. lbl_dir = OUTPUT_DIR / 'labels' / cam_name
  190. if not img_dir.exists():
  191. return 0
  192. images = sorted(img_dir.glob('czzt_*.jpg'))
  193. if not images:
  194. return 0
  195. sys.path.insert(0, YOLOV5_DIR)
  196. from models.common import DetectMultiBackend
  197. from utils.general import non_max_suppression, scale_boxes
  198. from utils.torch_utils import select_device
  199. device = select_device('')
  200. model = DetectMultiBackend(MODEL_PATH, device=device)
  201. model.eval()
  202. injected = 0
  203. for img_path in images:
  204. lbl_file = lbl_dir / f'{img_path.stem}.txt'
  205. # 读取已有标签
  206. existing = []
  207. if lbl_file.exists():
  208. with open(lbl_file) as f:
  209. for line in f:
  210. parts = line.strip().split()
  211. if len(parts) >= 5:
  212. existing.append([int(parts[0])] + [float(x) for x in parts[1:]])
  213. # 需要同时有hoist和ear才注入ready
  214. if not (any(r[0] == 0 for r in existing) and any(r[0] == 1 for r in existing)):
  215. continue
  216. # 推理
  217. img_bgr = cv2.imread(str(img_path))
  218. if img_bgr is None:
  219. continue
  220. img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
  221. h0, w0 = img_rgb.shape[:2]
  222. img_resized = cv2.resize(img_rgb, (IMG_WIDTH, IMG_HEIGHT))
  223. img_tensor = img_resized.transpose(2, 0, 1)
  224. img_tensor = np.ascontiguousarray(img_tensor)
  225. img_tensor = torch.from_numpy(img_tensor).to(device).float() / 255.0
  226. if img_tensor.ndimension() == 3:
  227. img_tensor = img_tensor.unsqueeze(0)
  228. pred = model(img_tensor)[0]
  229. pred = non_max_suppression(pred, READY_CONF_THRESH, READY_IOU_THRESH, classes=[2])
  230. if len(pred) == 0 or len(pred[0]) == 0:
  231. continue
  232. dets = pred[0]
  233. dets[:, :4] = scale_boxes(img_tensor.shape[2:], dets[:, :4], (h0, w0)).round()
  234. areas = (dets[:, 2] - dets[:, 0]) * (dets[:, 3] - dets[:, 1])
  235. # 收集ear框用于IoU过滤
  236. ear_boxes = []
  237. for r in existing:
  238. if r[0] == 1:
  239. ex, ey, ew, eh = r[1], r[2], r[3], r[4]
  240. ear_boxes.append([(ex-ew/2)*w0, (ey-eh/2)*h0, (ex+ew/2)*w0, (ey+eh/2)*h0])
  241. # 找最大的不与ear重叠的ready
  242. sorted_idx = torch.argsort(areas, descending=True)
  243. best = None
  244. for idx in sorted_idx:
  245. det = dets[idx]
  246. rbox = [det[0].item(), det[1].item(), det[2].item(), det[3].item()]
  247. if all(_calc_iou(rbox, ebox) <= READY_EAR_IOU_THRESH for ebox in ear_boxes):
  248. best = det
  249. break
  250. if best is None:
  251. continue
  252. xc = (best[0] + best[2]) / 2 / w0
  253. yc = (best[1] + best[3]) / 2 / h0
  254. w = (best[2] - best[0]) / w0
  255. h = (best[3] - best[1]) / h0
  256. with open(lbl_file, 'a') as f:
  257. f.write(f'2 {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}\n')
  258. injected += 1
  259. return injected
  260. def step3_inject():
  261. print("\n" + "=" * 60)
  262. print("Step 3: 注入 ready 标签 (YOLOv5推理)")
  263. print("=" * 60)
  264. cameras = sorted([d.name for d in (OUTPUT_DIR / 'images').iterdir() if d.is_dir()])
  265. total_injected = 0
  266. for cam_name in cameras:
  267. n = step3_inject_ready(cam_name)
  268. total_injected += n
  269. print(f" {cam_name}: injected ready to {n} images")
  270. print(f"\n Step 3 完成: {total_injected} ready labels injected")
  271. # ============================================================
  272. # Step 4: 质量过滤
  273. # ============================================================
  274. def step4_filter():
  275. print("\n" + "=" * 60)
  276. print("Step 4: 质量过滤")
  277. print("=" * 60)
  278. cameras = sorted([d.name for d in (OUTPUT_DIR / 'images').iterdir() if d.is_dir()])
  279. total_keep = 0
  280. for cam_name in cameras:
  281. img_dir = OUTPUT_DIR / 'images' / cam_name
  282. lbl_dir = OUTPUT_DIR / 'labels' / cam_name
  283. if not lbl_dir.exists():
  284. continue
  285. discard_ratio = discard_flat = discard_both = keep = 0
  286. for lbl_file in list(lbl_dir.glob('czzt_*.txt')):
  287. with open(lbl_file) as f:
  288. lines = f.readlines()
  289. hoist_areas, ready_areas, ear_aspects = [], [], []
  290. for line in lines:
  291. parts = line.strip().split()
  292. if len(parts) < 5:
  293. continue
  294. cls = int(parts[0])
  295. w, h = float(parts[3]), float(parts[4])
  296. if cls == 0:
  297. hoist_areas.append(w * h)
  298. elif cls == 2:
  299. ready_areas.append(w * h)
  300. elif cls == 1 and h > 0:
  301. ear_aspects.append(w / h)
  302. fail_ratio = (hoist_areas and ready_areas and
  303. max(ready_areas) / max(hoist_areas) >= READY_HOIST_RATIO_THRESH)
  304. fail_flat = (ear_aspects and all(a >= EAR_FLAT_ASPECT_THRESH for a in ear_aspects))
  305. if fail_ratio and fail_flat:
  306. discard_both += 1
  307. elif fail_ratio:
  308. discard_ratio += 1
  309. elif fail_flat:
  310. discard_flat += 1
  311. else:
  312. keep += 1
  313. continue
  314. img_file = img_dir / (lbl_file.stem + '.jpg')
  315. if img_file.exists():
  316. img_file.unlink()
  317. lbl_file.unlink()
  318. total_keep += keep
  319. print(f" {cam_name}: keep={keep}, discard_ratio={discard_ratio}, discard_flat={discard_flat}, discard_both={discard_both}")
  320. print(f"\n Step 4 完成: {total_keep} images after filtering")
  321. # ============================================================
  322. # Step 5: 重新YOLO推理 (高置信度阈值, 清除重叠框)
  323. # ============================================================
  324. def step5_reinfer():
  325. import torch
  326. import cv2
  327. print("\n" + "=" * 60)
  328. print(f"Step 5: 重新YOLO推理 (conf>={REINFER_CONF}, iou={REINFER_IOU})")
  329. print("=" * 60)
  330. sys.path.insert(0, YOLOV5_DIR)
  331. from models.common import DetectMultiBackend
  332. from utils.general import non_max_suppression, scale_boxes
  333. from utils.torch_utils import select_device
  334. device = select_device('')
  335. model = DetectMultiBackend(MODEL_PATH, device=device)
  336. model.eval()
  337. cameras = sorted([d.name for d in (OUTPUT_DIR / 'images').iterdir() if d.is_dir()])
  338. total_reinferred = 0
  339. for cam_name in cameras:
  340. img_dir = OUTPUT_DIR / 'images' / cam_name
  341. lbl_dir = OUTPUT_DIR / 'labels' / cam_name
  342. if not img_dir.exists():
  343. continue
  344. images = sorted(img_dir.glob('czzt_*.jpg'))
  345. if not images:
  346. continue
  347. reinferred = 0
  348. for img_path in images:
  349. img_bgr = cv2.imread(str(img_path))
  350. if img_bgr is None:
  351. continue
  352. img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
  353. h0, w0 = img_rgb.shape[:2]
  354. img_resized = cv2.resize(img_rgb, (IMG_WIDTH, IMG_HEIGHT))
  355. img_tensor = img_resized.transpose(2, 0, 1)
  356. img_tensor = np.ascontiguousarray(img_tensor)
  357. img_tensor = torch.from_numpy(img_tensor).to(device).float() / 255.0
  358. if img_tensor.ndimension() == 3:
  359. img_tensor = img_tensor.unsqueeze(0)
  360. pred = model(img_tensor)[0]
  361. pred = non_max_suppression(pred, REINFER_CONF, REINFER_IOU)
  362. lbl_file = lbl_dir / f'{img_path.stem}.txt'
  363. if len(pred) == 0 or len(pred[0]) == 0:
  364. lbl_file.write_text('')
  365. reinferred += 1
  366. continue
  367. dets = pred[0]
  368. dets[:, :4] = scale_boxes(img_tensor.shape[2:], dets[:, :4], (h0, w0)).round()
  369. with open(lbl_file, 'w') as f:
  370. for det in dets:
  371. cls_id = int(det[5])
  372. if cls_id not in CLASSES:
  373. continue
  374. xc = (det[0] + det[2]) / 2 / w0
  375. yc = (det[1] + det[3]) / 2 / h0
  376. w = (det[2] - det[0]) / w0
  377. h = (det[3] - det[1]) / h0
  378. f.write(f'{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}\n')
  379. reinferred += 1
  380. total_reinferred += reinferred
  381. print(f" {cam_name}: reinferred {reinferred} images")
  382. print(f"\n Step 5 完成: {total_reinferred} images reinferred")
  383. # ============================================================
  384. # Step 6: 质量排序取top MAX_PER_CAM
  385. # ============================================================
  386. def step6_select_top():
  387. print("\n" + "=" * 60)
  388. print(f"Step 6: 质量排序取top {MAX_PER_CAM}")
  389. print("=" * 60)
  390. cameras = sorted([d.name for d in (OUTPUT_DIR / 'images').iterdir() if d.is_dir()])
  391. for cam_name in cameras:
  392. img_dir = OUTPUT_DIR / 'images' / cam_name
  393. lbl_dir = OUTPUT_DIR / 'labels' / cam_name
  394. if not img_dir.exists():
  395. continue
  396. images = sorted(img_dir.glob('czzt_*.jpg'))
  397. if len(images) <= MAX_PER_CAM:
  398. print(f" {cam_name}: {len(images)} images (<= {MAX_PER_CAM}, no trimming)")
  399. continue
  400. def compute_quality(img_path):
  401. try:
  402. import cv2
  403. img = cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE)
  404. if img is None:
  405. return 0
  406. brightness = np.mean(img) / 255.0
  407. laplacian = cv2.Laplacian(img, cv2.CV_64F)
  408. sharpness = laplacian.var() / 1000.0
  409. return brightness * QUALITY_BRIGHTNESS_WEIGHT + min(sharpness, 1.0) * QUALITY_SHARPNESS_WEIGHT
  410. except Exception:
  411. return 0
  412. quality = {}
  413. with ThreadPoolExecutor(max_workers=16) as pool:
  414. for img_path in images:
  415. quality[img_path.name] = compute_quality(img_path)
  416. ranked = sorted(quality.keys(), key=lambda n: quality[n], reverse=True)
  417. keep_names = set(ranked[:MAX_PER_CAM])
  418. removed = 0
  419. for img_path in images:
  420. if img_path.name not in keep_names:
  421. img_path.unlink()
  422. lbl = lbl_dir / (img_path.stem + '.txt')
  423. if lbl.exists():
  424. lbl.unlink()
  425. removed += 1
  426. print(f" {cam_name}: kept {len(keep_names)}, removed {removed}")
  427. # ============================================================
  428. # Step 7: 生成 CVAT ZIP
  429. # ============================================================
  430. def step7_generate_cvat():
  431. print("\n" + "=" * 60)
  432. print("Step 7: 生成 CVAT ZIP")
  433. print("=" * 60)
  434. CVAT_ZIP_DIR.mkdir(parents=True, exist_ok=True)
  435. cameras = sorted([d.name for d in (OUTPUT_DIR / 'images').iterdir() if d.is_dir()])
  436. for cam_name in cameras:
  437. img_dir = OUTPUT_DIR / 'images' / cam_name
  438. lbl_dir = OUTPUT_DIR / 'labels' / cam_name
  439. img_names = sorted([f.name for f in img_dir.glob('czzt_*.jpg')])
  440. if not img_names:
  441. print(f" {cam_name}: 0 images, skipping")
  442. continue
  443. zip_path = CVAT_ZIP_DIR / f'{cam_name}.zip'
  444. obj_names_str = '\n'.join(CVAT_CLASSES) + '\n'
  445. obj_data = f'classes = {len(CVAT_CLASSES)}\ntrain = data/train.txt\nnames = data/obj.names\nbackup = backup/\n'
  446. train_lines = '\n'.join(f'data/obj_train_data/data/obj_train_data/{n}' for n in img_names) + '\n'
  447. with zipfile.ZipFile(str(zip_path), 'w', zipfile.ZIP_STORED) as zf:
  448. zf.writestr('obj.names', obj_names_str)
  449. zf.writestr('obj.data', obj_data)
  450. zf.writestr('train.txt', train_lines)
  451. for img_name in img_names:
  452. zf.write(str(img_dir / img_name), f'obj_train_data/data/obj_train_data/{img_name}')
  453. stem = img_name[:-4]
  454. lbl_file = lbl_dir / f'{stem}.txt'
  455. if lbl_file.exists():
  456. zf.write(str(lbl_file), f'obj_train_data/data/obj_train_data/{stem}.txt')
  457. print(f" {cam_name}: {len(img_names)} images -> {zip_path.name}")
  458. print(f"\n Step 7 完成: CVAT zips in {CVAT_ZIP_DIR}")
  459. # ============================================================
  460. # 最终统计
  461. # ============================================================
  462. def print_stats():
  463. print("\n" + "=" * 60)
  464. print("最终统计")
  465. print("=" * 60)
  466. cameras = sorted([d.name for d in (OUTPUT_DIR / 'images').iterdir() if d.is_dir()])
  467. total = 0
  468. label_counts = {0: 0, 1: 0, 2: 0}
  469. for cam_name in cameras:
  470. img_dir = OUTPUT_DIR / 'images' / cam_name
  471. lbl_dir = OUTPUT_DIR / 'labels' / cam_name
  472. n_img = len(list(img_dir.glob('czzt_*.jpg'))) if img_dir.exists() else 0
  473. n_lbl = {0: 0, 1: 0, 2: 0}
  474. if lbl_dir.exists():
  475. for lf in lbl_dir.glob('czzt_*.txt'):
  476. with open(lf) as f:
  477. for line in f:
  478. parts = line.strip().split()
  479. if len(parts) >= 5:
  480. c = int(parts[0])
  481. if c in n_lbl:
  482. n_lbl[c] += 1
  483. label_counts[c] += 1
  484. total += n_img
  485. print(f" {cam_name}: {n_img} images, hoist={n_lbl[0]}, ear={n_lbl[1]}, ready={n_lbl[2]}")
  486. print(f"\n 总计: {total} images")
  487. print(f" hoist(0): {label_counts[0]}")
  488. print(f" ear(1): {label_counts[1]}")
  489. print(f" ready(2): {label_counts[2]}")
  490. # ============================================================
  491. # Main
  492. # ============================================================
  493. def main():
  494. skip = set(sys.argv[1:])
  495. if '--skip-extract' not in skip:
  496. step1_extract()
  497. if '--skip-select' not in skip:
  498. step2_select()
  499. if '--skip-inject' not in skip:
  500. step3_inject()
  501. if '--skip-filter' not in skip:
  502. step4_filter()
  503. if '--skip-reinfer' not in skip:
  504. step5_reinfer()
  505. if '--skip-select-top' not in skip:
  506. step6_select_top()
  507. if '--skip-zip' not in skip:
  508. step7_generate_cvat()
  509. print_stats()
  510. if __name__ == '__main__':
  511. main()