拓冰建站拓冰建站
首页 / 资讯中心 / 正文

地瓜RDK S100平台ISX031相机内参读取与标定实践

最近在调地瓜RDK S100平台上的多路相机其中一路就是Ofilm欧菲光的ISX031车载摄像头模组。按道理说这种车规级模组出厂时都会做标定内参应该已经写进模组里的OTP或者EEPROM了软件侧只需要把它读出来再用到感知管线里就行。但实际做起来才发现想从RDK S100上把ISX031的内参稳定读出来并不是一条命令就能搞定的事。这篇把这次完整踩过的链路梳理一遍从硬件连接、驱动识别到V4L2/I2C/ISP工具三条读取路径再到万一模组没写内参时怎么用OpenCV自己做标定兜底。希望对在RDK平台接ISX031或者其他车规相机的朋友有点帮助。1. 内参数据实际放在哪从模组OTP到Linux文件系统的完整链路1.1 内参到底是一组什么数据相机内参Intrinsics通常指两个层面的参数一个是内参矩阵里的焦距和光心也就是fx、fy、cx、cy另一个是畸变系数常见的有k1、k2、p1、p2、k3对应OpenCV里的径向畸变和切向畸变模型。对双目视觉、SLAM、目标测距这些应用来说内参错误的影响是系统性的同一个目标在图像坐标上明明对准了反算到相机坐标系就是偏的误差还会随着距离被放大。所以内参不是“锦上添花”的校准而是下游所有几何计算的地基。在ISX031这类车规模组上内参的“官方来源”通常是模组厂在出厂前做的一次完整标定。标定设备和产线流程决定了这些参数以某种二进制格式写入模组上的非易失存储区域。我们做平台集成的人要做的就是把这份出厂标定数据原样读出来并解析成算法能用的格式。1.2 OTP、EEPROM、驱动内置参数先分清哪个才是你要的很多人在这一步就栽了。因为“参数”这个词太宽泛ISX031模组里其实同时存在好几类参数Sensor OTP敏感度校准、坏点校正、白平衡校正等这些是Sony sensor级别的图像质量参数由驱动在初始化时自动加载跟几何内参没有直接关系。模组EEPROM部分模组会把镜头中心偏移、焦距、畸变系数也放进一颗独立EEPROM里这才是真正意义上的内参。驱动内置参数RDK S100的驱动代码里可能硬编码了某个分辨率下的cam_overlay、iq_param但这类参数主要是给ISP用的不是几何标定结果。平台配置文件有时候内参根本不在模组上而是由方案商在应用层提供一份固定yaml/JSON文件。我见过不少朋友拿着i2cdump把Sensor OTP全部拉出来然后辛辛苦苦找焦距方向就完全错了。必须先搞清楚当前模组的出厂内参到底写在哪个存储里。Ofilm ISX031的车规版本通常有独立EEPROM默认I2C地址可能是0x50或者0x54也有放在Sensor地址空间里的版本需要看模组规格书或者直接咨询原厂FAE。1.3 为什么内参不能只“读一次”就完事还有一个很典型的问题内参是绑定分辨率和使用状态的。ISX031如果以1920x1080输出标定结果是一套如果锁到1280x720或者输出一个裁剪过的ROI焦距和光心可能就变了裁剪时尤其明显。所以“读到内参”之后必须确认读到的参数对应哪个分辨率、哪个输出模式。否则每次切换sensor模式后上一份内参可能就不适用了。另外定焦镜头和变焦镜头不一样定焦模组内参相对稳定但热胀冷缩和镜头松动会导致内参缓慢漂移。车规应用每隔一段时间做一次重标定或者至少用在线校验方式检查主点偏移都是常规操作。在RDK S100这种边缘平台上做多相机系统时我习惯把内参做成独立于可执行程序的配置文件这样模组更换后只用替换对应配置文件不用重新编译。2. RDK S100上让ISX031先“被系统看见”2.1 硬件连接与CSI通道分配先说硬件。地瓜RDK S100开发板上一搬有多路CSI接口支持通过FPC排线接入多路sensor。ISX031这种车规摄像头模组一般是MIPI CSI-2输出也需要外部提供MCLK和供电。接线看起来简单但有个关键点RDK S100的CSI接口有通道属性不是随便插哪一路都能被同一个驱动识别。这次我用了底板上的CSI0通道对应设备树里的一路MIPI D-PHY。上电顺序也需要注意。最好让sensor先进入待机状态等系统完成摄像头驱动加载后再通过I2C发送初始化序列唤醒sensor。如果反向操作驱动枚举时可能抓不到ISX031的ID。排查时可以直接看模组背面标签确认供电电压ISX031常见的是模拟供电和数字I/O电压分离如果IO域电压配置不对I2C读寄存器时会出现无响应。2.2 设备树和驱动是否已经支持ISX031RDK S100通常跑的是Ubuntu 地瓜Linux内核摄像头驱动以模块方式加载媒体设备管理依赖Linux Media Controller框架。接好线后第一步是确认内核日志里有没有出现sensor的ID信息dmesg | grep -i isx031 dmesg | grep -i mipi如果内核日志里已经能看到isx031 0-001a: Detected ISX031之类的信息说明驱动已经识别。如果什么都没有先确认I2C设备是否枚举成功ls /dev/video*正常情况下每路sensor会生成一个/dev/videoX节点。RDK平台的多路相机可能会生成多个video节点需要跟media-ctl配合使用。用这条命令可以看完整的拓扑media-ctl -p从打印结果里能看到sensor实体、ISP聚合实体以及最终的video节点对应关系。这一步不是可选的因为多路相机环境下/dev/video0不一定是ISX031有可能是另一路摄像头把节点对应关系搞错后面所有读取都会串路。如果官方驱动里没有ISX031那就得自己移植。好在ISX031跟其他Sony sensor一样是标准MIPI I2C控制驱动主体通常复用sony系列的公共代码。先找RDK SDK里有没有isx031.c或者类似的驱动文件如果没有可以用imx219这类驱动作为模板替换掉寄存器序列和sensor ID。I2C地址也需要改ISX031常见地址是0x1A7位地址对应8位地址0x34。如果系统里挂了好几个模组可以通过设备树给每路分配独立的I2C总线或者复用地址。2.3 用V4L2确认sensor真的能出图驱动识别之后先不要急着读内参确认sensor能正常出图再谈参数。ISX031作为车规sensor默认输出可能不是你想要的格式需要先查看当前格式v4l2-ctl --device/dev/video0 --get-fmt-video这条命令会打印出当前像素格式、宽高、四个字符编码。读到YUV420或者SRGGB10都是正常的具体取决于上层ISP配置。如果只想验证图像采集用GStreamer拉流最直接gst-launch-1.0 v4l2src device/dev/video0 ! videoconvert ! ximagesink能在屏幕上看到实时画面才说明ISX031的驱动链路是完整的。这一步里我遇到过一个很隐蔽的问题驱动报了VIDIOC_S_FMT错误看起来是格式设置失败实际原因是sensor还没有退出待机状态MIPI没有data lane时钟。给驱动加一个延迟初始化序列等MCLK稳定后再拉流就正常了。3. 读内参的三条路径V4L2、I2C和ISP工具链3.1 路径一V4L2扩展控制接口最简单但最不通用ISX031这类sensor驱动里一般会通过V4L2 control机制暴露一部分寄存器。如果你运气好驱动工程师把内参相关项做成了扩展控制那么直接就能看到v4l2-ctl --device/dev/video0 --list-ctrls看到类似focus_abs、focal_length、x_offset、y_offset这些自定义控制项就用--get-ctrl去读。比如v4l2-ctl --device/dev/video0 --get-ctrl focal_length但我必须说V4L2标准控制里并没有“畸变系数”这种概念所以这条路通常是厂商为了方便调试临时加的不一定所有SDK版本都有。在内参读取这块V4L2扩展控制更适合做“快速验证”不能当成唯一依赖。真正要稳定拿到内参得看驱动是否把模组EEPROM的内容映射到了某个文件节点。有些平台会通过v4l2-subdev的只读控件导出eeprom_dump用media-ctl指定subdev后配合v4l2-ctl --set-ctrl把数据抓下来。RDK S100对ISX031的驱动不一定默认开这个功能没有的话就得走I2C。3.2 路径二直接访问模组EEPROM/OTP最通用也最需要小心I2C是最后的硬通道。无论上层封装成什么样内参数据最终都在存储介质上I2C一定能读。前提是找到正确的总线、设备地址和寄存器偏移。先从系统层把所有I2C总线列出来i2cdetect -lRDK S100上每个I2C controller对应一条总线sensor的I2C控制也可能是挂在某一条总线上。拿i2cdetect扫一下地址注意不要用可能写坏设备的参数只用不写入的检测模式i2cdetect -y -r bus如果ISX031驱动已经加载模组的控制地址应该能在检测列表里看到。EEPROM地址一般在0x50到0x57之间。ISX031的sensor控制地址是0x1A的话EEPROM可能是0x50。看到0x50设备后直接dumpi2cdump -y bus 0x50I2C dump出来是一堆hex不能直接当内参用。要知道字节序和字段布局必须拿到Ofilm的“OTP/EEPROM MAP”文档。如果没有文档一个笨办法是先记录一个“已知好用的模组”和“需要对比的模组”的完整dump然后逐字节做差。内参在出厂时属于固定写入区差异大概率集中在生产序列号、时间戳和个别校验位上焦距和光心通常不会和序列号混在一起。但是这里要特别提醒一下I2C访问EEPROM时访问粒度可能不是一次一字节。部分车规EEPROM需要按16位地址、32字节页来读尤其是超过2Kbit容量的存储。直接i2cdump遇到跨页读取时可能会读到0xFF不是真数据。比较稳妥的方式是写一个小工具按固定偏移区间每次读16字节拼接后再解析。从模组手册拿到地址映射之后用i2cget单点验证就可以。千万别试着往OTP区域写入任何内容。OTP是一次性可编程的驱动初始化过程可能也有类似的写保护逻辑乱写轻则损坏标定数据重则让模组直接废掉。我做板级调试时一向只读不写写操作全部先拿一块验证板上实验。3.3 路径三ISP工具链和地瓜SDK里的“隐藏接口”地瓜RDK S100的软件栈里其实给摄像头做了一套封装不只是底层Linux驱动还有libcam、hobot_camera这类上层库。在一些版本的SDK里内参已经在上电加载过程中被驱动读取并缓存到内存了只是没有直接暴露给用户。这时候可以尝试调用官方工具链里的get_camera_intrinsics接口或者在ROS2示例里检索“camera_info”话题ros2 topic echo /camera_infoROS的CameraInfo消息里包含了K矩阵和D畸变系数如果发布节点内部已经实现了内参加载直接订阅这个话题就能拿到解析好的数据比从I2C裸读再解析效率高得多。这也是我推荐优先排查的方向因为SDK内部加载OTP时通常已经处理了校验和字节序省掉很多麻烦。如果SDK里没有现成接口还可以看ISP工具链地瓜平台自带ISP校准工具一般支持“读取Sensor OTP”和“导入标定文件”两个动作。打开工具后选择ISX031对应的sensor配置它会尝试从模组里读回标定块并显示解析结果。这个方式对不熟悉I2C的人最友好但前提是工具版本要和sensor驱动匹配。我用的SDK版本里这条路径读出来的数据是老版本工具生成的里面fx的单位不是像素而是um转换时差点翻车。为了更直观地对比三条路径的适用场景我整理了一个表格路径难度风险适用场景V4L2扩展控制低低但依赖驱动实现快速验证、调试阶段I2C直接读EEPROM中中地址/字节序易错有写坏风险驱动不支持、需要原始数据ISP工具链/ROS接口低低但依赖SDK版本应用集成、最终产品开发4. 当模组没写内参时自己标定的完整实操4.1 先判断是不是真的没写内参有时候不是模组里没写而是你还没找到正确的解析方式。我建议在动手标定之前先用原始数据做个简单检查EEPROM头几个字节如果不是全0xFF或者全0x00同时存在某种连续的、规律性的数据块那大概率就是有效标定数据只是解析格式未知。如果整个EEPROM都是0xFF或者只有几个字节有值那才需要做好自己标定的心理准备。还有一种“半残”情况模组写了内参但只写了焦距和光心没有写畸变系数。对广角镜头来说这种情况拿到手也不能直接用必须重新标定。ISX031如果配了广角镜头畸变会很明显不能只靠内参矩阵。4.2 自己标定的器材准备和采集建议自己能做的是用标定板和OpenCV做离线标定。先准备一块足够大的棋盘格最好是A2/A3尺寸格子数量在9x6或10x7比较合适。不要用印刷在普通A4纸上的小棋盘格边缘卷曲会让角点检测位置产生系统性偏差。直接去图文店用PVC板或者KT板打印硬一点才好。拍摄数量上最少15到20张角度要覆盖正对、左偏、右偏、上倾、下倾还要有不同距离。距离不能太近让棋盘格尽量充满画面的一半以上但又不能出画面边缘太多。光照要均匀避免反光和阴影。RDK S100拉流时会叠加ISP处理尽量关闭自动曝光否则角点检测时亮度一直在跳标定结果不稳定。4.3 OpenCV标定脚本要点标定用Python和OpenCV最省事。核心步骤是交替调用cv2.findChessboardCorners和cv2.calibrateCamera。采集图像时先检测角点只有所有内角点都检测到才保存这一帧。一个简化的参考流程如下import cv2 import numpy as np import glob pattern_size (9, 6) # 内部角点数 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objpoints [] imgpoints [] for fname in sorted(glob.glob(/data/calib/*.jpg)): img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) objpoints.append(objp) imgpoints.append(corners) else: print(fskip {fname}) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None ) print(mtx , mtx) print(dist , dist)这里有两个细节值得说。第一pattern_size填的是棋盘格内部角点数不是格子总数9x6的棋盘格实际是10x7个格子这个搞错以后角点怎么都配不上。第二cornerSubPix是角点亚像素细化跳过这一步标定出来的焦距和主点精度会差不少。对ISX031这种车规级sensor来说主点精度做到半个像素以下才放心。4.4 标定模型的坑普通针孔模型还是鱼眼模型OpenCV里默认的calibrateCamera使用的是针孔模型畸变模型是k1, k2, p1, p2, k3。如果你的ISX031模组配的是普通6mm镜头针孔模型就够用。但如果镜头视场角大于120°甚至到了140°以上普通模型就会出现“边缘压不住”的问题重投影误差始终大于1像素。这时候该用cv2.fisheye.calibrateimport cv2 import numpy as np K np.zeros((3, 3)) D np.zeros((4, 1)) rvecs [np.zeros((1, 1, 3), dtypenp.float64) for _ in range(len(objpoints))] tvecs [np.zeros((1, 1, 3), dtypenp.float64) for _ in range(len(objpoints))] rms, K, D, rvecs, tvecs cv2.fisheye.calibrate( objpoints, imgpoints, gray.shape[::-1], K, D, rvecs, tvecs, cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC cv2.fisheye.CALIB_CHECK_COND )选模型时不要贪多能把重投影误差降到0.3像素以下的模型就是合适的模型。鱼眼模型参数更多在数据不够好时反而更容易过拟合。我的经验是先用普通针孔模型跑一遍看重投影误差如果都小于0.5像素就不用换鱼眼模型。4.5 把标定结果变成下游能用的配置文件标定完成后不能只存到终端里要转成固定格式。如果你用的是ROS可以保存成CameraInfo的yaml格式image_width: 1920 image_height: 1080 camera_name: isx031_front camera_matrix: rows: 3 cols: 3 data: [fx, 0, cx, 0, fy, cy, 0, 0, 1] distortion_model: plumb_bob distortion_coefficients: rows: 1 cols: 5 data: [k1, k2, p1, p2, k3]注意单位。OpenCV输出的焦距单位是像素直接填入K矩阵没问题。但如果下游计算需要物理焦距就要再除以像素尺寸这个转换关系来自sensor的像素物理尺寸不是随便填的。RDK S100上的应用一般都需要归一化坐标直接把K矩阵填进去就好。5. 我踩过的坑和排查建议5.1 I2C总线上读不到EEPROM问题出在设备树复用这次调试ISX031时I2C总是扫不到EEPROM。驱动日志说sensor是正常的sensor地址也能读到但0x50一直不出来。排查到最后发现设备树里那路I2C总线同时挂了两个器件其中一个占用了0x50地址。把EEPROM的设备树节点改成通过不同的I2C mux通道访问后地址才不再冲突。这也给了一个经验多路相机共用一条I2C总线非常常见EEPROM地址又都喜欢默认0x50如果有两路相同模组地址就冲突了。RDK S100的多路CSI通常会对应多路I2C控制总线接不同通道时确认下i2cdetect -l里具体是哪条总线别想当然认为0x50一定在总线上。5.2 读出来的数据看起来合理其实是驱动初始化覆盖过的还有一个更隐蔽的问题。直接用I2C工具读到的0x50区域数据可能是驱动加载时把EEPROM内容读到内存、然后经过软件层重新映射后的结果。也就是说设备树里配置的read-only属性没有真正生效或者驱动的eeprom节点只是模拟出来的。这种情况下读到的数据和模组上真正的OTP原始内容不一定完全相同。对比办法很简单断掉驱动自动加载在系统启动早期sensor驱动还没probe时直接用I2C工具去读看两个时间点的dump是否一致。如果一致说明原始数据没被污染如果不一致说明驱动初始化过程对数据做过偏移或预处理。此时就要以驱动处理后的数据为准因为后续ISP加载用的就是这份处理过的结果。5.3 分辨率切换后内参全部“失效”不是数据错了而是模式变了我在RDK S100上验证过ISX031模组从1920x1080切到1920x960后V4L2设置的是裁剪输出。这时光心坐标会变化因为输出图像左侧部分被剪掉了cx自然要减去裁剪偏移。但有人一测发现OpenCV对标出来的内参和EEPROM里存的不一样就以为是数据存储错误其实不是是输出模式变了。车规模组通常支持多种曝光模式、binning、裁剪不同模式下内参不同。EEPROM里一般只存一组默认分辨率下的出厂内参做应用时要么固定分辨率要么针对每种分辨率单独建立内参索引。这一点在方案设计阶段就要确定否则后续软件层会非常痛苦。5.4 如果EEPROM里读出来的参数看起来“太完美”留个心眼还有一个经历也值得提。从一个ISX031模组的EEPROM里读到的焦距和主点精度高得吓人重投影误差几乎为零。后来和Ofilm那边的人确认那是产线上用高精度转台标定的结果确实准。但另一批模组同样地址读取出来的数据明显偏差很大原因是产线换了镜头型号后没有更新标定数据。这说明出厂内参并不是绝对可信尤其是换过镜头、修过模组的批次。对于关键应用我现在的习惯是从模组里读到内参后先不做任何剧情假设直接用这份内参跑一次实际图像的重投影检查。怎么检查找一个已知尺寸的棋盘格拍一帧静态图提取角点后用读取到的内参去计算棋盘格的角点位置看看重投影到像素坐标后和检测到的角点差多少像素。如果误差在0.5像素内就放心用如果大于1像素不管EEPROM里写得多么漂亮都以实际重标定为准。5.5 一个加强稳定性的建议把内参读取封装成独立服务最后给一个工程上的建议。RDK S100上跑的不只是一个读内参的小工具后面还挂着感知、规划、显示等一堆进程。如果每个进程都自己去I2C读EEPROM一方面启动时连续访问I2C很容易卡住另一方面解析逻辑重复维护也会麻烦。我通常会把“内参读取”封装成一个独立的配置服务启动时检测参数文件是否存在存在就直接加载不存在才去触发I2C或者OTP读取流程读取成功后写进json作为缓存。缓存的配置文件最好再加上模组序列号、读取时间、固件版本这些元信息。这样以后更换模组时可以通过序列号自动匹配内参文件不需要重新标定。对生产环境来说这个很小的设计能省掉很多现场调试时间。这次在RDK S100上处理ISX031相机内参整体感受是平台层面的摄像头驱动已经越来越完善但内参读取这个环节仍然是“半开放”状态很多信息只存在于模组厂的非公开文档里。最可靠的路线还是先向Ofilm或者地瓜的FAE确认EEPROM地址和数据格式然后用I2C读原始数据、按文档解析同时把OpenCV标定作为兜底方案。别一上来就硬刚寄存器先把SDK提供的工具链和ROS接口摸一遍很多时候官方接口早就把复杂逻辑封装好了直接调用反而更稳。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门