基于Python与OpenCV的运动目标速度检测:实现快速动作筛选
基于现有代码实现运动目标速度度量与快速场景筛选方案
完全可以实现你需要的效果——不需要换算成m/s、km/h这类真实物理速度,用跨帧像素位移作为通用量化运动快慢的指标,就足够支撑阈值筛选,完全适配你这个测试场景里识别打斗等突发剧烈运动的需求。
核心实现逻辑
你现有的代码已经能通过相邻帧差检测出运动目标的矩形框,要计算运动快慢,只需要补一层简单的目标跨帧匹配逻辑:
- 给每个上一帧检测到的目标,存储它的矩形框中心坐标、唯一ID、位置历史记录
- 当前帧检测到新的目标框后,通过中心位置的欧氏距离,匹配和上一帧位置最接近的目标,判定为同一个运动目标
- 计算同一个目标相邻两帧的中心位置欧氏距离,这个值就是你要的量化速度指标:数值越大,代表单位时间内目标移动的距离越远,运动越快
- 给这个位移值设置对应阈值,就能筛选出快速运动的场景:比如普通站立小幅晃动的位移值通常在5-10像素,慢走的位移值在10-30像素,打斗时肢体快速挥动的位移值通常能超过50像素,超过阈值的目标可以用特殊颜色加粗标注,直接区分普通运动和剧烈运动
针对现有代码的关键修改点
你现在的代码存在两个影响速度计算的问题:
- 没有目标跨帧匹配逻辑,每帧检测到的矩形框都是独立的,没法关联同一个目标的前后位置
- 现有退出按键判断逻辑有误:
cv2.waitKey(60) == 60是按6号键退出,通常应该改成检测ESC键(ASCII码27)退出
修改后的核心代码片段示例:
# 初始化跟踪目标列表,存储上一帧的目标信息:格式为 {id: [中心x, 中心y, 历史位置]} tracked_objects = {} next_object_id = 0 speed_threshold = 45 # 可根据实际测试调整的速度阈值,单位:像素/帧 while cap.isOpened(): ret, frame1 = cap.read() if not ret: break diff = cv2.absdiff(frame1, frame2) gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY) dilated = cv2.dilate(thresh, None, iterations=5) contours, _ = cv2.findContours(dilated, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 当前帧检测到的目标列表 current_detections = [] for contour in contours: cnt_area = cv2.contourArea(contour) if cnt_area < 1000 or cnt_area > 4000: continue (x, y, w, h) = cv2.boundingRect(contour) center_x = x + w//2 center_y = y + h//2 current_detections.append((center_x, center_y, x, y, w, h)) # 匹配当前检测和上一帧的跟踪目标 updated_tracks = {} for det in current_detections: det_cx, det_cy, x, y, w, h = det min_dist = float('inf') matched_id = -1 # 找距离最近的上一帧目标 for obj_id, (prev_cx, prev_cy, _) in tracked_objects.items(): dist = np.sqrt((det_cx - prev_cx)**2 + (det_cy - prev_cy)**2) if dist < min_dist and dist < 60: # 匹配阈值:中心距离小于60像素才算同一个目标 min_dist = dist matched_id = obj_id # 匹配上的目标,计算速度 if matched_id != -1: prev_cx, prev_cy, history = tracked_objects[matched_id] speed = min_dist history.append((det_cx, det_cy)) updated_tracks[matched_id] = [det_cx, det_cy, history] # 超过速度阈值就用红色加粗框标注,普通目标用灰色框 if speed > speed_threshold: cv2.rectangle(frame1, (x,y), (x+w, y+h), (0, 0, 255), thickness=2) cv2.putText(frame1, f"快速运动 速度:{int(speed)}", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2) else: cv2.rectangle(frame1, (x,y), (x+w, y+h), (128,128,128), thickness=1) # 没匹配上的新目标,分配新ID else: updated_tracks[next_object_id] = [det_cx, det_cy, [(det_cx, det_cy)]] next_object_id +=1 # 更新跟踪列表 tracked_objects = updated_tracks cv2.imshow('frame', frame1) frame2 = frame1.copy() ret, frame2 = cap.read() if cv2.waitKey(60) == 27: # 按ESC退出 break cv2.destroyAllWindows() cap.release()
调优建议
- 速度阈值
speed_threshold不需要固定,你可以跑一遍测试视频,打印每个目标的实时速度值,找打斗场景和普通站立、走路场景的速度分界值就行,一般在30-60像素/帧区间最适合这个测试视频 - 可以加个短暂的速度平滑:比如取最近3帧的平均位移值当最终速度,避免视频噪声导致的单帧速度虚高误判
- 匹配阈值不要设太大,避免把相邻的两个人错匹配成同一个目标,通常设置成比单个人体的宽度小一点的值就够
内容的提问来源于stack exchange,提问作者taga
相关产品推荐
相关产品推荐

