import cv2
import numpy as np
from collections import deque
# 基础配置参数(关键修改:固定px_per_cm,动态计算透视目标尺寸)
known_length_cm = 29.7 # A4纸长度(cm)
known_width_cm = 21.0 # A4纸宽度(cm)
FIXED_PX_PER_CM = 100 # 固定像素/厘米比例(核心!不受距离影响)
threshold_value = 75 # 二值化阈值
max_threshold = 255 # 最大阈值
# 存储检测数据的双端队列(保留最近5次结果用于滤波)
detection_data = {
"axis_distance": deque(maxlen=5),
"vertical_distance": deque(maxlen=5),
"shapes": deque(maxlen=5)
}
def order_points(pts):
"""标准四边形顶点排序(左上→右上→右下→左下),避免透视变换扭曲"""
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # 左上(x+y最小)
rect[2] = pts[np.argmax(s)] # 右下(x+y最大)
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)] # 右上(x-y最小)
rect[3] = pts[np.argmax(diff)] # 左下(x-y最大)
return rect
def detect_shapes_in_warped(warped, px_per_cm, display_frame, threshold_value):
"""
非YOLO模式核心形状检测函数(无修改,仅依赖稳定的px_per_cm)
:param warped: 透视变换后的矫正图像
:param px_per_cm: 固定像素/厘米比例(恒为FIXED_PX_PER_CM)
:param display_frame: 主显示帧(用于叠加文字说明)
:param threshold_value: 二值化阈值
:return: warped_display(标注后矫正图)、shapes_info(形状信息列表)、binary_warped(二值化图像)
"""
# 1. 图像预处理:灰度转换 + 二值化
gray_warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
_, binary_warped = cv2.threshold(gray_warped, threshold_value, max_threshold, cv2.THRESH_BINARY)
# 2. 形态学闭运算:填充孔洞(5x5矩形结构元素)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
binary_warped = cv2.morphologyEx(binary_warped, cv2.MORPH_CLOSE, kernel)
# 3. 轮廓提取 + 小噪点过滤(面积<500像素)
contours, _ = cv2.findContours(binary_warped, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
warped_display = warped.copy()
shapes_info = []
# 4. 形状识别与分类
for i, cnt in enumerate(contours):
area = cv2.contourArea(cnt)
if area < 500: # 过滤小噪点
continue
perimeter = cv2.arcLength(cnt, True)
if perimeter == 0: # 避免除零错误
continue
approx = cv2.approxPolyDP(cnt, 0.04 * perimeter, True) # 多边形逼近
approx_len = len(approx)
x, y, w, h = cv2.boundingRect(cnt) # 外接矩形
# 三角形(3条边)
if approx_len == 3:
side_lengths = [np.linalg.norm(approx[j][0] - approx[(j + 1) % 3][0]) for j in range(3)]
max_side_cm = max(side_lengths) / px_per_cm
# 可视化标注
cv2.drawContours(warped_display, [approx], 0, (0, 255, 0), 2)
cv2.putText(warped_display, f"Triangle: {max_side_cm:.2f}cm", (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
cv2.putText(display_frame, f"Triangle {i + 1}: {max_side_cm:.2f}cm", (10, 50 + i * 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
# 存储形状信息
shapes_info.append({
"type": "triangle",
"max_side_cm": max_side_cm,
"contour_index": i
})
# 正方形(4条边 + 宽高比0.85~1.15)
elif approx_len == 4 and 0.85 <= float(w) / h <= 1.15:
side_cm = w / px_per_cm
# 可视化标注
cv2.rectangle(warped_display, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(warped_display, f"Square: {side_cm:.2f}cm", (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2)
cv2.putText(display_frame, f"Square {i + 1}: {side_cm:.2f}cm", (10, 90 + i * 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2)
# 存储形状信息
shapes_info.append({
"type": "square",
"side_cm": side_cm,
"contour_index": i
})
# 圆形(圆形度0.85~1.15)
else:
circularity = (4 * np.pi * area) / (perimeter ** 2)
if 0.85 <= circularity <= 1.15:
diameter_cm = max(w, h) / px_per_cm
center = (x + w // 2, y + h // 2)
# 可视化标注
cv2.circle(warped_display, center, w // 2, (0, 255, 255), 2)
cv2.putText(warped_display, f"Circle: {diameter_cm:.2f}cm", (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)
cv2.putText(display_frame, f"Circle {i + 1}: {diameter_cm:.2f}cm", (10, 130 + i * 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)
# 存储形状信息
shapes_info.append({
"type": "circle",
"diameter_cm": diameter_cm,
"contour_index": i
})
# 新增:返回二值化图像,用于单独显示
return warped_display, shapes_info, binary_warped
# 辅助函数:移动平均滤波(用于距离和形状尺寸平滑)
def moving_average_filter(data, window_size=5):
if len(data) < window_size:
return sum(data) / len(data) if data else 0
return sum(list(data)[-window_size:]) / window_size
# 辅助函数:形状数据滤波(基于历史5次结果)
def filter_shape_data(shapes_history, shape_type):
if not shapes_history:
return None
type_history = [s for entry in shapes_history for s in entry if s["type"] == shape_type]
if not type_history:
return None
filtered = type_history[-1].copy()
if shape_type == "triangle":
filtered["max_side_cm"] = sum(s["max_side_cm"] for s in type_history) / len(type_history)
elif shape_type == "square":
filtered["side_cm"] = sum(s["side_cm"] for s in type_history) / len(type_history)
elif shape_type == "circle":
filtered["diameter_cm"] = sum(s["diameter_cm"] for s in type_history) / len(type_history)
return filtered
# 主流程(核心修改:适配电脑摄像头,移除V4L2后端)
def main_basic_detection():
# 关键修改:电脑摄像头用默认后端(移除cv2.CAP_V4L2),0表示默认摄像头,1表示外接摄像头
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头!请检查:")
print("1. 摄像头是否被其他程序占用(如Zoom、微信视频)")
print("2. 电脑是否授予摄像头访问权限(设置→隐私→摄像头)")
print("3. 若有外接摄像头,将cap = cv2.VideoCapture(0) 改为 cap = cv2.VideoCapture(1)")
return
# 可选优化:设置摄像头分辨率(电脑摄像头推荐1280x720,平衡清晰度和性能)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 相机标定参数(用于畸变校正,可选但建议保留)
camera_matrix = np.array([
[1.22664341e+04, 0.00000000e+00, 1.23363768e+03],
[0.00000000e+00, 1.22682592e+04, 3.25759070e+02],
[0.00000000e+00, 0.00000000e+00, 1.00000000e+00]
], dtype=np.float32)
dist_coeffs = np.array([[0, 0, 0, 0, 0]], dtype=np.float32)
# 动态计算透视变换目标尺寸(固定px_per_cm,不受距离影响)
dst_width = int(known_width_cm * FIXED_PX_PER_CM) # 21cm × 100px/cm = 2100px
dst_height = int(known_length_cm * FIXED_PX_PER_CM) # 29.7cm × 100px/cm = 2970px
dst_pts = np.array([
[0, 0],
[dst_width - 1, 0],
[dst_width - 1, dst_height - 1],
[0, dst_height - 1]
], dtype=np.float32) # 目标四边形顶点(固定尺寸)
while True:
ret, frame = cap.read()
if not ret:
print("获取帧失败!请检查摄像头连接")
break
# 1. 畸变校正(可选,提升精度)
h, w = frame.shape[:2]
new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_coeffs, (w, h), 1, (w, h))
mapx, mapy = cv2.initUndistortRectifyMap(camera_matrix, dist_coeffs, None, new_camera_matrix, (w, h), 5)
frame = cv2.remap(frame, mapx, mapy, cv2.INTER_LINEAR)
x, y, crop_w, crop_h = roi
frame = frame[y:y + crop_h, x:x + crop_w]
# 2. 检测A4纸轮廓(用于透视变换)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, threshold_value, max_threshold, cv2.THRESH_BINARY)
inv_binary = cv2.bitwise_not(binary) # 反相:A4纸为白色,背景为黑色
# 形态学开运算:先腐蚀再膨胀,去除小噪点
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
inv_binary = cv2.morphologyEx(inv_binary, cv2.MORPH_OPEN, kernel)
# 提取轮廓
contours, _ = cv2.findContours(inv_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 3. 透视变换(核心修改:目标尺寸固定,px_per_cm恒为100)
warped = frame.copy()
if contours:
# 选择最大轮廓(A4纸)
max_contour = max(contours, key=cv2.contourArea)
perimeter = cv2.arcLength(max_contour, True)
approx = cv2.approxPolyDP(max_contour, 0.03 * perimeter, True) # 多边形逼近
if len(approx) == 4: # 确认是四边形(A4纸)
src_pts = order_points(approx.reshape(4, 2).astype(np.float32)) # 排序顶点
M = cv2.getPerspectiveTransform(src_pts, dst_pts) # 计算透视矩阵
warped = cv2.warpPerspective(frame, M, (dst_width, dst_height)) # 透视变换
# 4. 固定px_per_cm,不受距离影响(核心!)
px_per_cm = FIXED_PX_PER_CM
# 5. 核心:形状检测与尺寸测量(新增接收二值化图像)
display_frame = frame.copy()
cv2.putText(display_frame, "Shape Detection Results (PC Camera)", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 2)
# 新增:接收binary_warped(透视后的二值化图像)
warped_display, shapes_info, binary_warped = detect_shapes_in_warped(warped, px_per_cm, display_frame, threshold_value)
# 6. 存储形状数据用于滤波
if shapes_info:
detection_data["shapes"].append(shapes_info)
# 7. 显示结果(调整窗口大小适配电脑屏幕)
cv2.imshow("1. Detection Results", cv2.resize(display_frame, (800, 600))) # 检测结果窗口
cv2.imshow("2. Warped & Annotated", cv2.resize(warped_display, (600, 800))) # 矫正+标注窗口
# 新增:二值化图像窗口(将单通道灰度图转为三通道BGR,便于统一显示)
binary_warped_bgr = cv2.cvtColor(binary_warped, cv2.COLOR_GRAY2BGR)
# 在二值化图像上添加标题,方便识别
cv2.putText(binary_warped_bgr, "Binary Image (Warped)", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2)
cv2.imshow("3. Binary Image", cv2.resize(binary_warped_bgr, (600, 800))) # 二值化窗口
# 按键退出(按'q'退出,按's'保存当前所有结果)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('s'):
cv2.imwrite("1_detection_result.jpg", display_frame)
cv2.imwrite("2_warped_annotated.jpg", warped_display)
cv2.imwrite("3_binary_image.jpg", binary_warped)
print("所有结果已保存到当前目录!")
# 释放资源(关闭所有窗口)
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main_basic_detection()针对这个代码的基础上给我添加pnn算法,实现无论距离远近,测出来物体的实际尺寸不随距离变化产生太大误差【相机内参矩阵】(camera_matrix):
[[422.17826004 0. 222.64141001]
[ 0. 437.04479208 307.11403884]
[ 0. 0. 1. ]]
【畸变系数】(dist_coeffs):
[[-0.00606872 0.05575608 0.00526463 0.00236328 -0.14050405]]这是摄像机的参数
最新发布