From 0befaa05478d3b3061021d423250119ae98aaac4 Mon Sep 17 00:00:00 2001 From: hula-ovo <1477131602@qq.com> Date: Mon, 13 Apr 2026 14:23:19 +0800 Subject: [PATCH 1/5] Add auto-adjusting detection region functionality --- .../scripts/yolo_vehicle_counter.py | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py index 54c627818..f6e034ec6 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py @@ -14,6 +14,60 @@ OUTPUT_VIDEO_PATH = "../res/sample_res.mp4" # ================================================== +# 检测区域初始值 +current_region = [400, 300, 1250, 500] # [left, top, right, bottom] +region_padding = 50 # 区域扩展的 padding 值 +region_adjust_interval = 10 # 每10帧调整一次区域 + + +def auto_adjust_detection_region(frame, detections, current_region, padding=50): + """自动调整检测区域大小 + + Args: + frame: 当前视频帧 + detections: 检测结果 + current_region: 当前区域 [left, top, right, bottom] + padding: 区域扩展的像素数 + + Returns: + list: 调整后的区域 [left, top, right, bottom] + """ + if len(detections) == 0: + return current_region + + # 获取所有检测到的车辆边界框 + boxes = detections.xyxy + if len(boxes) == 0: + return current_region + + # 计算所有车辆的边界 + min_x = min(boxes[:, 0]) + max_x = max(boxes[:, 2]) + min_y = min(boxes[:, 1]) + max_y = max(boxes[:, 3]) + + # 扩展区域边界 + frame_height, frame_width = frame.shape[:2] + new_left = max(0, int(min_x - padding)) + new_top = max(0, int(min_y - padding)) + new_right = min(frame_width, int(max_x + padding)) + new_bottom = min(frame_height, int(max_y + padding)) + + # 确保区域不会太小 + min_region_width = 200 + min_region_height = 100 + if new_right - new_left < min_region_width: + center_x = (new_left + new_right) // 2 + new_left = max(0, center_x - min_region_width // 2) + new_right = min(frame_width, center_x + min_region_width // 2) + + if new_bottom - new_top < min_region_height: + center_y = (new_top + new_bottom) // 2 + new_top = max(0, center_y - min_region_height // 2) + new_bottom = min(frame_height, center_y + min_region_height // 2) + + return [new_left, new_top, new_right, new_bottom] + def main(model_path=None, input_video_path=None, output_video_path=None, ground_truth_file=None): """主函数 - 运行车辆计数 @@ -194,7 +248,7 @@ def draw_tracks_and_count(frame, detections, total_counts, limits, w=1920): total_counts.append(track_id) sv.draw_line(frame, start=sv.Point(x=limits[0], y=limits[1]), end=sv.Point(x=limits[2], y=limits[3]), color=sv.Color.ROBOFLOW, thickness=4) - draw_overlay(frame, (400, 300), (1250, 500), alpha=0.25, color=(10, 255, 50)) + draw_overlay(frame, (current_region[0], current_region[1]), (current_region[2], current_region[3]), alpha=0.25, color=(10, 255, 50)) # 显示车辆计数和精度信息 sv.draw_text(frame, f"COUNTS: {len(total_counts)}", sv.Point(x=110, y=30), sv.Color.ROBOFLOW, 1.25, @@ -251,11 +305,16 @@ def draw_tracks_and_count(frame, detections, total_counts, limits, w=1920): total_detections += len(detections) correct_detections += int(len(detections) * avg_confidence) + # 每N帧自动调整检测区域 + if frame_count % region_adjust_interval == 0: + global current_region + current_region = auto_adjust_detection_region(frame, detections, current_region, region_padding) + if detections.tracker_id is not None: # 绘制计数线并处理车辆轨迹 sv.draw_line(frame, start=sv.Point(x=limits[0], y=limits[1]), end=sv.Point(x=limits[2], y=limits[3]), color=sv.Color.RED, thickness=4) - draw_overlay(frame, (400, 300), (1250, 500), alpha=0.2) + draw_overlay(frame, (current_region[0], current_region[1]), (current_region[2], current_region[3]), alpha=0.2) draw_tracks_and_count(frame, detections, total_counts, limits, w) # 写入帧到输出视频 From fc9c38737fb740fd7a259761617c4a2cb499bc9e Mon Sep 17 00:00:00 2001 From: hula-ovo <1477131602@qq.com> Date: Wed, 15 Apr 2026 19:00:21 +0800 Subject: [PATCH 2/5] Update detection region functionality --- .../scripts/yolo_vehicle_counter.py | 230 ++++++++++++++++-- 1 file changed, 204 insertions(+), 26 deletions(-) diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py index f6e034ec6..48c69625e 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py @@ -111,8 +111,22 @@ def main(model_path=None, input_video_path=None, output_video_path=None, ground_ selected_classes = [cls_id for cls_id, class_name in model.names.items() if class_name in vehicle_classes] # 初始化计数器 - limits = [400, 400, 1250, 400] # 计数线位置:起点(x1, y)到终点(x2, y) - total_counts, crossed_ids = [], set() # 总计数和已计数车辆ID集合 + # 基础计数区域 [left, top, right, bottom] + counting_region = [400, 350, 1250, 450] # 初始计数区域 + total_counts = [] # 总计数 + # 车辆状态跟踪:{track_id: {'in_region': bool, 'entry_time': timestamp}} + vehicle_states = {} + # 区域调整参数 + region_adjust_interval = 3 # 每3帧调整一次区域(提高响应速度) + region_padding = 100 # 区域扩展的padding值(增大扩展范围) + smooth_factor = 0.6 # 平滑因子(增大响应速度) + + # 精度优化参数 + confidence_threshold = 0.6 # 置信度阈值(平衡精确率和召回率) + min_detection_frames = 3 # 最小检测帧数(连续检测4帧才认为有效) + iou_threshold = 0.25 # IOU阈值(适中的NMS严格度) + min_track_length = 4 # 最小轨迹长度(轨迹至少6个点才计数) + max_speed_threshold = 150 # 最大速度阈值(像素/帧),过滤不合理的跳跃 # 精度衡量配置 ground_truth_file = "../dataset/ground_truth/ground_truth.txt" # 可以设置为包含真实车辆总数的文件路径 @@ -176,23 +190,183 @@ def draw_overlay(frame, pt1, pt2, alpha=0.25, color=(51, 68, 255), filled=True): cv.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame) - def count_vehicles(track_id, cx, cy, limits, crossed_ids): - """统计穿过计数线的车辆 + def count_vehicles_by_region(track_id, cx, cy, region, vehicle_states, total_counts): + """基于区域统计车辆 - 改进版:使用轨迹方向判断 + 稳定性检查 Args: track_id: 车辆追踪ID cx, cy: 车辆中心点坐标 - limits: 计数线位置 - crossed_ids: 已计数车辆ID集合 + region: 计数区域 [left, top, right, bottom] + vehicle_states: 车辆状态字典 + total_counts: 总计数列表 Returns: bool: 是否计数成功 """ - if limits[0] < cx < limits[2] and limits[1] - 10 < cy < limits[1] + 10 and track_id not in crossed_ids: - crossed_ids.add(track_id) - return True + left, top, right, bottom = region + region_center_y = (top + bottom) / 2 + is_in_region = left < cx < right and top < cy < bottom + + # 初始化车辆状态 + if track_id not in vehicle_states: + vehicle_states[track_id] = { + 'in_region': is_in_region, + 'entry_time': frame_count, + 'positions': [(cx, cy)], # 记录位置历史 + 'counted': False, + 'detection_count': 1, # 连续检测计数 + 'last_seen': frame_count # 上次检测到的帧 + } + return False + + # 更新检测状态 + vehicle_states[track_id]['detection_count'] += 1 + vehicle_states[track_id]['last_seen'] = frame_count + + # 更新位置历史 + vehicle_states[track_id]['positions'].append((cx, cy)) + if len(vehicle_states[track_id]['positions']) > 10: # 只保留最近10个位置 + vehicle_states[track_id]['positions'].pop(0) + + # 如果已经计数过,不再计数 + if vehicle_states[track_id]['counted']: + return False + + # 稳定性检查:确保车辆被连续检测到足够帧数 + if vehicle_states[track_id]['detection_count'] < min_detection_frames: + return False + + # 轨迹长度检查:确保轨迹足够长 + positions = vehicle_states[track_id]['positions'] + if len(positions) < min_track_length: + return False + + # 速度一致性检查:过滤不合理的跳跃 + if len(positions) >= 2: + speeds = [] + for i in range(1, len(positions)): + dx = positions[i][0] - positions[i-1][0] + dy = positions[i][1] - positions[i-1][1] + speed = np.sqrt(dx**2 + dy**2) + speeds.append(speed) + + if speeds: + avg_speed = np.mean(speeds) + max_speed = np.max(speeds) + # 如果平均速度或最大速度超过阈值,认为是不合理轨迹 + if avg_speed > max_speed_threshold or max_speed > max_speed_threshold * 2: + return False + + # 检测车辆是否穿过区域中心线(从下到上或从上到下) + if len(positions) >= 3: + # 计算垂直方向移动 + y_positions = [p[1] for p in positions] + + # 检测是否穿过区域中心线 + prev_y = y_positions[-2] + curr_y = y_positions[-1] + + # 从下到上穿过中心线,或从上到下穿过中心线 + if (prev_y > region_center_y and curr_y <= region_center_y) or \ + (prev_y < region_center_y and curr_y >= region_center_y): + # 确保车辆确实在区域内或附近 + if is_in_region or vehicle_states[track_id]['in_region']: + total_counts.append(track_id) + vehicle_states[track_id]['counted'] = True + return True + + # 更新区域状态 + vehicle_states[track_id]['in_region'] = is_in_region + if is_in_region and not vehicle_states[track_id]['in_region']: + vehicle_states[track_id]['entry_time'] = frame_count + return False + def filter_overlapping_detections(detections, iou_threshold=0.3): + """使用NMS过滤重叠的检测框 + + Args: + detections: 检测结果 + iou_threshold: IOU阈值 + + Returns: + Detections: 过滤后的检测结果 + """ + if len(detections) == 0: + return detections + + # 获取边界框和置信度 + boxes = detections.xyxy + scores = detections.confidence + + # 使用OpenCV的NMS + indices = cv.dnn.NMSBoxes( + boxes.tolist(), + scores.tolist(), + score_threshold=confidence_threshold, + nms_threshold=iou_threshold + ) + + if len(indices) > 0: + indices = indices.flatten() + return detections[indices] + return detections + + def adjust_counting_region(frame, detections, current_region, padding=50): + """动态调整计数区域 + + Args: + frame: 当前视频帧 + detections: 检测结果 + current_region: 当前计数区域 [left, top, right, bottom] + padding: 区域扩展的像素数 + + Returns: + list: 调整后的区域 [left, top, right, bottom] + """ + if len(detections) == 0: + return current_region + + # 获取所有检测到的车辆边界框 + boxes = detections.xyxy + if len(boxes) == 0: + return current_region + + # 计算所有车辆的边界 + min_x = min(boxes[:, 0]) + max_x = max(boxes[:, 2]) + min_y = min(boxes[:, 1]) + max_y = max(boxes[:, 3]) + + # 扩展区域边界 + frame_height, frame_width = frame.shape[:2] + new_left = max(0, int(min_x - padding)) + new_top = max(0, int(min_y - padding)) + new_right = min(frame_width, int(max_x + padding)) + new_bottom = min(frame_height, int(max_y + padding)) + + # 确保区域不会太小 + min_region_width = 200 + min_region_height = 100 + if new_right - new_left < min_region_width: + center_x = (new_left + new_right) // 2 + new_left = max(0, center_x - min_region_width // 2) + new_right = min(frame_width, center_x + min_region_width // 2) + + if new_bottom - new_top < min_region_height: + center_y = (new_top + new_bottom) // 2 + new_top = max(0, center_y - min_region_height // 2) + new_bottom = min(frame_height, center_y + min_region_height // 2) + + # 平滑过渡到新区域(使用外部定义的平滑因子) + left, top, right, bottom = current_region + new_left = int(left * (1 - smooth_factor) + new_left * smooth_factor) + new_top = int(top * (1 - smooth_factor) + new_top * smooth_factor) + new_right = int(right * (1 - smooth_factor) + new_right * smooth_factor) + new_bottom = int(bottom * (1 - smooth_factor) + new_bottom * smooth_factor) + + return [new_left, new_top, new_right, new_bottom] + def calculate_accuracy_metrics(ground_truth_total, detected_count): """计算精度指标 @@ -212,18 +386,23 @@ def calculate_accuracy_metrics(ground_truth_total, detected_count): accuracy = 1.0 - error_rate return {'accuracy': accuracy, 'precision': accuracy, 'recall': accuracy, 'f1_score': accuracy} - def draw_tracks_and_count(frame, detections, total_counts, limits, w=1920): + def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_states, w=1920): """绘制轨迹并统计车辆 Args: frame: 输入帧 detections: 检测结果 total_counts: 总计数列表 - limits: 计数线位置 + region: 计数区域 + vehicle_states: 车辆状态字典 w: 视频宽度 """ - # 按车辆类别和检测置信度过滤 - detections = detections[(np.isin(detections.class_id, selected_classes)) & (detections.confidence > 0.5)] + # 按车辆类别和检测置信度过滤(使用更高的阈值) + detections = detections[(np.isin(detections.class_id, selected_classes)) & (detections.confidence > confidence_threshold)] + + # 过滤重叠检测(NMS) + if len(detections) > 0: + detections = filter_overlapping_detections(detections, iou_threshold) # 为每个检测框生成标签 labels = [f"#{track_id} {class_names[cls_id]}" for track_id, cls_id in @@ -244,11 +423,14 @@ def draw_tracks_and_count(frame, detections, total_counts, limits, w=1920): cv.circle(frame, (cx, cy), 4, (0, 255, 255), cv.FILLED) # 绘制车辆中心点 - if count_vehicles(track_id, cx, cy, limits, crossed_ids): - total_counts.append(track_id) - sv.draw_line(frame, start=sv.Point(x=limits[0], y=limits[1]), end=sv.Point(x=limits[2], y=limits[3]), - color=sv.Color.ROBOFLOW, thickness=4) - draw_overlay(frame, (current_region[0], current_region[1]), (current_region[2], current_region[3]), alpha=0.25, color=(10, 255, 50)) + if count_vehicles_by_region(track_id, cx, cy, region, vehicle_states, total_counts): + # 计数成功时高亮显示计数区域 + draw_overlay(frame, (region[0], region[1]), (region[2], region[3]), alpha=0.25, color=(10, 255, 50)) + + # 绘制计数区域 + cv.rectangle(frame, (region[0], region[1]), (region[2], region[3]), (0, 255, 0), 2) + cv.putText(frame, "Counting Region", (region[0], region[1]-10), + cv.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # 显示车辆计数和精度信息 sv.draw_text(frame, f"COUNTS: {len(total_counts)}", sv.Point(x=110, y=30), sv.Color.ROBOFLOW, 1.25, @@ -305,17 +487,13 @@ def draw_tracks_and_count(frame, detections, total_counts, limits, w=1920): total_detections += len(detections) correct_detections += int(len(detections) * avg_confidence) - # 每N帧自动调整检测区域 + # 每N帧自动调整计数区域 if frame_count % region_adjust_interval == 0: - global current_region - current_region = auto_adjust_detection_region(frame, detections, current_region, region_padding) + counting_region = adjust_counting_region(frame, detections, counting_region, region_padding) if detections.tracker_id is not None: - # 绘制计数线并处理车辆轨迹 - sv.draw_line(frame, start=sv.Point(x=limits[0], y=limits[1]), end=sv.Point(x=limits[2], y=limits[3]), - color=sv.Color.RED, thickness=4) - draw_overlay(frame, (current_region[0], current_region[1]), (current_region[2], current_region[3]), alpha=0.2) - draw_tracks_and_count(frame, detections, total_counts, limits, w) + # 处理车辆轨迹和计数 + draw_tracks_and_count(frame, detections, total_counts, counting_region, vehicle_states, w) # 写入帧到输出视频 out.write(frame) From daf5c687f0994a4073137bfb8609a853ef9027ef Mon Sep 17 00:00:00 2001 From: hula-ovo <1477131602@qq.com> Date: Thu, 16 Apr 2026 20:17:50 +0800 Subject: [PATCH 3/5] Add vehicle speed calculation functionality --- .../scripts/yolo_vehicle_counter.py | 120 +++++++++++++++++- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py index 48c69625e..8cb8ca8ef 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py @@ -127,6 +127,14 @@ def main(model_path=None, input_video_path=None, output_video_path=None, ground_ iou_threshold = 0.25 # IOU阈值(适中的NMS严格度) min_track_length = 4 # 最小轨迹长度(轨迹至少6个点才计数) max_speed_threshold = 150 # 最大速度阈值(像素/帧),过滤不合理的跳跃 + + # 速度计算参数 + speed_history = {} # 存储车辆速度历史 {track_id: [speed1, speed2, ...]} + max_speed_history = 10 # 每个车辆保留的最大速度历史记录 + pixel_to_meter_ratio = 0.02 # 像素到米的转换比例(根据实际场景调整,更符合真实道路场景) + speed_units = 'km/h' # 速度单位:'km/h' 或 'm/s' + speed_display_threshold = 10 # 最小显示速度阈值(避免显示低速噪声) + speed_smoothing_factor = 0.7 # 速度平滑因子,使速度变化更自然 # 精度衡量配置 ground_truth_file = "../dataset/ground_truth/ground_truth.txt" # 可以设置为包含真实车辆总数的文件路径 @@ -190,6 +198,76 @@ def draw_overlay(frame, pt1, pt2, alpha=0.25, color=(51, 68, 255), filled=True): cv.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame) + def calculate_vehicle_speed(track_id, cx, cy, vehicle_states, speed_history, fps): + """计算车辆速度 + + Args: + track_id: 车辆追踪ID + cx, cy: 车辆中心点坐标 + vehicle_states: 车辆状态字典 + speed_history: 速度历史字典 + fps: 视频帧率 + + Returns: + float: 车辆速度(像素/秒) + """ + if track_id not in vehicle_states or len(vehicle_states[track_id]['positions']) < 2: + return 0.0 + + # 获取最近的两个位置 + positions = vehicle_states[track_id]['positions'] + prev_pos = positions[-2] + curr_pos = (cx, cy) + + # 计算距离(像素) + dx = curr_pos[0] - prev_pos[0] + dy = curr_pos[1] - prev_pos[1] + distance_pixels = np.sqrt(dx**2 + dy**2) + + # 计算时间差(秒) + time_diff = 1.0 / fps + + # 计算速度(像素/秒) + speed_pixels_per_second = distance_pixels / time_diff + + # 存储速度历史 + if track_id not in speed_history: + speed_history[track_id] = [] + + # 应用速度平滑 + if speed_history[track_id]: + # 使用指数移动平均进行平滑 + last_speed = speed_history[track_id][-1] + smoothed_speed = last_speed * speed_smoothing_factor + speed_pixels_per_second * (1 - speed_smoothing_factor) + speed_history[track_id].append(smoothed_speed) + else: + speed_history[track_id].append(speed_pixels_per_second) + + if len(speed_history[track_id]) > max_speed_history: + speed_history[track_id].pop(0) + + # 返回平均速度 + return np.mean(speed_history[track_id]) if speed_history[track_id] else 0.0 + + def convert_pixel_speed_to_real(speed_pixels_per_second, pixel_to_meter_ratio, units='km/h'): + """将像素速度转换为真实速度 + + Args: + speed_pixels_per_second: 像素/秒 + pixel_to_meter_ratio: 像素到米的转换比例 + units: 速度单位 ('km/h' 或 'm/s') + + Returns: + float: 真实速度 + """ + # 转换为米/秒 + speed_mps = speed_pixels_per_second * pixel_to_meter_ratio + + # 转换为指定单位 + if units == 'km/h': + return speed_mps * 3.6 # 米/秒 -> 公里/小时 + return speed_mps # 米/秒 + def count_vehicles_by_region(track_id, cx, cy, region, vehicle_states, total_counts): """基于区域统计车辆 - 改进版:使用轨迹方向判断 + 稳定性检查 @@ -386,7 +464,7 @@ def calculate_accuracy_metrics(ground_truth_total, detected_count): accuracy = 1.0 - error_rate return {'accuracy': accuracy, 'precision': accuracy, 'recall': accuracy, 'f1_score': accuracy} - def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_states, w=1920): + def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_states, speed_history, w=1920): """绘制轨迹并统计车辆 Args: @@ -395,6 +473,7 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state total_counts: 总计数列表 region: 计数区域 vehicle_states: 车辆状态字典 + speed_history: 速度历史字典 w: 视频宽度 """ # 按车辆类别和检测置信度过滤(使用更高的阈值) @@ -404,9 +483,23 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state if len(detections) > 0: detections = filter_overlapping_detections(detections, iou_threshold) - # 为每个检测框生成标签 - labels = [f"#{track_id} {class_names[cls_id]}" for track_id, cls_id in - zip(detections.tracker_id, detections.class_id)] + # 为每个检测框生成标签(包含速度信息) + labels = [] + for track_id, cls_id in zip(detections.tracker_id, detections.class_id): + # 计算车辆速度 + center_point = detections.get_anchors_coordinates(anchor=sv.Position.CENTER)[list(detections.tracker_id).index(track_id)] + cx, cy = map(int, center_point) + + # 计算速度 + pixel_speed = calculate_vehicle_speed(track_id, cx, cy, vehicle_states, speed_history, fps) + real_speed = convert_pixel_speed_to_real(pixel_speed, pixel_to_meter_ratio, speed_units) + + # 生成标签 + if real_speed >= speed_display_threshold: + label = f"#{track_id} {class_names[cls_id]} {real_speed:.1f} {speed_units}" + else: + label = f"#{track_id} {class_names[cls_id]}" + labels.append(label) # 绘制边界框、标签和轨迹 box_annotator.annotate(frame, detections=detections) @@ -441,6 +534,23 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state accuracy_text = f"ACC: {accuracy_metrics['accuracy']:.2%} | F1: {accuracy_metrics['f1_score']:.2f}" sv.draw_text(frame, accuracy_text, sv.Point(x=w-400, y=30), sv.Color.GREEN, 1.0, 1, background_color=sv.Color.WHITE) + + # 显示速度统计信息 + if speed_history: + all_speeds = [] + for speeds in speed_history.values(): + if speeds: + all_speeds.extend(speeds) + + if all_speeds: + avg_speed_pixel = np.mean(all_speeds) + avg_speed_real = convert_pixel_speed_to_real(avg_speed_pixel, pixel_to_meter_ratio, speed_units) + max_speed_pixel = np.max(all_speeds) + max_speed_real = convert_pixel_speed_to_real(max_speed_pixel, pixel_to_meter_ratio, speed_units) + + speed_text = f"AVG: {avg_speed_real:.1f} {speed_units} | MAX: {max_speed_real:.1f} {speed_units}" + sv.draw_text(frame, speed_text, sv.Point(x=w//2 - 150, y=30), sv.Color.BLUE, 1.0, + 1, background_color=sv.Color.WHITE) @@ -493,7 +603,7 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state if detections.tracker_id is not None: # 处理车辆轨迹和计数 - draw_tracks_and_count(frame, detections, total_counts, counting_region, vehicle_states, w) + draw_tracks_and_count(frame, detections, total_counts, counting_region, vehicle_states, speed_history, w) # 写入帧到输出视频 out.write(frame) From f18467ec7f88b6a2e34467acddeab6d7967d3dd1 Mon Sep 17 00:00:00 2001 From: hula-ovo <1477131602@qq.com> Date: Thu, 16 Apr 2026 22:01:21 +0800 Subject: [PATCH 4/5] Add FPS display and abnormal vehicle detection --- .../scripts/yolo_vehicle_counter.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py index 8cb8ca8ef..4a0d5fe19 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py @@ -4,6 +4,7 @@ import supervision as sv import os import json +import time # ==================== 配置路径 ==================== # 模型文件路径 @@ -567,8 +568,17 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state detection_accuracies = [] # 存储每帧的检测精度 total_detections = 0 # 总检测数 correct_detections = 0 # 正确检测数 + + # FPS计算相关变量 + start_time = time.time() + fps_history = [] # 存储FPS历史 + frame_time_history = [] # 存储帧时间历史 + fps_update_interval = 10 # 每10帧更新一次FPS while cap.isOpened(): + # 记录帧开始时间 + frame_start_time = time.time() + ret, frame = cap.read() if not ret: break @@ -605,6 +615,43 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state # 处理车辆轨迹和计数 draw_tracks_and_count(frame, detections, total_counts, counting_region, vehicle_states, speed_history, w) + # 计算帧时间和FPS + frame_end_time = time.time() + frame_time = (frame_end_time - frame_start_time) * 1000 # 转换为毫秒 + current_fps = 1000 / frame_time if frame_time > 0 else 0 + + # 存储历史数据 + fps_history.append(current_fps) + frame_time_history.append(frame_time) + + # 只保留最近30帧的数据 + if len(fps_history) > 30: + fps_history.pop(0) + if len(frame_time_history) > 30: + frame_time_history.pop(0) + + # 计算平均FPS和帧时间 + avg_fps = np.mean(fps_history) if fps_history else 0 + avg_frame_time = np.mean(frame_time_history) if frame_time_history else 0 + + # 计算状态色标 + if avg_fps > 60: + fps_color = (0, 255, 0) # 绿色 + elif avg_fps > 30: + fps_color = (0, 255, 255) # 黄色 + else: + fps_color = (0, 0, 255) # 红色 + + # 显示FPS、帧时间和状态色标 + fps_text = f"FPS: {avg_fps:.1f}" + frame_time_text = f"Frame Time: {avg_frame_time:.1f}ms" + + # 绘制FPS信息面板 + cv.rectangle(frame, (w - 200, 60), (w - 10, 120), (255, 255, 255), cv.FILLED) + cv.rectangle(frame, (w - 200, 60), (w - 10, 120), fps_color, 2) + cv.putText(frame, fps_text, (w - 190, 90), cv.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) + cv.putText(frame, frame_time_text, (w - 190, 115), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1) + # 写入帧到输出视频 out.write(frame) # 显示当前帧 From a4d28cab348b462884ad2021588a8ca948194754 Mon Sep 17 00:00:00 2001 From: hula-ovo <1477131602@qq.com> Date: Fri, 17 Apr 2026 22:38:33 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=94=AE?= =?UTF-8?q?=E7=9B=98=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=89=A9=E5=B1=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 scripts/keyboard_handler.py 模块,提供统一的键盘事件处理 - 支持按 s 键保存当前帧截图 - 支持按 q 键快速退出程序 - 暂停时按空格键逐帧播放 - 集成到所有车辆计数脚本中 --- .../scripts/keyboard_handler.py | 54 +++++++++++++++++++ .../scripts/yolo_vehicle_counter.py | 5 +- .../scripts/yolo_vehicle_counter_carla.py | 8 +-- .../scripts/yolo_vehicle_counter_improved.py | 5 +- .../scripts/yolo_vehicle_counter_region.py | 8 +-- .../scripts/yolo_vehicle_counter_updown.py | 5 +- 6 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 src/yolo11n_vehicle_counter/scripts/keyboard_handler.py diff --git a/src/yolo11n_vehicle_counter/scripts/keyboard_handler.py b/src/yolo11n_vehicle_counter/scripts/keyboard_handler.py new file mode 100644 index 000000000..eb0e75477 --- /dev/null +++ b/src/yolo11n_vehicle_counter/scripts/keyboard_handler.py @@ -0,0 +1,54 @@ +import cv2 as cv +import os + +def handle_keyboard_events(key, frame, frame_count, cap, out, window_name): + """处理键盘事件 + + Args: + key: 按下的键 + frame: 当前帧 + frame_count: 当前帧计数 + cap: 视频捕获对象 + out: 视频输出对象 + window_name: 窗口名称 + + Returns: + bool: 是否继续运行 + """ + if key == ord('p'): # 按'p'键暂停 + # 进入暂停状态 + paused = True + while paused: + pause_key = cv.waitKey(0) & 0xff + if pause_key == ord('p'): # 再次按'p'键继续 + paused = False + elif pause_key == ord(' '): # 按空格键逐帧播放 + # 读取下一帧 + ret, frame = cap.read() + if not ret: + paused = False + break + frame_count += 1 + # 显示当前帧 + cv.imshow(window_name, frame) + # 写入帧到输出视频 + out.write(frame) + elif pause_key == ord('q'): # 按'q'键退出程序 + # 释放资源 + cap.release() + out.release() + cv.destroyAllWindows() + return False + elif key == ord('s'): # 按's'键保存当前帧截图 + # 创建截图目录(如果不存在) + screenshot_dir = "screenshots" + if not os.path.exists(screenshot_dir): + os.makedirs(screenshot_dir) + # 保存截图 + screenshot_path = os.path.join(screenshot_dir, f"frame_{frame_count}.jpg") + cv.imwrite(screenshot_path, frame) + print(f"✅ 截图已保存: {screenshot_path}") + elif key == ord('q'): # 按'q'键退出程序 + return False + + return True diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py index 4a0d5fe19..b5cf6e26c 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter.py @@ -5,6 +5,7 @@ import os import json import time +from keyboard_handler import handle_keyboard_events # ==================== 配置路径 ==================== # 模型文件路径 @@ -657,7 +658,9 @@ def draw_tracks_and_count(frame, detections, total_counts, region, vehicle_state # 显示当前帧 cv.imshow("Camera", frame) - if cv.waitKey(1) & 0xff == ord('p'): # 按'p'键暂停 + # 键盘事件处理 + key = cv.waitKey(1) & 0xff + if not handle_keyboard_events(key, frame, frame_count, cap, out, "Camera"): break # 计算整体精度 diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_carla.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_carla.py index d45c911f0..48f7c0e55 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_carla.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_carla.py @@ -2,6 +2,7 @@ from ultralytics import YOLO import numpy as np import supervision as sv +from keyboard_handler import handle_keyboard_events # ==================== 配置路径 ==================== # 模型文件路径 @@ -190,9 +191,10 @@ def draw_tracks_and_count(frame, detections, total_counts, limits): # 显示当前帧 cv.imshow("YOLO11n Vehicle Counter - CARLA", frame) - if cv.waitKey(1) & 0xff == ord('p'): # 按'p'键暂停 - print("用户暂停,按任意键继续...") - cv.waitKey(0) + # 键盘事件处理 + key = cv.waitKey(1) & 0xff + if not handle_keyboard_events(key, frame, frame_count, cap, out, "YOLO11n Vehicle Counter - CARLA"): + break # 释放资源 cap.release() diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_improved.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_improved.py index de6bbe3da..cc1023942 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_improved.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_improved.py @@ -2,6 +2,7 @@ from ultralytics import YOLO import numpy as np import supervision as sv +from keyboard_handler import handle_keyboard_events # ==================== 配置路径 ==================== # 模型文件路径 @@ -166,7 +167,9 @@ def draw_tracks_and_count(frame, detections, total_counts, limits): # 显示当前帧 cv.imshow("Camera", frame) - if cv.waitKey(1) & 0xff == ord('p'): # 按'p'键暂停 + # 键盘事件处理 + key = cv.waitKey(1) & 0xff + if not handle_keyboard_events(key, frame, len(total_counts), cap, out, "Camera"): break # 释放资源 diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_region.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_region.py index 610ae2af1..40ba977d3 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_region.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_region.py @@ -14,6 +14,7 @@ from ultralytics import YOLO import numpy as np import supervision as sv +from keyboard_handler import handle_keyboard_events # ==================== 配置路径 ==================== # 模型文件路径 @@ -197,9 +198,10 @@ def annotate_frame(frame, detections): # 显示当前帧 cv.imshow("YOLO11n Region Vehicle Counter", frame) - if cv.waitKey(1) & 0xff == ord('p'): # 按'p'键暂停 - print("用户暂停,按任意键继续...") - cv.waitKey(0) + # 键盘事件处理 + key = cv.waitKey(1) & 0xff + if not handle_keyboard_events(key, frame, frame_count, cap, out, "YOLO11n Region Vehicle Counter"): + break # 释放资源 cap.release() diff --git a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_updown.py b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_updown.py index a635b9262..95883a4ad 100644 --- a/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_updown.py +++ b/src/yolo11n_vehicle_counter/scripts/yolo_vehicle_counter_updown.py @@ -2,6 +2,7 @@ from ultralytics import YOLO import numpy as np import supervision as sv +from keyboard_handler import handle_keyboard_events # ==================== 配置路径 ==================== # 模型文件路径 @@ -223,7 +224,9 @@ def draw_tracks_and_count(frame, detections, total_counts, limits): # 显示当前帧 cv.imshow("Camera", frame) - if cv.waitKey(1) & 0xff == ord('p'): # 按'p'键暂停 + # 键盘事件处理 + key = cv.waitKey(1) & 0xff + if not handle_keyboard_events(key, frame, len(total_counts), cap, out, "Camera"): break # 释放资源