step1_crop.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. # -*- coding: utf-8 -*-
  2. """
  3. step1: yolo26s-seg 裁切 + 透视拉直 + 纯几何横屏(dx>dy 保留, dx<dy 逆时针 90°)
  4. 不做任何 OCR 方向判断、不删任何图、不做语种判断。
  5. 输出: cropped_raw/<cls>/<name>__c<conf>.jpg
  6. """
  7. import os, time, cv2, numpy as np
  8. from pathlib import Path
  9. from ultralytics import YOLO
  10. WEIGHTS = "/home/martin/顾工交接/wzj/ultralytics/runs/segment/yolo26s_seg_grading/weights/best.pt"
  11. SRC_DIR = "/home/martin/顾工交接/wzj/data_all/test"
  12. OUT_DIR = "/home/martin/顾工交接/wzj/data_all/cropped_raw"
  13. CONF = 0.5
  14. IMGSZ = 640
  15. DEVICE = 0
  16. CLASS_NAMES = ['PSA', 'BGS', 'BGS-AU', 'BGS-AUTO', 'BGS-ONLY-AUTO',
  17. 'CGC', 'CGC-OLD', 'SGC', 'SGC-AUTO', 'SGC-OLD', 'TAG', 'Other']
  18. def mask_to_corners(mask, min_area=30.0):
  19. m = (mask > 0).astype(np.uint8)
  20. cnts, _ = cv2.findContours(m, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  21. if not cnts: return None
  22. c = max(cnts, key=cv2.contourArea)
  23. if cv2.contourArea(c) < min_area: return None
  24. hull = cv2.convexHull(c)
  25. peri = cv2.arcLength(hull, True)
  26. for eps in (0.01, 0.02, 0.03, 0.04, 0.06, 0.08, 0.10, 0.15):
  27. approx = cv2.approxPolyDP(hull, eps * peri, True)
  28. if len(approx) == 4:
  29. return approx.reshape(4, 2).astype(np.float32)
  30. return cv2.boxPoints(cv2.minAreaRect(hull)).astype(np.float32)
  31. def order_corners(pts):
  32. pts = np.asarray(pts, dtype=np.float32).reshape(4, 2)
  33. r = np.zeros((4, 2), dtype=np.float32)
  34. s = pts.sum(1); d = np.diff(pts, 1).ravel()
  35. r[0]=pts[s.argmin()]; r[2]=pts[s.argmax()]
  36. r[1]=pts[d.argmin()]; r[3]=pts[d.argmax()]
  37. return r
  38. def warp_perspective(img, corners):
  39. o = order_corners(corners)
  40. wt=np.linalg.norm(o[1]-o[0]); wb=np.linalg.norm(o[2]-o[3])
  41. hl=np.linalg.norm(o[3]-o[0]); hr=np.linalg.norm(o[2]-o[1])
  42. W=max(int(round((wt+wb)/2)),1); H=max(int(round((hl+hr)/2)),1)
  43. dst=np.array([[0,0],[W-1,0],[W-1,H-1],[0,H-1]],np.float32)
  44. return cv2.warpPerspective(img, cv2.getPerspectiveTransform(o, dst), (W, H))
  45. def ensure_landscape(img):
  46. """纯几何横屏: dx>dy 保留; dx<dy(竖向)逆时针 90° → 横向"""
  47. h, w = img.shape[:2]
  48. if w < h:
  49. return cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
  50. return img
  51. def main():
  52. Path(OUT_DIR).mkdir(parents=True, exist_ok=True)
  53. for n in CLASS_NAMES:
  54. Path(OUT_DIR, n).mkdir(exist_ok=True)
  55. model = YOLO(WEIGHTS)
  56. img_paths = []
  57. for ext in ("*.jpg", "*.jpeg", "*.png"):
  58. img_paths.extend(Path(SRC_DIR).rglob(ext))
  59. print(f"Found {len(img_paths)} images", flush=True)
  60. t0 = time.time()
  61. total = 0
  62. for img_path in img_paths:
  63. try:
  64. results = model.predict(source=str(img_path), conf=CONF, imgsz=IMGSZ,
  65. device=DEVICE, verbose=False, retina_masks=True)
  66. if not results: continue
  67. res = results[0]
  68. H, W = res.orig_img.shape[:2]
  69. if res.boxes is None or len(res.boxes) == 0: continue
  70. xyxy = res.boxes.xyxy.cpu().numpy()
  71. clss = res.boxes.cls.cpu().numpy().astype(int)
  72. confs = res.boxes.conf.cpu().numpy()
  73. masks = res.masks.data.cpu().numpy() if res.masks is not None else None
  74. best_i = int(np.argmax(confs))
  75. cropped = None
  76. if masks is not None and best_i < len(masks):
  77. m = (masks[best_i] > 0.5).astype(np.uint8) * 255
  78. if m.shape != (H, W):
  79. m = cv2.resize(m, (W, H), interpolation=cv2.INTER_NEAREST)
  80. corners = mask_to_corners(m)
  81. if corners is not None:
  82. warped = warp_perspective(res.orig_img, corners)
  83. if warped is not None and warped.size > 0:
  84. cropped = warped
  85. if cropped is None:
  86. box = xyxy[best_i]
  87. h, w = res.orig_img.shape[:2]
  88. x1, y1 = max(0, int(box[0])), max(0, int(box[1]))
  89. x2, y2 = min(w, int(box[2])), min(h, int(box[3]))
  90. if x2 > x1 and y2 > y1:
  91. cropped = res.orig_img[y1:y2, x1:x2]
  92. if cropped is None: continue
  93. cropped = ensure_landscape(cropped)
  94. cls_id = int(clss[best_i])
  95. cls_name = CLASS_NAMES[cls_id] if 0 <= cls_id < len(CLASS_NAMES) else f"cls{cls_id}"
  96. out_path = Path(OUT_DIR) / cls_name / f"{img_path.stem}__c{confs[best_i]:.2f}.jpg"
  97. cv2.imwrite(str(out_path), cropped, [cv2.IMWRITE_JPEG_QUALITY, 92])
  98. total += 1
  99. if total % 200 == 0:
  100. print(f" {total} done, elapsed={time.time()-t0:.0f}s", flush=True)
  101. except Exception as e:
  102. print(f" err {img_path.name}: {e}", flush=True)
  103. print(f"DONE total={total} time={time.time()-t0:.1f}s", flush=True)
  104. if __name__ == "__main__":
  105. main()