三、图像传感器与接口:摄像头选型与图像采集

好,咱们进入第三讲。这一讲我打算聊聊摄像头本身——说白了,就是农田里那双「眼睛」到底该怎么选、怎么接、怎么让它把图像数据送到咱们的处理器里。

我记得刚入行那会儿,总觉得摄像头嘛,能出图像就行。结果第一次在田间测试,阳光一强,画面全白;傍晚光线一暗,全是噪点。嗯,从那以后我才明白,选摄像头这事儿,真不能马虎。

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 延长线太长导致的。
我的经验: 做原型验证用 USB 摄像头没问题,但真要部署到农机上,我建议还是用 CSI 接口。稳定性不是一个量级的。

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 图像采集原理

图像采集这事儿,听起来简单——摄像头一拍,数据就来了。但实际底层流程比你想象的要复杂。

简单来说,图像采集分三步:

  1. 光电转换:CMOS 传感器上的每个像素点把光信号转成电信号(电压)。
  2. 模数转换(ADC):把模拟电压转成数字值(0-255 或 0-1023)。
  3. ISP 处理:对原始 RAW 数据进行去噪、白平衡、色彩校正、伽马校正等处理,最终输出标准的 RGB 或 YUV 图像。

这里有个关键点:很多 USB 摄像头内部已经集成了 ISP,你拿到的就是处理好的图像。但 CSI 摄像头有时只输出 RAW 数据,需要你自己在处理器上跑 ISP 算法。我刚开始用 CSI 摄像头时,拿到的图像是「灰蒙蒙」的,后来才发现是没做白平衡校正。

核心概念: 图像采集的「帧率」和「曝光时间」是互斥的。曝光时间越长,帧率越低。在农田强光环境下,我通常把曝光时间控制在 1-5 毫秒,帧率能跑到 30fps 以上。如果光线不足,就得降低帧率来保证曝光。

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()
注意: OpenCV 默认使用 BGR 色彩空间,不是 RGB。如果你用 matplotlib 显示,记得转换:img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。我刚开始做可视化时,图像颜色全不对,就是这个原因。

3.4 小结

这一讲我们聊了摄像头选型的三个关键点:接口(USB vs CSI)、快门类型(全局 vs 卷帘)、以及 OpenCV 的基本读取方法。我个人觉得,对于农田障碍检测这个场景,全局快门 + CSI 接口是最稳妥的组合。虽然成本高一点,但稳定性和图像质量对算法的影响太大了。

下一讲我们会深入图像预处理——毕竟,摄像头拿到的原始图像,直接扔给算法是不行的。咱们得先做点「清洁工作」。