GIT-test/dot_matrix_detector.py
2026-02-21 23:42:50 +08:00

345 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
点阵灯盘检测算法
核心功能:
1. 预处理与离散点聚合
2. 连通域分析与圆拟合
3. 多灯盘分离
4. 后处理过滤
"""
import cv2
import numpy as np
from sklearn.cluster import DBSCAN
class DotMatrixDetector:
def __init__(self):
"""初始化点阵灯盘检测器"""
# 默认HSV参数针对高亮绿色LED
self.h_low = 35 # H通道最小值绿色起始
self.h_high = 85 # H通道最大值绿色结束
self.s_low = 60 # S通道最小值饱和度阈值降低以适应LED
self.s_high = 255 # S通道最大值
self.v_low = 200 # V通道最小值亮度阈值提高以过滤环境光
self.v_high = 255 # V通道最大值
# 形态学参数
self.morph_kernel_size = 20 # 闭运算核大小略大于LED点阵间距
self.morph_open_kernel = 5 # 开运算核大小(去除噪点)
# 几何参数
self.min_disk_area = 500 # 最小灯盘面积(像素)
self.min_led_count = 10 # 最小LED数量
self.circularity_threshold = 0.4 # 圆度阈值(点阵灯盘较宽松)
# 检测模式
self.detection_mode = 'least_squares' # least_squares, min_enclosing, dbscan
# DBSCAN参数
self.dbscan_eps = 30 # 空间距离阈值
self.dbscan_min_samples = 5 # 最小样本数
def set_hsv_range(self, h_low, h_high, s_low, s_high, v_low, v_high):
"""设置HSV颜色范围"""
self.h_low = h_low
self.h_high = h_high
self.s_low = s_low
self.s_high = s_high
self.v_low = v_low
self.v_high = v_high
def set_morph_parameters(self, kernel_size, open_kernel):
"""设置形态学参数"""
self.morph_kernel_size = kernel_size
self.morph_open_kernel = open_kernel
def set_geometry_parameters(self, min_area, min_leds, circularity):
"""设置几何参数"""
self.min_disk_area = min_area
self.min_led_count = min_leds
self.circularity_threshold = circularity
def set_detection_mode(self, mode):
"""设置检测模式"""
self.detection_mode = mode
def detect(self, frame):
"""
检测点阵灯盘
Args:
frame: BGR格式的图像
Returns:
results: 检测结果列表,每个元素包含圆心坐标、半径、置信度等
mask: 二值掩码图像
raw_leds: 原始LED点集
aggregated_mask: 形态学聚合后的掩码
"""
# 1. 预处理:颜色空间转换
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 2. 颜色阈值分割
lower_green = np.array([self.h_low, self.s_low, self.v_low])
upper_green = np.array([self.h_high, self.s_high, self.v_high])
mask = cv2.inRange(hsv, lower_green, upper_green)
# 3. 形态学操作:闭运算(聚合离散点)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (self.morph_kernel_size, self.morph_kernel_size))
aggregated_mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# 4. 形态学操作:开运算(去除噪点)
open_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (self.morph_open_kernel, self.morph_open_kernel))
aggregated_mask = cv2.morphologyEx(aggregated_mask, cv2.MORPH_OPEN, open_kernel)
# 5. 提取原始LED点
raw_leds = self._extract_raw_leds(mask)
# 6. 检测灯盘
results = []
if self.detection_mode == 'dbscan' and len(raw_leds) >= self.min_led_count:
# 使用DBSCAN聚类检测多灯盘
results = self._detect_with_dbscan(raw_leds)
else:
# 使用连通域分析
results = self._detect_with_contours(aggregated_mask, mask)
# 7. 后处理:非极大值抑制
results = self._non_maximum_suppression(results)
return results, mask, raw_leds, aggregated_mask
def _extract_raw_leds(self, mask):
"""提取原始LED点"""
# 查找连通域
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
leds = []
for contour in contours:
# 计算中心点
M = cv2.moments(contour)
if M['m00'] > 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
leds.append((cx, cy))
return np.array(leds)
def _detect_with_contours(self, aggregated_mask, raw_mask):
"""基于连通域的灯盘检测"""
results = []
# 查找轮廓
contours, _ = cv2.findContours(aggregated_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for i, contour in enumerate(contours):
# 计算轮廓面积
area = cv2.contourArea(contour)
if area < self.min_disk_area:
continue
# 在原始掩码中提取该连通域内的LED点
led_points = self._extract_leds_in_contour(contour, raw_mask)
if len(led_points) < self.min_led_count:
continue
# 根据检测模式拟合圆
if self.detection_mode == 'min_enclosing':
center, radius = cv2.minEnclosingCircle(led_points)
cx, cy = center
else: # least_squares
center, radius = self._fit_circle_least_squares(led_points)
if center is None:
continue
cx, cy = center
# 计算圆度
circularity = self._calculate_circularity(contour, radius)
if circularity < self.circularity_threshold:
continue
# 计算置信度
confidence = self._calculate_confidence(len(led_points), circularity, radius)
# 添加结果
results.append({
'id': i,
'center': (cx, cy),
'radius': radius,
'confidence': confidence,
'led_count': len(led_points),
'circularity': circularity,
'contour': contour,
'led_points': led_points
})
return results
def _detect_with_dbscan(self, led_points):
"""使用DBSCAN聚类检测多灯盘"""
results = []
if len(led_points) < self.min_led_count:
return results
# DBSCAN聚类
db = DBSCAN(eps=self.dbscan_eps, min_samples=self.dbscan_min_samples).fit(led_points)
labels = db.labels_
# 处理每个簇
unique_labels = set(labels)
for i, label in enumerate(unique_labels):
if label == -1: # 噪声点
continue
# 提取簇中的点
cluster_points = led_points[labels == label]
if len(cluster_points) < self.min_led_count:
continue
# 拟合圆
center, radius = self._fit_circle_least_squares(cluster_points)
if center is None:
continue
cx, cy = center
# 计算置信度
circularity = self._calculate_cluster_circularity(cluster_points, center, radius)
confidence = self._calculate_confidence(len(cluster_points), circularity, radius)
# 添加结果
results.append({
'id': i,
'center': (cx, cy),
'radius': radius,
'confidence': confidence,
'led_count': len(cluster_points),
'circularity': circularity,
'led_points': cluster_points
})
return results
def _extract_leds_in_contour(self, contour, mask):
"""提取轮廓内的LED点"""
# 创建掩码
contour_mask = np.zeros_like(mask)
cv2.drawContours(contour_mask, [contour], -1, 255, -1)
# 与原始掩码相交
combined_mask = cv2.bitwise_and(mask, contour_mask)
# 提取点
return self._extract_raw_leds(combined_mask)
def _fit_circle_least_squares(self, points):
"""最小二乘拟合圆"""
if len(points) < 3:
return None, None
# 最小二乘拟合
x = points[:, 0]
y = points[:, 1]
# 构建矩阵
A = np.column_stack((x, y, np.ones_like(x)))
b = x**2 + y**2
# 求解线性方程组
try:
x_opt = np.linalg.lstsq(A, b, rcond=None)[0]
cx = x_opt[0] / 2
cy = x_opt[1] / 2
radius = np.sqrt(x_opt[2] + cx**2 + cy**2)
return (cx, cy), radius
except:
return None, None
def _calculate_circularity(self, contour, radius):
"""计算圆度"""
area = cv2.contourArea(contour)
expected_area = np.pi * radius**2
if expected_area == 0:
return 0
return area / expected_area
def _calculate_cluster_circularity(self, points, center, radius):
"""计算聚类点集的圆度"""
if len(points) < 3:
return 0
# 计算点到圆心的距离
distances = np.sqrt((points[:, 0] - center[0])**2 + (points[:, 1] - center[1])**2)
# 计算距离的标准差
std_distance = np.std(distances)
if radius == 0:
return 0
# 圆度:标准差越小,圆度越高
circularity = 1.0 / (1.0 + std_distance / radius)
return circularity
def _calculate_confidence(self, led_count, circularity, radius):
"""计算置信度"""
# LED数量得分
led_score = min(led_count / 50, 1.0)
# 圆度得分
circ_score = min(circularity / 0.7, 1.0)
# 综合得分
confidence = (led_score * 0.6 + circ_score * 0.4) * 100
return confidence
def _non_maximum_suppression(self, results, iou_threshold=0.5):
"""非极大值抑制"""
if not results:
return []
# 按置信度排序
results.sort(key=lambda x: x['confidence'], reverse=True)
filtered_results = []
while results:
current = results.pop(0)
filtered_results.append(current)
# 过滤重叠的结果
results = [r for r in results if not self._is_overlapping(current, r, iou_threshold)]
return filtered_results
def _is_overlapping(self, result1, result2, threshold):
"""判断两个圆是否重叠"""
cx1, cy1 = result1['center']
r1 = result1['radius']
cx2, cy2 = result2['center']
r2 = result2['radius']
# 计算圆心距
distance = np.sqrt((cx1 - cx2)**2 + (cy1 - cy2)**2)
# 计算重叠面积
if distance >= r1 + r2:
return False
if distance <= abs(r1 - r2):
return True
# 计算重叠区域
a = (r1**2 - r2**2 + distance**2) / (2 * distance)
b = distance - a
h = np.sqrt(r1**2 - a**2)
area1 = r1**2 * np.arccos(a / r1) - a * h
area2 = r2**2 * np.arccos(b / r2) - b * h
overlap_area = area1 + area2
# 计算IoU
total_area = np.pi * (r1**2 + r2**2) - overlap_area
iou = overlap_area / total_area
return iou > threshold