OpenCV实战指南:机器人视觉从入门到应用
用OpenCV-Python实现图像预处理、颜色检测、轮廓分析、特征匹配等机器人视觉核心技能,附完整代码和模型车应用场景。
OpenCV 计算机视觉 Python 机器人
OpenCV实战指南:机器人视觉从入门到应用
计算机视觉是自动驾驶和机器人感知的基础。OpenCV作为最成熟的视觉库,提供了从图像读取、预处理到特征检测的完整工具链。本文不讲空洞理论,直接用代码演示模型车项目中最常用的视觉技术。
环境搭建
# 安装OpenCV(推荐用pip)
pip install opencv-python numpy matplotlib
# 如果需要额外模块(SIFT、立体匹配等)
pip install opencv-contrib-python
# Jetson Nano / JetBot 用户(已有系统Python)
pip3 install opencv-python numpy
# JetPack自带CUDA加速的OpenCV,无需重新编译
验证安装:
import cv2
print(f"OpenCV版本: {cv2.__version__}")
print(f"CUDA支持: {cv2.cuda.getCudaEnabledDeviceCount() > 0}")
图像基础操作
import cv2
import numpy as np
# 读取图像
img = cv2.imread('track_photo.jpg')
print(f"图像尺寸: {img.shape}") # (高, 宽, 通道数)
# 颜色空间转换
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 调整大小(模型车摄像头通常需要降分辨率)
resized = cv2.resize(img, (160, 120)) # DonkeyCar标准输入尺寸
# 显示图像
cv2.imshow('Original', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
颜色检测——识别赛道标记
模型车赛道上经常有颜色标记(停车线、检查点等),用HSV颜色空间检测比RGB更稳定:
import cv2
import numpy as np
def detect_color_region(frame, lower_hsv, upper_hsv, color_name="target"):
"""检测指定颜色区域,返回最大轮廓的中心点和面积"""
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 创建掩码
mask = cv2.inRange(hsv, np.array(lower_hsv), np.array(upper_hsv))
# 形态学操作去噪
kernel = np.ones((5, 5), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# 找轮廓
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None, 0
# 取最大轮廓
largest = max(contours, key=cv2.contourArea)
area = cv2.contourArea(largest)
if area < 100: # 过滤小噪点
return None, 0
# 计算中心点
M = cv2.moments(largest)
if M["m00"] == 0:
return None, 0
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
return (cx, cy), area
# 使用示例:检测红色标记
# 读取一帧
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
# 红色在HSV中有两个区间(因为H通道是环形的)
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([180, 255, 255])
center1, area1 = detect_color_region(frame, lower_red1, upper_red1, "red")
center2, area2 = detect_color_region(frame, lower_red2, upper_red2, "red")
center = center1 if center1 else center2
if center:
print(f"检测到红色标记,中心: {center}, 面积: {max(area1, area2)}")
cv2.circle(frame, center, 5, (0, 255, 0), -1)
cap.release()
车道线检测——模型车核心视觉任务
对于DonkeyCar这类循线小车,车道线检测是关键:
import cv2
import numpy as np
def detect_lane_lines(frame):
"""
检测赛道车道线,返回左右车道线的角度和偏移量
适用于俯视图或前视摄像头
"""
# 1. 转灰度 + 高斯模糊
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 2. Canny边缘检测
edges = cv2.Canny(blurred, 50, 150)
# 3. 感兴趣区域(ROI)——只关注画面下半部分
h, w = frame.shape[:2]
roi_vertices = np.array([
[0, h // 2],
[w, h // 2],
[w, h],
[0, h]
], dtype=np.int32)
mask = np.zeros_like(edges)
cv2.fillPoly(mask, [roi_vertices], 255)
masked_edges = cv2.bitwise_and(edges, mask)
# 4. Hough直线检测
lines = cv2.HoughLinesP(
masked_edges,
rho=1,
theta=np.pi / 180,
threshold=30,
minLineLength=20,
maxLineGap=10
)
if lines is None:
return None
# 5. 分离左右车道线
left_lines = []
right_lines = []
mid_x = w // 2
for line in lines:
x1, y1, x2, y2 = line[0]
if x1 == x2:
continue
slope = (y2 - y1) / (x2 - x1)
# 根据斜率和位置判断左右
if slope < -0.3 and x1 < mid_x:
left_lines.append((slope, y1 - slope * x1))
elif slope > 0.3 and x1 > mid_x:
right_lines.append((slope, y1 - slope * x1))
# 6. 拟合平均车道线
def average_line(lines, y_bottom, y_top):
if not lines:
return None
avg_slope = np.mean([l[0] for l in lines])
avg_intercept = np.mean([l[1] for l in lines])
x_bottom = int((y_bottom - avg_intercept) / avg_slope)
x_top = int((y_top - avg_intercept) / avg_slope)
return ((x_bottom, y_bottom), (x_top, y_top))
y_bottom = h
y_top = h // 2
left_line = average_line(left_lines, y_bottom, y_top)
right_line = average_line(right_lines, y_bottom, y_top)
return left_line, right_line
# 可视化
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
result = detect_lane_lines(frame)
if result and result[0] and result[1]:
left, right = result
# 画车道线
overlay = frame.copy()
cv2.line(overlay, left[0], left[1], (0, 255, 0), 3)
cv2.line(overlay, right[0], right[1], (0, 255, 0), 3)
# 计算车道中心偏移
h, w = frame.shape[:2]
left_x_bottom = left[0][0]
right_x_bottom = right[0][0]
lane_center = (left_x_bottom + right_x_bottom) // 2
offset = lane_center - w // 2
cv2.putText(frame, f"Offset: {offset}px", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
frame = cv2.addWeighted(overlay, 0.3, frame, 0.7, 0)
cv2.imshow('Lane Detection', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
特征匹配——视觉定位
当你的机器人需要在已知环境中定位(比如仓库中的特定货架),特征匹配是实用方案:
import cv2
import numpy as np
def match_features(query_img, reference_img):
"""
用ORB特征匹配判断query在reference中的位置
返回匹配数量和变换矩阵
"""
orb = cv2.ORB_create(nfeatures=1000)
# 检测特征点和描述子
kp1, des1 = orb.detectAndCompute(query_img, None)
kp2, des2 = orb.detectAndCompute(reference_img, None)
if des1 is None or des2 is None:
return 0, None
# BFMatcher暴力匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)
# 取前50个好的匹配
good_matches = matches[:50]
if len(good_matches) < 4:
return len(good_matches), None
# 提取匹配点坐标
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
# 计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
return len(good_matches), M
# 使用示例
query = cv2.imread('current_view.jpg', 0)
reference = cv2.imread('map_image.jpg', 0)
n_matches, homography = match_features(query, reference)
print(f"匹配特征点数: {n_matches}")
if homography is not None:
# 将query的四个角映射到reference坐标系
h, w = query.shape
corners = np.float32([[0,0], [0,h], [w,h], [w,0]]).reshape(-1, 1, 2)
transformed_corners = cv2.perspectiveTransform(corners, homography)
print("当前视角在地图中的位置:", transformed_corners.reshape(-1, 2))
视频流处理——实时性能优化
在树莓派或Jetson上跑视觉算法,性能是关键:
import cv2
import time
from threading import Thread, Lock
class VideoStream:
"""独立线程读取摄像头,避免主循环阻塞"""
def __init__(self, src=0, resolution=(320, 240), fps=30):
self.cap = cv2.VideoCapture(src)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, resolution[0])
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, resolution[1])
self.cap.set(cv2.CAP_PROP_FPS, fps)
self.ret, self.frame = self.cap.read()
self.lock = Lock()
self.stopped = False
# 启动读取线程
Thread(target=self.update, daemon=True).start()
def update(self):
while not self.stopped:
ret, frame = self.cap.read()
if ret:
with self.lock:
self.ret = ret
self.frame = frame
def read(self):
with self.lock:
return self.ret, self.frame.copy()
def stop(self):
self.stopped = True
self.cap.release()
# 使用
stream = VideoStream(src=0, resolution=(160, 120), fps=30)
time.sleep(1) # 等待摄像头稳定
while True:
ret, frame = stream.read()
if not ret:
continue
# 处理帧(这里放你的视觉算法)
start = time.time()
# ... 你的处理代码 ...
fps = 1.0 / (time.time() - start)
cv2.putText(frame, f"FPS: {fps:.1f}", (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
cv2.imshow('Stream', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
stream.stop()
cv2.destroyAllWindows()
实用技巧总结
- 降分辨率:模型车不需要4K,160x120足够跑算法,帧率能提升10倍
- ROI裁剪:只处理感兴趣区域,比如只处理画面下半部分做车道线检测
- 形态学操作:开运算去噪点,闭运算填孔洞,比阈值过滤效果好
- HSV优于RGB:颜色检测用HSV空间,对光照变化更鲁棒
- 多线程读帧:摄像头读取和处理分离,避免I/O阻塞计算
- GStreamer后端:Jetson用户用GStreamer替代V4L2,能利用硬件编解码
OpenCV是机器人视觉的瑞士军刀。掌握这些基础操作后,你就能为DonkeyCar、JetBot或任何模型车项目构建可靠的视觉感知模块。