实现基于Yolo的异常聚集算法
实现方法
基于YOLO检测算法实现“异常聚集”检测是一个很实用的应用场景,比如人群过度聚集、车辆拥堵、特定物品(如垃圾、危险物)堆积等。核心思路是:先用YOLO检测出目标个体,再通过后处理算法分析这些个体的空间分布来判断是否发生聚集。
以下是详细的实现步骤和关键点:
核心流程:
-
目标检测 (YOLO):
- 模型选择: 使用预训练好的YOLO模型(如YOLOv5, YOLOv8, YOLO-NAS等)。选择哪个版本取决于你对精度和速度的要求(v8通常是个好平衡)。
- 模型微调 (可选但强烈推荐):
- 如果你的聚集目标比较特殊(例如检测特定种类的昆虫聚集、特定区域的生产物料堆积),或者你的应用场景环境(光照、角度、背景)与COCO等通用数据集差异较大,你需要用自己的数据集微调模型。
- 收集包含目标物体的图像/视频,标注边界框。
- 在预训练模型的基础上进行训练,提高在你特定场景下的检测精度和召回率。
- 推理: 输入待分析的图像或视频帧,运行YOLO模型。
- 输出: 获取检测结果。对于每一帧,你会得到:
- 检测到的每个目标的边界框 (
bbox
:[x_min, y_min, x_max, y_max]
或[center_x, center_y, width, height]
) - 目标的类别 (
class_id
) - 检测的置信度 (
confidence
)
- 检测到的每个目标的边界框 (
-
聚集分析 (后处理):
这是判断“异常聚集”的核心。利用上一步得到的目标位置信息,分析它们的空间分布。常用方法有:- a. 基于密度的分析 (最常用):
- 思路: 判断某个局部区域内目标的数量是否超过设定的阈值。
- 方法:
- 区域密度法:
- 将图像划分成网格(例如10x10的格子)。
- 统计每个网格内检测到的目标数量。
- 如果某个或某些相邻网格内的目标数量之和超过阈值
N_threshold
,则认为该区域发生聚集。 - 优点: 简单直观,计算量相对较小。
- 缺点: 聚集区域的形状可能不规则,网格边界可能切割聚集区;阈值
N_threshold
需要根据网格大小和实际场景仔细调整。
- 基于距离的密度法 (DBSCAN思想):
- 计算所有检测到的目标中心点。
- 对于每个目标点
P
:- 统计在以
P
为中心、半径为R
的圆内(或边长为2R
的正方形内)有多少个其他目标点(包括P
自身)。 - 如果这个数量 >=
MinPts
(例如 5),则认为点P
是一个核心点,它所在的区域是密集的。
- 统计在以
- 所有相互连通的核心点(即可以通过一系列核心点连接起来)及其邻域内的边界点(在
R
内有核心点但自身邻域内点数< MinPts
的点)构成一个簇。 - 如果一个簇包含的点数 >=
ClusterSize_threshold
(例如 10),则认为这是一个异常聚集区域。 - 优点: 能更自然地识别任意形状的聚集区域。
- 缺点: 计算复杂度较高(需要计算点与点之间的距离),参数
R
和MinPts
的选择对结果影响大。
- 区域密度法:
- b. 基于最小包围区域:
- 思路: 计算所有目标点的凸包或最小包围矩形/圆。
- 方法:
- 计算所有目标中心点的凸包或最小包围矩形(
cv2.minAreaRect
)/最小包围圆 (cv2.minEnclosingCircle
)。 - 计算这个凸包的面积或包围矩形的面积/包围圆的面积。
- 计算密度 = 目标数量 / 包围区域面积。
- 如果密度 >
Density_threshold
,则认为发生聚集。
- 计算所有目标中心点的凸包或最小包围矩形(
- 优点: 计算相对简单。
- 缺点: 对离群点非常敏感。一个远离主群体的点会显著增大包围区域面积,降低密度值,可能导致漏检。
- c. 基于最近邻距离:
- 思路: 判断目标之间是否靠得太近。
- 方法:
- 计算每个目标到其最近邻目标的距离 (
d_min
)。 - 统计所有目标的
d_min
。 - 计算
d_min
的平均值或中位数。 - 如果平均值/中位数 <
Distance_threshold
,则认为整体上目标过于靠近,发生聚集。 - 或者,统计
d_min < Distance_threshold
的目标数量占总目标数量的比例,如果比例 >Ratio_threshold
,则认为发生聚集。
- 计算每个目标到其最近邻目标的距离 (
- 优点: 直接反映目标间的接近程度。
- 缺点: 可能无法区分一个大而稀疏的群体和一个非常小但极其密集的群体;阈值设定需谨慎。
- a. 基于密度的分析 (最常用):
-
阈值设定与判定:
- 无论采用哪种聚集分析方法,都需要设定合理的阈值(
N_threshold
,R
/MinPts
/ClusterSize_threshold
,Density_threshold
,Distance_threshold
,Ratio_threshold
)。 - 这些阈值高度依赖于具体的应用场景:
- 目标类型: 人、车、动物、物品的大小和正常间距不同。
- 场景: 室内、室外、开阔地、走廊的拥挤标准不同。
- 相机视角: 俯视、平视、广角镜头会影响目标在图像中的分布和大小。
- 定义“异常”: 什么是“异常聚集”?是5个人挤在1平米内?还是50辆车堵在100米路上?需要业务定义。
- 如何设定: 需要通过大量标注了“正常”和“聚集”状态的样本数据进行分析和实验来确定最佳阈值。可以使用验证集进行调优。
- 无论采用哪种聚集分析方法,都需要设定合理的阈值(
-
结果输出与可视化:
- 报警/提示: 当检测到异常聚集时,触发报警(声音、灯光、日志记录、消息推送等)。
- 可视化:
- 在原始图像/视频帧上绘制检测到的目标边界框。
- 高亮聚集区域: 用不同颜色绘制被判定为聚集的边界框;或在聚集区域(网格、DBSCAN簇、包围区域)绘制半透明覆盖层(如红色遮罩)。
- 显示统计信息(如聚集区域数量、最大聚集人数/车数等)。
优化与注意事项:
- YOLO检测精度是关键: 聚集分析严重依赖YOLO检测的准确性。漏检(False Negative)会低估密度,误检(False Positive)会高估密度。务必确保你的YOLO模型在目标场景下表现良好(高召回率和高精度)。注意小目标和遮挡目标的检测。
- 目标跟踪 (视频流): 对于视频应用,仅仅分析单帧是不够的。短时间内的目标移动可能导致单帧密度瞬时升高(如人群短暂交叉)。结合目标跟踪(如DeepSORT, ByteTrack等)可以:
- 获得更稳定的目标轨迹。
- 计算一段时间内(如3秒)某个区域的平均密度或持续聚集状态,减少瞬时波动的影响。
- 区分进入和离开聚集区的目标。
- 透视校正 (非俯视视角): 如果摄像头不是正俯视,图像中不同位置的目标大小和间距会受到透视畸变影响。远处的目标看起来更密集。考虑进行透视校正或使用相机标定参数将图像坐标映射到真实世界的地面平面坐标,再进行密度或距离计算会更准确。
- 计算效率:
- 聚集分析(尤其是基于距离的方法)的计算开销可能比YOLO推理本身还大,特别是在目标数量很多时(
O(n²)
或O(n log n)
复杂度)。 - 优化距离计算(如使用KD-Tree进行最近邻搜索)。
- 考虑只在YOLO检测到目标数量超过某个基础阈值后才进行复杂的聚集分析。
- 根据实际需求调整分析频率(不是每一帧都分析)。
- 聚集分析(尤其是基于距离的方法)的计算开销可能比YOLO推理本身还大,特别是在目标数量很多时(
- 定义“异常”: 清晰定义什么是“异常聚集”至关重要。这需要结合具体业务场景和安全规范。
- 场景适配: 不同场景(如地铁站台、广场、工厂车间)可能需要不同的聚集分析算法和参数。可能需要为不同区域配置不同的规则。
实现代码:基于YOLO的异常聚集检测方案
完整的解决方案:使用YOLOv8进行目标检测,结合DBSCAN聚类算法实现异常聚集检测。这种方法能有效识别任意形状的聚集区域,是异常聚集检测的最优方法之一。代码如下:
import cv2
import numpy as np
from ultralytics import YOLO
from sklearn.cluster import DBSCAN
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, Circle
from matplotlib.collections import PatchCollectionclass AnomalyAggregationDetector:def __init__(self, model_path='yolov8n.pt', target_class=0):"""初始化异常聚集检测器参数:model_path: YOLO模型路径 (默认为yolov8n)target_class: 要检测的目标类别 (0: person)"""# 加载YOLO模型self.model = YOLO(model_path)self.target_class = target_class# 聚类参数 (需要根据实际场景调整)self.eps = 50 # 邻域半径(像素)self.min_samples = 5 # 形成核心点所需的最小样本数self.cluster_size_threshold = 8 # 判定为聚集的最小簇大小# 可视化参数self.colors = plt.cm.jet(np.linspace(0, 1, 10))[:, :3] * 255def detect_objects(self, image):"""使用YOLO检测图像中的目标参数:image: 输入图像 (numpy数组)返回:检测到的目标边界框列表 [x_min, y_min, x_max, y_max]目标中心点列表 [[x, y]]"""# 使用YOLO进行目标检测results = self.model(image, verbose=False)boxes = []centers = []# 处理检测结果for result in results:for box in result.boxes:# 只处理目标类别的检测结果if int(box.cls) == self.target_class and box.conf > 0.5:# 获取边界框坐标x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())boxes.append([x1, y1, x2, y2])# 计算中心点center_x = (x1 + x2) // 2center_y = (y1 + y2) // 2centers.append([center_x, center_y])return np.array(boxes), np.array(centers)def find_aggregations(self, centers):"""使用DBSCAN聚类算法检测聚集区域参数:centers: 目标中心点列表返回:聚类标签数组 (-1表示噪声点)聚集簇的统计信息"""if len(centers) == 0:return np.array([]), []# 使用DBSCAN进行聚类db = DBSCAN(eps=self.eps, min_samples=self.min_samples).fit(centers)labels = db.labels_# 统计聚类结果clusters = {}for label in set(labels):if label == -1: # 跳过噪声点continue# 获取当前簇的所有点cluster_points = centers[labels == label]# 计算簇的中心cluster_center = cluster_points.mean(axis=0)# 计算簇的半径 (最远点到中心的距离)distances = np.linalg.norm(cluster_points - cluster_center, axis=1)cluster_radius = distances.max()# 计算凸包if len(cluster_points) > 2:hull = cv2.convexHull(cluster_points.astype(np.float32))hull = hull.squeeze()else:hull = cluster_pointsclusters[label] = {'points': cluster_points,'center': cluster_center,'radius': cluster_radius,'size': len(cluster_points),'hull': hull}return labels, clustersdef detect(self, image_path, output_path='output.jpg'):"""执行异常聚集检测参数:image_path: 输入图像路径output_path: 输出图像路径"""# 读取图像image = cv2.imread(image_path)if image is None:print(f"错误: 无法读取图像 {image_path}")return# 检测目标boxes, centers = self.detect_objects(image)# 检测聚集区域labels, clusters = self.find_aggregations(centers)# 可视化结果result_img = self.visualize(image.copy(), boxes, centers, labels, clusters)# 保存结果cv2.imwrite(output_path, result_img)print(f"结果已保存至: {output_path}")# 显示结果plt.figure(figsize=(12, 8))plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB))plt.title('异常聚集检测结果')plt.axis('off')plt.show()return clustersdef visualize(self, image, boxes, centers, labels, clusters):"""可视化检测结果参数:image: 原始图像boxes: 检测框centers: 中心点labels: 聚类标签clusters: 聚类信息返回:可视化后的图像"""# 绘制检测框和中心点for i, box in enumerate(boxes):x1, y1, x2, y2 = boxcolor = (0, 255, 0) # 绿色边界框# 绘制边界框cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)# 绘制中心点cx, cy = centers[i]cv2.circle(image, (cx, cy), 4, (0, 0, 255), -1) # 红色中心点# 绘制聚类结果for label, cluster_info in clusters.items():size = cluster_info['size']center = cluster_info['center']radius = cluster_info['radius']hull = cluster_info['hull']# 跳过小簇if size < self.cluster_size_threshold:continue# 为每个簇选择随机颜色color = tuple(map(int, self.colors[label % len(self.colors)]))# 绘制凸包cv2.polylines(image, [hull.astype(int)], True, color, 2)# 绘制簇中心cv2.circle(image, tuple(map(int, center)), 6, color, -1)# 绘制簇信息text = f"Aggregation: {size} people"cv2.putText(image, text, (int(center[0]) - 60, int(center[1]) - int(radius) - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)# 绘制警告信息warning_text = "WARNING: Abnormal Aggregation!"text_size = cv2.getTextSize(warning_text, cv2.FONT_HERSHEY_SIMPLEX, 1.2, 3)[0]cv2.putText(image, warning_text, ((image.shape[1] - text_size[0]) // 2, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3)return imageif __name__ == "__main__":# 创建检测器实例detector = AnomalyAggregationDetector(model_path='yolov8n.pt', # 可以是 yolov8s/m/l/xtarget_class=0 # 0: person)# 设置参数 (需要根据实际场景调整)detector.eps = 60 # 邻域半径(像素) - 需要根据图像分辨率和目标大小调整detector.min_samples = 4 # 形成核心点所需的最小样本数detector.cluster_size_threshold = 6 # 判定为聚集的最小簇大小# 执行检测image_path = 'crowd.jpg' # 替换为你的图像路径clusters = detector.detect(image_path, output_path='detection_result.jpg')# 打印检测到的聚集区域if clusters:print("\n检测到的聚集区域:")for label, cluster in clusters.items():if cluster['size'] >= detector.cluster_size_threshold:print(f"簇 {label}: {cluster['size']}人 (中心点: {cluster['center']})")
关键功能说明
-
目标检测:
- 使用YOLOv8检测图像中的目标(默认检测人)
- 获取目标边界框和中心点坐标
-
聚集检测:
- 使用DBSCAN聚类算法分析目标的空间分布
- 通过密度分析识别异常聚集区域
- 可调整参数:邻域半径(eps)、最小样本数(min_samples)、聚集阈值(cluster_size_threshold)
-
结果可视化:
- 绘制目标检测框和中心点
- 标记聚集区域的凸包边界
- 显示聚集区域大小和位置信息
- 对异常聚集区域添加警告标识
参数调优指南
参数优化是系统有效工作的关键,需要根据具体场景调整:
-
邻域半径(eps):
- 值太小:会将密集区域分割成多个小簇
- 值太大:会将本应分开的聚集区域合并
- 调整建议:从图像高度的1/20开始尝试
-
最小样本数(min_samples):
- 值太小:会检测到过多小聚集
- 值太大:会忽略较小的聚集
- 调整建议:3-5之间较为合适
-
聚集阈值(cluster_size_threshold):
- 根据业务需求定义"异常聚集"的最小人数
- 例如:安全场景可能需要>5人,疫情控制可能需要>3人
实际应用建议
-
视频流处理:
# 伪代码:视频流处理 cap = cv2.VideoCapture(0) # 摄像头输入 while cap.isOpened():ret, frame = cap.read()boxes, centers = detector.detect_objects(frame)labels, clusters = detector.find_aggregations(centers)output_frame = detector.visualize(frame, boxes, centers, labels, clusters)# 显示或保存结果
-
透视校正(非俯视角度):
# 伪代码:透视校正 def perspective_correction(points, homography_matrix):# 应用单应性矩阵将点转换到俯视平面corrected_points = cv2.perspectiveTransform(points, homography_matrix)return corrected_points
-
多目标类别支持:
# 可同时检测多类目标 detector = AnomalyAggregationDetector(target_class=None) # 检测所有类别
环境要求
pip install ultralytics scikit-learn opencv-python matplotlib numpy
实际应用效果
- 对稀疏分布的人群:不会标记为聚集
- 对紧密聚集的人群:标记为异常聚集区域
- 对多个聚集区域:分别标记不同颜色
- 对边缘分散个体:不包含在聚集区域中