
最近有一些需求要手搓一台长基线的双目相机,因为要考虑到移动场景,控制器恰好是树莓派Zero 2 W,所以就买了树莓派家的Global Shutter Camera,价格在一众全域快门相机里,算比较亲民切合理的了。这个相机可以通过配备的线缆与各种树莓派连接,支持硬触发,也兼容C/CS接口而连接不同的镜头。我选用了6mm的广角镜头。此相机基于SONY IMX296打造,分辨率“高达”1456 × 1088。虽然听起来很低,但是毕竟这是全域快门相机,在拍摄运动物品的时候没有任何“果冻效应”。而且在计算机视觉领域,分辨率并非越高越好。分辨率过高反而会因为处理难度上升而不得不降低分辨率。而且较低分辨率也能换来更好的暗光成像质量。所以对于运动的双目相机而言,这个相机是价格和性能相对而言较为平衡的了。(虽然只要几百块就可以买到一个成品的Intel RealSense双目相机,但是它不能满足我的需求)。总之,每台相机连接一个树莓派Zero 2 W,然后通过PoE和GPIO信号触发。 这里列一下相机的详细参数便于参考: 外形尺寸(Form factor):38 × 38 × 19.8 mm(含 29.5 mm 转接环和防尘盖) 重量(Weight):34 g(含转接环和防尘盖为 41 g) 传感器(Sensor):Sony IMX296LQR-C 分辨率(Resolution):1.58 百万像素(彩色) 传感器尺寸(Sensor size):对角线 6.3 mm 像素尺寸(Pixel size):3.45 μm × 3.45 μm 输出格式(Output):RAW10 镜头后焦距(Back focus length of lens):可调(12.5–22.4 mm) 镜头接口标准(Lens standards):CS 接口、C 接口(附带 C-CS 转接环) 红外截止滤镜(IR cut filter):内置 排线长度(Ribbon cable length):150 mm 随附配件(Included accessories):C-CS 接口转接环、螺丝刀 三脚架接口(Tripod mount):1/4”-20 视频模式(Video modes):最高支持 1456 × 1088 @ 60 Hz
前期准备
相机通过随包装附带的CSI线缆与树莓派相连,卡扣很好抠也很紧,比小厂做的好很多。附带的线长是200mm,对我的需求而言有些长,后续大概会定制更短的。相机有8个外部引脚,焊点非常非常小,而且很脆弱。(看官方的文档的照片,有一个焊盘也是坏掉的,笑死。)我在焊接后不小心弄掉了GND的焊盘。好在经过测量,相机与树莓派通过CSI线缆连接后,是共地的,因此焊盘掉了也不影响。另外值得注意的是,相机的四个螺丝口虽然也有很大的焊盘,但是他们其实是不接地的。如果使用外部触发,只需要焊接XTR接口即可。当然如果想的话,你也可以焊接GND,但是我选择直接从树莓派上引出。焊接后强烈建议立刻用热熔胶固定,因为焊盘非常的脆弱。
根据官方文档,此相机有不同的批次。某些批次需要去掉一个电阻才能使用外部硬触发。而恰好,我一起买的两台相机,是两个批次。而我只拆了一台,而拆的那台刚好是不需要去掉电阻的那一台。而上机测试的是需要去掉电阻的那一台。直到我拿出示波器,我才意识到原来问题出在相机上。因此如果使用硬触发,一定要拆开相机背的保护板检查,如果有额外的六条腿芯片,那么一定要移除其左上角的那颗电阻。如果不移除的话显然无法外部硬触发。而现象就是用示波器看它会把0V拉到接近0.8V。

左侧相机不需要移除额外电阻,右侧需要。

随后查阅文档发现,相机的触发要求电压是1.8V,而树莓派的GPIO的电压是3.3V。因此不管怎么样都需要做一个电阻分压来保护电路。好在相机耐压很宽,根据网上说法,有人长期用3.3V电压用于触发,也没有烧坏相机。官方建议使用1.5k和1.8k的电阻分压。我手头没有1.8k的,使用了1k和1.5k进行分压,得到的电压是接近2V,我认为既然短期可以耐压3.3V,长期使用2V问题不大。相机内部应该设计了保护电路,测试的时候就算使用3.3V,如果时间不长的话,应该问题也不大。(我排查问题的时候也接了很久的3.3V做测试,并没有烧坏。)另外根据官方文档,相机的触发是低电平触发,而不是常见的高电平触发。同时触发间隔为快门速度。因此需要通过代码来控制快门速度。写代码的时候不要写反了。
在正式试验前,还有三个小事情。
首先推荐设置相机的always-on。使用sudo vim(我喜欢,用nano什么的也无所谓)编辑/boot/firmware/config.txt文件,修改camera_auto_detect=0(不修改也行)然后在文件末尾插入dtoverlay=imx296,always-on。
其次推荐设置相机默认启动Slave模式。当然不设置的话,每次使用slave模式前都输入echo 1 | sudo tee /sys/module/imx296/parameters/trigger_mode也可以。我的话基本上都是使用外部触发,因此设置一下对我而言更方便。默认是没有文件的,使用sudo vim /etc/modprobe.d/imx296.conf,并在文件内插入options imx296 trigger_mode=1,这里mode1是Slave模式,0是Master模式。~~(是不是反了,不是)~~ 最后,相机默认设置的超时是1s。也就是处于Slave模式的相机在启动后1s内,如果没有接收到硬触发信号,就会报错然后重启相机。这是很不方便的。因此也需要修改。根据树莓派不同,路径也不同。路径是/usr/share/libcamera/pipeline/rpi/pisp/rpi_apps.yaml (Pi 5) 或者 /usr/share/libcamera/pipeline/rpi/vc4/rpi_apps.yaml (earlier Pis)。需要修改"camera_timeout_value_ms":字段,并将前面的注释符号删除。我这里设置为60000,也就是10分钟。其实很多人都建议设置为一个尽量大的数,比如9999999,这样相机就会一直等待你的操作了。当然都可以。总之一定要改,不然相机收不到信号就会不断报错然后重启。报错内容大概如下:
ERROR: Device timeout detected, attempting a restart!!! [0:44:48.072165744] [802] WARN V4L2 v4l2_videodevice.cpp:2100 /dev/video0[10:cap]: Dequeue timer of 1000000.00us has expired! [0:44:48.072438453] [802] ERROR RPI pipeline_base.cpp:1350 Camera frontend has timed out! [0:44:48.072549287] [802] ERROR RPI pipeline_base.cpp:1351 Please check that your camera sensor connector is attached securely. [0:44:48.072658350] [802] ERROR RPI pipeline_base.cpp:1352 Alternatively, try another cable and/or sensor. ERROR: Device timeout detected, attempting a restart!!!
现在所有事先准备都完成了,接下来就是接线和测试了。
接线和测试 最重要的是,共地。所有设备必须共地。当然如果你只有一个设备,那就无所谓了,甚至如果是自身触发自身的话,都不需要接GND。当然有硬触发需求的话,肯定是外部控制的对吧。信号线一定要接GND。随后就是连接GPIO与电阻。我这里使用python编写了一段测试代码,生成稳定的pwm信号。和另一段测试代码,敲一次回车发送一个触发信号。我使用了树莓派5的GPIO17用来输出,和GPIO27用来进行回环检测。你可以选择任意端口,但是记得更改代码。 from gpiozero import PWMOutputDevice, DigitalInputDevice from time import sleep, time import signal # --- 配置区域 --- TRIGGER_PIN = 17 # 发送信号引脚 (Output) ECHO_PIN = 27 # 回环检测引脚 (Input) -> 物理连接到 TRIGGER_PIN 的 3.3V 侧 TARGET_FPS = 30 # 目标帧率 (Hz) SHUTTER_US = 5000 # 曝光时间 (微秒) # --- 计算逻辑 --- frame_length_us = 1000000 / TARGET_FPS low_pulse_ratio = SHUTTER_US / frame_length_us high_pulse_duty_cycle = 1.0 - low_pulse_ratio # 安全限制 if high_pulse_duty_cycle < 0.01: print("错误:曝光时间太长,甚至超过了帧间隔!") exit() if high_pulse_duty_cycle > 0.999: print("错误:曝光时间太短!") exit() # --- 计数器变量 --- pulse_count = 0 def count_edge(): """中断回调函数""" global pulse_count pulse_count += 1 def main(): global pulse_count print("-" * 40) print(f" IMX296 Global Shutter 触发器 (带回环检测)") print("-" * 40) print(f"发送引脚: GPIO {TRIGGER_PIN}") print(f"检测引脚: GPIO {ECHO_PIN}") print(f"目标帧率: {TARGET_FPS} Hz") print(f"曝光时间: {SHUTTER_US} us") print(f"PWM占空比 (High): {high_pulse_duty_cycle:.2%}") print("-" * 40) # 1. 初始化触发器 (初始状态为 1/High/1.65V) trigger = PWMOutputDevice(TRIGGER_PIN, frequency=TARGET_FPS, initial_value=1) # 2. 初始化检测器 # pull_up=None: 悬空模式 # active_state=True: 高电平视为激活 echo = DigitalInputDevice(ECHO_PIN, pull_up=None, active_state=True) # 3. 绑定中断事件 # 检测下降沿 (从 Active 变成 Inactive 的瞬间,即开始曝光) echo.when_deactivated = count_edge try: # 开始输出信号 trigger.value = high_pulse_duty_cycle print("信号已发送,开始监测回环数据...\n") last_time = time() while True: # 每秒统计一次 sleep(1) now = time() duration = now - last_time # 计算实际测得的频率 measured_fps = pulse_count / duration # 简单的状态显示 status = "正常" if abs(measured_fps - TARGET_FPS) < 2 else "异常" print(f"[{status}] 目标: {TARGET_FPS} Hz | 实测: {measured_fps:.2f} Hz | 累计脉冲: {pulse_count}") # 重置计数器 pulse_count = 0 last_time = now except KeyboardInterrupt: print("\n\n 程序停止,关闭信号输出。") trigger.off() trigger.close() echo.close() if __name__ == "__main__": main()
另一个测试用代码。
from gpiozero import OutputDevice from time import sleep trigger = OutputDevice(17, initial_value=True) print("信号线已拉高 (Idle High)...") try: while True: input("按回车键触发一次拍照 (Pull Low)...") # 拉低开始曝光 trigger.off() # 保持低电平 5ms (曝光 5000us) sleep(0.005) # 拉高结束曝光,传输数据 trigger.on() print("脉冲发送完毕") except KeyboardInterrupt: trigger.close() 在实际接入相机前可以用万用表测试一下。因为空占比大概是25%(低电平为拍照),所以电压大概是3.3Vx0.85=2.8V左右。分压后电压大概是2.8Vx1.5/2.5=1.68V左右。
接入相机后,如果你想的话,可以使用ffmpg来同步监看。我这里的ip和端口可以根据情况更改。两台设备分别输入
rpicam-vid -t 0 --inline --codec h264 --width 1456 --height 1088 --gain 1.0 -o udp://192.168.2.1:5000 rpicam-vid -t 0 --inline --codec h264 --width 1456 --height 1088 --gain 1.0 -o udp://192.168.2.1:5001
监看端,确保已经安装ffmpg后,开两个终端分别输入
ffplay -fflags nobuffer -flags low_delay -framedrop udp://@:5000 ffplay -fflags nobuffer -flags low_delay -framedrop udp://@:5001
就可以实时监看两个摄像头的画面了。 不监看的话,可以直接输入 rpicam-hello -t 0 --qt-preview --shutter 3000 如果树莓派没有图形页面,就输入 rpicam-hello -t 0 --shutter 3000
如果没有报错,那就说明成功了。
至此硬件触发就完成了,接下来就是拿来用了。也许之后还会更新。那一颗电阻让我花了一周的时间(当然有四天其实在摸鱼)去寻找为什么会无法触发,甚至被迫去借示波器。真的是自己的不小心和想当然,给自己挖了很大的坑。