树莓派4中Python3.9 Thonny适配Python3.11编写的OpenCV代码问题
树莓派4 Python3.9环境适配模拟仪表识别代码方案
错误根源分析
- 摄像头读取失败:树莓派原生摄像头与OpenCV默认GStreamer后端兼容性差,导致内存分配失败,无法捕获帧
- 文件读取错误:摄像头未成功捕获帧,目标图片文件未生成,
cv2.imread返回None,触发'NoneType' object has no attribute 'shape'异常 - 代码逻辑漏洞:存在循环嵌套错误、资源未释放等问题
适配步骤与修改代码
1. 摄像头读取适配(二选一)
方案A:调整OpenCV VideoCapture参数
指定V4L2后端规避GStreamer兼容性问题:
# 替换原cam = cv2.VideoCapture(0)为: cam = cv2.VideoCapture(0, cv2.CAP_V4L2) # 按需设置分辨率 cam.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
方案B:改用Picamera2(推荐,树莓派原生支持)
先安装依赖:
sudo apt install python3-picamera2
替换摄像头读取逻辑:
from picamera2 import Picamera2 def take_measure(...): # 初始化Picamera2 picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": 'RGB888', "size": (640, 480)}) picam2.configure(config) picam2.start() cv2.namedWindow("test") frame_bgr = None while True: frame = picam2.capture_array() frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) cv2.imshow("test", frame_bgr) k = cv2.waitKey(1) if k % 256 == 27: print("Escape hit, closing...") break elif k % 256 == 32: cv2.imwrite("analog_gauge_30.png", frame_bgr) print("analog_gauge_30.png written!") break picam2.stop() cv2.destroyWindow("test")
2. 完善错误处理
添加空值判断,避免None对象操作:
if frame_bgr is None: print("Failed to capture valid frame") return None, None img = frame_bgr height, width = img.shape[:2]
3. 修复代码逻辑漏洞
- 拆分嵌套循环,修正刻度坐标计算逻辑:
# 计算刻度线起点 for i in range(0, interval): for j in range(0, 2): if j % 2 == 0: p1[i][j] = x + 0.9 * r * np.cos(separation * i * np.pi / 180) else: p1[i][j] = y + 0.9 * r * np.sin(separation * i * np.pi / 180) text_offset_x = 10 text_offset_y = 5 # 计算刻度线终点和文本位置 for i in range(0, interval): for j in range(0, 2): if j % 2 == 0: p2[i][j] = x + r * np.cos(separation * i * np.pi / 180) p_text[i][j] = x - text_offset_x + 1.2 * r * np.cos((separation) * (i + 9) * np.pi / 180) else: p2[i][j] = y + r * np.sin(separation * i * np.pi / 180) p_text[i][j] = y + text_offset_y + 1.2 * r * np.sin((separation) * (i + 9) * np.pi / 180)
- 添加资源释放逻辑,在程序退出时销毁所有窗口:
cv2.destroyAllWindows()
4. 树莓派环境配置
- 启用摄像头:运行
sudo raspi-config,选择Interface Options->Camera,启用后重启设备 - 安装依赖库:
sudo apt install python3-opencv python3-numpy
完整适配后代码(Picamera2版本)
import cv2 import numpy as np from picamera2 import Picamera2 def avg_circles(circles, b): avg_x = 0 avg_y = 0 avg_r = 0 for i in range(b): avg_x = avg_x + circles[0][i][0] avg_y = avg_y + circles[0][i][1] avg_r = avg_r + circles[0][i][2] avg_x = int(avg_x / (b)) avg_y = int(avg_y / (b)) avg_r = int(avg_r / (b)) return avg_x, avg_y, avg_r def dist_2_pts(x1, y1, x2, y2): return np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) def take_measure(threshold_img, threshold_ln, minLineLength, maxLineGap, diff1LowerBound, diff1UpperBound, diff2LowerBound, diff2UpperBound): picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": 'RGB888', "size": (640, 480)}) picam2.configure(config) picam2.start() cv2.namedWindow("test") frame_bgr = None while True: frame = picam2.capture_array() frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) cv2.imshow("test", frame_bgr) k = cv2.waitKey(1) if k % 256 == 27: print("Escape hit, closing...") break elif k % 256 == 32: cv2.imwrite("analog_gauge_30.png", frame_bgr) print("analog_gauge_30.png written!") break picam2.stop() cv2.destroyWindow("test") if frame_bgr is None: print("Failed to capture valid frame") return None, None img = frame_bgr height, width = img.shape[:2] gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20) if circles is not None: a, b, c = circles.shape x, y, r = avg_circles(circles, b) cv2.circle(img, (x, y), r, (0, 255, 0), 3, cv2.LINE_AA) cv2.circle(img, (x, y), 2, (0, 255, 0), 3, cv2.LINE_AA) min_angle = 0 max_angle = 360 min_value = 0 max_value = 16 separation = 10 interval = int(360 / separation) p1 = np.zeros((interval, 2)) p2 = np.zeros((interval, 2)) p_text = np.zeros((interval, 2)) # 计算刻度线起点 for i in range(0, interval): for j in range(0, 2): if j % 2 == 0: p1[i][j] = x + 0.9 * r * np.cos(separation * i * np.pi / 180) else: p1[i][j] = y + 0.9 * r * np.sin(separation * i * np.pi / 180) text_offset_x = 10 text_offset_y = 5 # 计算刻度线终点和文本位置 for i in range(0, interval): for j in range(0, 2): if j % 2 == 0: p2[i][j] = x + r * np.cos(separation * i * np.pi / 180) p_text[i][j] = x - text_offset_x + 1.2 * r * np.cos((separation) * (i + 9) * np.pi / 180) else: p2[i][j] = y + r * np.sin(separation * i * np.pi / 180) p_text[i][j] = y + text_offset_y + 1.2 * r * np.sin((separation) * (i + 9) * np.pi / 180) # 绘制刻度线和文本 for i in range(0, interval): cv2.line(img, (int(p1[i][0]), int(p1[i][1])), (int(p2[i][0]), int(p2[i][1])), (0, 255, 0), 2) cv2.putText(img, '%s' % (int(i * separation)), (int(p_text[i][0]), int(p_text[i][1])), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (255, 0, 0), 1, cv2.LINE_AA) cv2.putText(img, "Gauge OK!", (50, 75), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2, cv2.LINE_AA) gray3 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) maxValue = 255 th, dst2 = cv2.threshold(gray3, threshold_img, maxValue, cv2.THRESH_BINARY_INV) dst2 = cv2.medianBlur(dst2, 5) dst2 = cv2.Canny(dst2, 50, 150) dst2 = cv2.GaussianBlur(dst2, (5, 5), 0) in_loop = 0 lines = cv2.HoughLinesP(image=dst2, rho=3, theta=np.pi / 180, threshold=threshold_ln, minLineLength=minLineLength, maxLineGap=maxLineGap) final_line_list = [] if lines is not None: for i in range(0, len(lines)): for x1, y1, x2, y2 in lines[i]: diff1 = dist_2_pts(x, y, x1, y1) diff2 = dist_2_pts(x, y, x2, y2) if diff1 > diff2: diff1, diff2 = diff2, diff1 if ((diff1 < diff1UpperBound * r) and (diff1 > diff1LowerBound * r) and (diff2 < diff2UpperBound * r) and (diff2 > diff2LowerBound * r)): final_line_list.append([x1, y1, x2, y2]) in_loop = 1 if in_loop == 1: x1, y1, x2, y2 = final_line_list[0] cv2.line(img, (x1, y1), (x2, y2), (0, 255, 255), 2) dist_pt_0 = dist_2_pts(x, y, x1, y1) dist_pt_1 = dist_2_pts(x, y, x2, y2) if dist_pt_0 > dist_pt_1: x_angle = x1 - x y_angle = y - y1 else: x_angle = x2 - x y_angle = y - y2 res = np.arctan2(y_angle, x_angle) res = np.rad2deg(res) # 修正角度计算逻辑 if x_angle > 0 and y_angle > 0: final_angle = 270 - res elif x_angle < 0 and y_angle > 0: final_angle = 90 - res elif x_angle < 0 and y_angle < 0: final_angle = 90 - res elif x_angle > 0 and y_angle < 0: final_angle = 270 - res else: final_angle = 0 # 转换为仪表数值 old_min = float(min_angle) old_max = float(max_angle) new_min = float(min_value) new_max = float(max_value) old_value = final_angle old_range = old_max - old_min new_range = new_max - new_min new_value = (((old_value - old_min) * new_range) / old_range) + new_min - 1.4 cv2.putText(img, "Indicator OK!", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2, cv2.LINE_AA) cv2.putText(img, f"{new_value:.1f}", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2, cv2.LINE_AA) print("Res:", res) print("Final Angle: ", final_angle) print("New value", new_value) else: cv2.putText(img, "Can't see the gauge!", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2, cv2.LINE_AA) else: cv2.putText(img, "Can't detect gauge circle!", (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2, cv2.LINE_AA) return img, img if __name__ == "__main__": threshold_img = 120 threshold_ln = 150 minLineLength = 40 maxLineGap = 8 diff1LowerBound = 0.15 diff1UpperBound = 0.25 diff2LowerBound = 0.5 diff2UpperBound = 1.0 while True: img, img2 = take_measure(threshold_img, threshold_ln, minLineLength, maxLineGap, diff1LowerBound, diff1UpperBound, diff2LowerBound, diff2UpperBound) if img is not None: cv2.imshow('Analog Gauge RESULT', img2) if cv2.waitKey(1) == ord('q'): break cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者user22417774
相关产品推荐
相关产品推荐

