2026-04-20 14:19:12 +08:00

281 lines
9.4 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.

import cv2
import numpy as np
import motor
import time
import signal
import sys
# 信号处理函数用于捕获Ctrl+C
def signal_handler(sig, frame):
print("\n接收到中断信号,正在停止电机...")
motor.stop()
print("电机已停止,程序退出")
sys.exit(0)
# 注册信号处理函数
signal.signal(signal.SIGINT, signal_handler)
print("开始运行摄像头程序...")
print(f"OpenCV版本: {cv2.__version__}")
# 尝试打开摄像头
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头!")
exit()
print("摄像头打开成功")
# 设置摄像头参数
cap.set(3, 320)
cap.set(4, 240)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
print(f"摄像头分辨率: {cap.get(3)}x{cap.get(4)}")
# 初始化稳定阶段
print("初始化摄像头...")
success_count = 0
for i in range(10):
ret, frame = cap.read()
if not ret:
print(f"{i+1}帧读取失败")
continue
success_count += 1
print(f"{i+1}帧读取成功")
time.sleep(0.1)
print(f"摄像头初始化完成,成功读取{success_count}")
# 读取初始帧
ret, prev_frame = cap.read()
if not ret:
print("无法读取初始帧!")
cap.release()
exit()
print("初始帧读取成功")
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
prev_gray = cv2.GaussianBlur(prev_gray, (5,5), 0)
print("初始帧处理完成")
# 记录上一帧目标的位置和面积
prev_cx = None
prev_cy = None
prev_area = None
# 目标跟踪状态
tracking_target = None
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5,5), 0)
# 帧差
diff = cv2.absdiff(prev_gray, gray)
diff = cv2.convertScaleAbs(diff, alpha=2.5)
# 应用形态学操作,去除噪声
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
diff = cv2.morphologyEx(diff, cv2.MORPH_OPEN, kernel)
diff = cv2.morphologyEx(diff, cv2.MORPH_CLOSE, kernel)
_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# ===== 核心:目标检测和跟踪 =====
target = None
max_area = 0
# 找到所有符合条件的轮廓
valid_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 75: # 过滤噪声,增大阈值
continue
if area > 5000: # 过滤大面积假目标(如整个画面),减小阈值
continue
# 计算轮廓的宽高比,过滤不符合垃圾特征的目标
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = float(w) / h if h > 0 else 0
if aspect_ratio > 3 or aspect_ratio < 0.3: # 过滤过于狭长的目标
continue
valid_contours.append((area, cnt, x, y, w, h))
# 如果有有效的轮廓
if valid_contours:
# 如果正在跟踪目标,优先选择与上一目标位置接近的轮廓
if tracking_target is not None and prev_cx is not None and prev_cy is not None:
best_match = None
min_distance = float('inf')
search_radius = 80 # 目标锁定区域半径
for area, cnt, x, y, w, h in valid_contours:
cx = x + w // 2
cy = y + h // 2
# 计算与上一目标的距离
distance = ((cx - prev_cx) ** 2 + (cy - prev_cy) ** 2) ** 0.5
# 只考虑在锁定区域内的目标
if distance > search_radius:
continue
# 如果距离小于阈值,认为是同一个目标
if distance < 50: # 距离阈值
if distance < min_distance:
min_distance = distance
best_match = (area, cnt, x, y, w, h)
# 如果找到匹配的目标
if best_match:
area, cnt, x, y, w, h = best_match
target = cnt
max_area = area
else:
# 如果没有找到匹配的目标,选择最大的轮廓
valid_contours.sort(key=lambda x: x[0], reverse=True)
area, cnt, x, y, w, h = valid_contours[0]
target = cnt
max_area = area
# 开始跟踪新目标
tracking_target = True
else:
# 如果没有正在跟踪的目标,选择最大的轮廓
valid_contours.sort(key=lambda x: x[0], reverse=True)
area, cnt, x, y, w, h = valid_contours[0]
target = cnt
max_area = area
# 开始跟踪新目标
tracking_target = True
# ===== 只处理一个目标 =====
if target is not None:
x, y, w, h = cv2.boundingRect(target)
cx = x + w // 2
cy = y + h // 2
# 目标位置平滑处理
alpha = 0.6
if prev_cx is not None and prev_cy is not None:
cx = int(alpha * prev_cx + (1 - alpha) * cx)
cy = int(alpha * prev_cy + (1 - alpha) * cy)
# 绘制画面中心
center_x = 90 # 画面中心x坐标
center_y = 120 # 画面中心y坐标
cv2.circle(frame, (center_x, center_y), 5, (255, 0, 0), -1)
# 绘制目标矩形和中心
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
print("唯一目标:", cx, cy, "area:", max_area)
# 检测目标是否突然跳变
target_jumped = False
if prev_cx is not None and prev_cy is not None and prev_area is not None:
# 计算位置变化
dx = abs(cx - prev_cx)
dy = abs(cy - prev_cy)
# 计算面积变化比例
area_ratio = max_area / prev_area if prev_area > 0 else 0
# 如果位置变化过大或面积变化过大,认为目标跳变
if dx > 100 or dy > 100 or area_ratio < 0.3 or area_ratio > 3:
target_jumped = True
print("目标突然跳变,可能已离开视野范围")
# 重置跟踪状态
tracking_target = None
# 控制逻辑根据x轴和y轴坐标控制垃圾桶移动
center_x = 90 # 画面中心x坐标
center_y = 120 # 画面中心y坐标
threshold = 15 # 阈值范围
if target_jumped:
# 目标跳变,停止移动
print("目标跳变,停止移动")
motor.stop()
else:
# 计算目标与中心的偏差
dx = cx - center_x
dy = cy - center_y
# 确定移动方向
if abs(dx) < threshold and abs(dy) < threshold:
# 目标在中心附近,停止移动
print("目标在中心,停止移动")
motor.stop()
elif abs(dx) < threshold:
# 只在y轴方向有偏差
if dy > threshold:
# 目标在下侧,垃圾桶向右移动
print("目标在下侧,向右移动")
motor.move_right(speed=1.0)
else:
# 目标在上侧,垃圾桶向左移动
print("目标在上侧,向左移动")
motor.move_left(speed=1.0)
elif abs(dy) < threshold:
# 只在x轴方向有偏差
if dx > threshold:
# 目标在右侧,垃圾桶向后移动
print("目标在右侧,向后移动")
motor.backward(speed=1.0)
else:
# 目标在左侧,垃圾桶向前移动
print("目标在左侧,向前移动")
motor.forward(speed=1.0)
else:
# 在x轴和y轴方向都有偏差使用斜向移动
if dx > threshold and dy > threshold:
# 目标在右下侧,垃圾桶向右后移动
print("目标在右下侧,向右后移动")
motor.move_right_backward(speed=1.0)
elif dx > threshold and dy < -threshold:
# 目标在右上侧,垃圾桶向左后移动
print("目标在右上侧,向左后移动")
motor.move_left_backward(speed=1.0)
elif dx < -threshold and dy > threshold:
# 目标在左下侧,垃圾桶向右前移动
print("目标在左下侧,向右前移动")
motor.move_right_forward(speed=1.0)
else:
# 目标在左上侧,垃圾桶向左前移动
print("目标在左上侧,向左前移动")
motor.move_left_forward(speed=1.0)
# 更新上一帧的目标信息
prev_cx = cx
prev_cy = cy
prev_area = max_area
else:
# 没有检测到目标,停止移动
print("未检测到目标,停止移动")
motor.stop()
# 重置上一帧的目标信息
prev_cx = None
prev_cy = None
prev_area = None
# 重置跟踪状态
tracking_target = None
cv2.imshow("tracking", frame)
prev_gray = gray.copy()
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
motor.stop()