贴边 + 过小框过滤
This commit is contained in:
parent
e0a4f85f59
commit
a095f0b776
|
|
@ -50,6 +50,25 @@ def _load_filter_names(config):
|
|||
return {str(n).strip() for n in (names or []) if str(n).strip()}
|
||||
|
||||
|
||||
def _load_edge_filter_params(config):
|
||||
"""贴边/过小框过滤参数;可用 Config 覆盖,否则读 settings。"""
|
||||
def _get(name, default):
|
||||
if hasattr(config, name):
|
||||
return getattr(config, name)
|
||||
try:
|
||||
from dao import settings as S
|
||||
return getattr(S, name, default)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
return {
|
||||
"edge_margin": int(_get("DISH_DETECT_EDGE_MARGIN_PX", 8)),
|
||||
"edge_min_area_ratio": float(_get("DISH_DETECT_EDGE_MIN_AREA_RATIO", 0.02)),
|
||||
"edge_min_side": int(_get("DISH_DETECT_EDGE_MIN_SIDE_PX", 48)),
|
||||
"min_area_ratio": float(_get("DISH_DETECT_MIN_AREA_RATIO", 0.008)),
|
||||
}
|
||||
|
||||
|
||||
class YOLOv11PlateExtractor:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
|
@ -64,10 +83,18 @@ class YOLOv11PlateExtractor:
|
|||
except Exception:
|
||||
pass
|
||||
self.filter_class_names = _load_filter_names(config)
|
||||
self.edge_filter = _load_edge_filter_params(config)
|
||||
print(f"✅ Plate 模型加载成功: {weights}")
|
||||
print(f"[OK] detect classes: {self.config.LABEL_NAMES}")
|
||||
if self.filter_class_names:
|
||||
print(f"[OK] detect filter drop: {sorted(self.filter_class_names)}")
|
||||
ef = self.edge_filter
|
||||
print(
|
||||
f"[OK] edge/small box filter: margin={ef['edge_margin']}px "
|
||||
f"edge_area<{ef['edge_min_area_ratio']:.3f} "
|
||||
f"edge_side<{ef['edge_min_side']} "
|
||||
f"min_area<{ef['min_area_ratio']:.3f}"
|
||||
)
|
||||
|
||||
def predict(self, source):
|
||||
"""source 可为图片路径或 BGR ndarray。"""
|
||||
|
|
@ -92,8 +119,42 @@ class YOLOv11PlateExtractor:
|
|||
return False
|
||||
return name in self.filter_class_names
|
||||
|
||||
def iter_kept_detections(self, results):
|
||||
"""产出保留的检测框:(box, conf, class_name)。手/空盘等已过滤。"""
|
||||
def _should_drop_incomplete_box(self, box, img_w, img_h):
|
||||
"""贴边且过小 / 面积过小 → 视为入画不完整,丢弃。"""
|
||||
try:
|
||||
x1, y1, x2, y2 = [float(v) for v in box[:4]]
|
||||
except Exception:
|
||||
return True
|
||||
bw = max(0.0, x2 - x1)
|
||||
bh = max(0.0, y2 - y1)
|
||||
if bw <= 1 or bh <= 1 or img_w <= 0 or img_h <= 0:
|
||||
return True
|
||||
area_ratio = (bw * bh) / float(img_w * img_h)
|
||||
ef = self.edge_filter or {}
|
||||
min_area = float(ef.get("min_area_ratio") or 0)
|
||||
if min_area > 0 and area_ratio < min_area:
|
||||
return True
|
||||
|
||||
margin = max(0, int(ef.get("edge_margin") or 0))
|
||||
touches = (
|
||||
x1 <= margin
|
||||
or y1 <= margin
|
||||
or x2 >= (img_w - 1 - margin)
|
||||
or y2 >= (img_h - 1 - margin)
|
||||
)
|
||||
if not touches:
|
||||
return False
|
||||
edge_area = float(ef.get("edge_min_area_ratio") or 0)
|
||||
edge_side = int(ef.get("edge_min_side") or 0)
|
||||
# 贴边且「面积小」或「短边过短」→ 多半是只露一角
|
||||
if edge_area > 0 and area_ratio < edge_area:
|
||||
return True
|
||||
if edge_side > 0 and min(bw, bh) < edge_side:
|
||||
return True
|
||||
return False
|
||||
|
||||
def iter_kept_detections(self, results, image_shape=None):
|
||||
"""产出保留的检测框:(box, conf, class_name)。类别/贴边不完整已过滤。"""
|
||||
if results is None or results.boxes is None:
|
||||
return
|
||||
boxes = results.boxes.xyxy.cpu().numpy()
|
||||
|
|
@ -102,20 +163,29 @@ class YOLOv11PlateExtractor:
|
|||
clss = results.boxes.cls.cpu().numpy()
|
||||
else:
|
||||
clss = [0] * len(boxes)
|
||||
dropped = 0
|
||||
img_h = img_w = None
|
||||
if image_shape is not None and len(image_shape) >= 2:
|
||||
img_h, img_w = int(image_shape[0]), int(image_shape[1])
|
||||
dropped_cls = 0
|
||||
dropped_edge = 0
|
||||
for box, conf, cls_id in zip(boxes, confs, clss):
|
||||
cname = self._class_name(results, cls_id)
|
||||
if self._should_drop_class(cname):
|
||||
dropped += 1
|
||||
dropped_cls += 1
|
||||
continue
|
||||
if img_w and img_h and self._should_drop_incomplete_box(box, img_w, img_h):
|
||||
dropped_edge += 1
|
||||
continue
|
||||
yield box, float(conf), cname
|
||||
if dropped:
|
||||
print(f"[detect] filtered {dropped} box(es) in {sorted(self.filter_class_names)}")
|
||||
if dropped_cls:
|
||||
print(f"[detect] filtered {dropped_cls} box(es) by class {sorted(self.filter_class_names)}")
|
||||
if dropped_edge:
|
||||
print(f"[detect] filtered {dropped_edge} incomplete/edge box(es)")
|
||||
|
||||
def crop_plate(self, image, results):
|
||||
cropped_regions = []
|
||||
h, w = image.shape[:2]
|
||||
for box, conf, cname in self.iter_kept_detections(results):
|
||||
for box, conf, cname in self.iter_kept_detections(results, image_shape=image.shape):
|
||||
x1, y1, x2, y2 = map(int, box)
|
||||
x1 = max(0, x1)
|
||||
y1 = max(0, y1)
|
||||
|
|
@ -197,7 +267,7 @@ class YOLOv11PlateExtractor:
|
|||
"""
|
||||
img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
||||
font = self._load_font()
|
||||
kept = list(self.iter_kept_detections(results))
|
||||
kept = list(self.iter_kept_detections(results, image_shape=image.shape))
|
||||
for i, (box, conf, cname) in enumerate(kept):
|
||||
if labels is not None and i < len(labels):
|
||||
text = str(labels[i])
|
||||
|
|
|
|||
|
|
@ -130,6 +130,15 @@ DISH_DETECT_FILTER_NAMES = [
|
|||
x.strip() for x in os.environ.get('DISH_DETECT_FILTER_NAMES', '手,空盘').split(',')
|
||||
if x.strip()
|
||||
]
|
||||
# 贴边不完整框过滤:托盘刚入画只露一角时 YOLO 常会检出,直接丢弃
|
||||
# 框任一边缘距画面边界 ≤ margin 像素,视为贴边
|
||||
DISH_DETECT_EDGE_MARGIN_PX = int(os.environ.get('DISH_DETECT_EDGE_MARGIN_PX', '8'))
|
||||
# 贴边且框面积占整图比例低于此值 → 丢弃(默认 2%)
|
||||
DISH_DETECT_EDGE_MIN_AREA_RATIO = float(os.environ.get('DISH_DETECT_EDGE_MIN_AREA_RATIO', '0.02'))
|
||||
# 贴边且框较短边像素低于此值 → 丢弃
|
||||
DISH_DETECT_EDGE_MIN_SIDE_PX = int(os.environ.get('DISH_DETECT_EDGE_MIN_SIDE_PX', '48'))
|
||||
# 任意框(不论是否贴边)面积占比低于此值也丢弃;0=关闭
|
||||
DISH_DETECT_MIN_AREA_RATIO = float(os.environ.get('DISH_DETECT_MIN_AREA_RATIO', '0.008'))
|
||||
|
||||
# 用本地视频文件夹代替相机实时流(调试/回放,主要给海康入口用)。
|
||||
# 1=读 DISH_VIDEO_SOURCE_DIR,0=读相机。usb_camera_app 固定读 USB 相机,不受此项影响。
|
||||
|
|
|
|||
Loading…
Reference in New Issue