三、图像传感器与接口:摄像头选型与图像采集
好,咱们进入第三讲。这一讲我打算聊聊摄像头本身——说白了,就是农田里那双「眼睛」到底该怎么选、怎么接、怎么让它把图像数据送到咱们的处理器里。
我记得刚入行那会儿,总觉得摄像头嘛,能出图像就行。结果第一次在田间测试,阳光一强,画面全白;傍晚光线一暗,全是噪点。嗯,从那以后我才明白,选摄像头这事儿,真不能马虎。
3.1 摄像头选型:USB、CSI、全局快门
先说说接口。目前嵌入式视觉里主流的摄像头接口就两种:USB 和 CSI。我个人习惯把 USB 摄像头叫做「傻瓜型」,把 CSI 摄像头叫做「专业型」。
3.1.1 USB 摄像头
USB 摄像头最大的好处就是——即插即用。你插上电脑,系统自动识别,OpenCV 里一句 cv2.VideoCapture(0) 就能干活。我在做早期原型验证时特别喜欢用 USB 摄像头,省事。
但它的缺点也很明显:
- 带宽受限:USB 2.0 的理论带宽只有 480 Mbps,实际能跑 640×480@30fps 就不错了。你要是想跑 1080p 60fps,得上 USB 3.0。
- 延迟较高:USB 协议本身有开销,再加上驱动层的缓冲,从摄像头到应用层通常有 2-3 帧的延迟。这在高速障碍检测里是个隐患。
- 供电不稳定:USB 口供电通常只有 500mA,长线缆传输时压降明显。我在农田里遇到过摄像头间歇性掉线,最后发现是 USB 延长线太长导致的。
3.1.2 CSI 摄像头
CSI(Camera Serial Interface)是直接挂在处理器 MIPI 总线上的。说白了,它跟处理器之间是「直连」,没有 USB 那套协议栈的折腾。
CSI 的优势:
- 低延迟:数据直接从传感器到 ISP(图像信号处理器),延迟通常在毫秒级。
- 高带宽:MIPI CSI-2 接口可以轻松跑到 1 Gbps 以上,支持 4K 分辨率。
- 低功耗:没有额外的 USB 控制器,整体功耗更低。
但 CSI 也有门槛——你得自己配置驱动。我记得第一次调树莓派的 CSI 摄像头,折腾了一整天,就为了把 raspistill 的参数调对。嗯,这里有个坑:不同 SoC 的 CSI 接口引脚定义不一样,买摄像头之前一定要确认兼容性。
3.1.3 全局快门 vs 卷帘快门
这个知识点,我觉得是农田障碍检测里最容易被忽略的。你想想看,农机在田里跑,速度虽然不快,但颠簸啊。卷帘快门(Rolling Shutter)的摄像头,在震动时拍出来的图像会「果冻效应」——画面扭曲,障碍物变形。
为什么会这样?
卷帘快门是一行一行扫描曝光的。如果场景在动,先扫的行和后扫的行之间有时间差,物体就变形了。全局快门(Global Shutter)则是一次性曝光所有像素,瞬间冻结画面。
| 特性 | 全局快门 | 卷帘快门 |
|---|---|---|
| 曝光方式 | 所有像素同时曝光 | 逐行扫描曝光 |
| 运动畸变 | 无 | 有(果冻效应) |
| 低光性能 | 一般(噪声略高) | 较好 |
| 成本 | 较高 | 较低 |
| 推荐场景 | 运动检测、障碍识别 | 静态场景、拍照 |
3.2 图像采集原理
图像采集这事儿,听起来简单——摄像头一拍,数据就来了。但实际底层流程比你想象的要复杂。
简单来说,图像采集分三步:
- 光电转换:CMOS 传感器上的每个像素点把光信号转成电信号(电压)。
- 模数转换(ADC):把模拟电压转成数字值(0-255 或 0-1023)。
- ISP 处理:对原始 RAW 数据进行去噪、白平衡、色彩校正、伽马校正等处理,最终输出标准的 RGB 或 YUV 图像。
这里有个关键点:很多 USB 摄像头内部已经集成了 ISP,你拿到的就是处理好的图像。但 CSI 摄像头有时只输出 RAW 数据,需要你自己在处理器上跑 ISP 算法。我刚开始用 CSI 摄像头时,拿到的图像是「灰蒙蒙」的,后来才发现是没做白平衡校正。
3.3 OpenCV 图像读取与显示
好,摄像头选好了,数据也采集了,怎么在代码里拿到图像?OpenCV 提供了非常简洁的接口。
3.3.1 读取 USB 摄像头
import cv2
# 打开摄像头(0 表示第一个 USB 摄像头)
cap = cv2.VideoCapture(0)
# 设置分辨率(我习惯设为 640x480,平衡速度和精度)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# 设置帧率
cap.set(cv2.CAP_PROP_FPS, 30)
while True:
ret, frame = cap.read()
if not ret:
print("读取失败,检查摄像头连接")
break
# 显示图像
cv2.imshow('Farm Camera', frame)
# 按 'q' 退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这段代码看起来简单,但我遇到过不少坑。比如 cap.set() 设置分辨率时,不是所有摄像头都支持你指定的值。有些摄像头只支持固定的分辨率列表,你设一个不支持的,它可能自动切换到最接近的。我曾经被这个坑过——设了 800x600,结果实际输出是 640x480,算法里预设的 ROI 全偏了。
cap.set() 之后,用 cap.get(cv2.CAP_PROP_FRAME_WIDTH) 和 cap.get(cv2.CAP_PROP_FRAME_HEIGHT) 确认实际值。
3.3.2 读取 CSI 摄像头(以树莓派为例)
CSI 摄像头在 Linux 下通常被映射为 /dev/video0 设备。你可以用 V4L2 驱动来访问:
import cv2
# 打开 CSI 摄像头(通常设备号为 0)
cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
# 设置格式为 MJPG(减少带宽占用)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)
while True:
ret, frame = cap.read()
if not ret:
print("CSI 摄像头读取失败")
break
# 显示
cv2.imshow('CSI Camera', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这里有个细节:cv2.CAP_V4L2 是告诉 OpenCV 使用 V4L2 后端。如果不指定,OpenCV 可能默认用其他后端,导致 CSI 摄像头无法正常工作。我刚开始没加这个参数,折腾了半天才发现问题。
3.3.3 从图像文件读取
有时候我们做离线测试,不需要实时摄像头。直接从文件读取更简单:
import cv2
# 读取单张图片
img = cv2.imread('field_obstacle.jpg')
if img is None:
print("图片加载失败,检查路径")
else:
cv2.imshow('Test Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。我刚开始做可视化时,图像颜色全不对,就是这个原因。
3.4 小结
这一讲我们聊了摄像头选型的三个关键点:接口(USB vs CSI)、快门类型(全局 vs 卷帘)、以及 OpenCV 的基本读取方法。我个人觉得,对于农田障碍检测这个场景,全局快门 + CSI 接口是最稳妥的组合。虽然成本高一点,但稳定性和图像质量对算法的影响太大了。
下一讲我们会深入图像预处理——毕竟,摄像头拿到的原始图像,直接扔给算法是不行的。咱们得先做点「清洁工作」。