1. 为什么需要"简易"摄像头测试工具:产线、维修与交付的共性痛点
1.1 一个典型的打杂场景
我做摄像头相关的事情有段时间了,最烦的从来不是算法调参,也不是驱动移植,而是"帮我看一下这个摄像头是不是好的"。这句话我在不同场合听了无数遍:硬件同事拿了一块新板子,想确认 sensor 有没有被正确初始化;采购那边到了一批货,想知道有没有运输损坏;维修同事修完一台设备,怕装回去之后画面有问题;甚至隔壁工位的同学临时要测一台USB摄像头,只想知道"能不能出图、颜色对不对"。
你去翻市面上现成的测试软件,要么是厂商调试工具,功能确实全,但只支持自家sensor,换个型号直接不认;要么是通用播放器或者视频会议软件,能出画面,但分辨率、帧率、曝光状态这些关键信息全被隐藏了;还有一种是工业级的综合测试仪,几万块一台,功能无可挑剔,可为一个几百块的模组测试去上这种设备,项目预算经不起这么造。来回折腾了几次之后,我下定决心自己写一个摄像头简易测试工具,它不追求全功能,只解决一个核心问题:快速、可靠、无脑地判断"这颗摄像头到底行不行"。
这个工具最终在我手头变成了一套同时支持USB摄像头、板载CSI摄像头、以及RTSP网络摄像头的统一测试入口。界面极简,测试项固定,报告自动输出,任何一个没有技术背景的人,只要按照屏幕提示操作,五分钟内就能完成对一台设备的检测。这篇文章就是把整个工具从需求梳理、功能设计、代码实现到踩坑排查的过程完整拆开来,把每一步为什么这样做讲清楚,给同样被"帮我看看摄像头坏了没"困扰的朋友提供一个可以直接抄作业的方案。
1.2 简易不等于简陋:工具的三层边界
做这个工具之前,我先把需求认真捋了一遍。既然是通用测试工具,它就得同时满足几类人的需要:产线工人不关心技术细节,只要"绿勾/红叉";硬件工程师希望看到具体参数和日志;验收人员需要留下测试记录。不同人看重的信息层级完全不同,这是设计的第一条原则:分层展示,主结论优先,细节可展开。
第二条原则是判定规则必须直观可解释。摄像头画质好坏这件事,主观成分很高,同一个画面两个人能看出截然相反的结论。工具能做的是把画质拆成可量化的指标:分辨率是否达标、帧率是否稳定、坏点是否超限、灰阶是否能够分辨、色彩是否明显偏色。每个指标都有明确阈值,超了就是不合格,不靠人来拍脑袋。
第三条原则是工具必须与环境解耦。同样一颗摄像头,在室内自然光下和暗箱环境下测出来的曝光、白平衡数据完全不同。所以测试工具本身只负责采集和对比基线,环境差异由测试条件规范去约束,而不是靠工具内部"智能补偿"。这个边界如果不划清楚,工具会越写越复杂,最后变成一个谁都不敢改、调一个参数要翻三篇文档的怪物。
1.3 谁需要这个工具
我总结下来,有四类场景最常用到这种简易测试工具:第一类是小批量硬件组装与验收,比如几十台到几百台的设备到货,确认每台摄像头能否正常工作;第二类是维修后的功能复核,换过排线、重焊过座子之后,不必整机跑完整功能测试,单测摄像头即可快速判定;第三类是研发阶段的基础功能验证,驱动改了一版、sensor寄存器参数调整之后,先跑一遍工具确认基本功能没被破坏;第四类是教学和演示场景,给刚入行的同学演示摄像头采集链路,用这个工具比看满屏API文档直观得多。
工具开发完成后,我自己在至少四五个不同的项目里复用过,每次只要把设备枚举方式改一改,核心检测逻辑基本原封不动。
2. 测试项设计:一张表看清楚单测该管哪些事
2.1 基础采集层:出图、枚举和分辨率
一台摄像头接上电脑之后,最基础的问题是"系统能不能看到它、能不能让画面跑起来"。这一层是所有上层测试的前提,如果设备枚举都失败,后面的一切免谈。基础采集层我设置了三个检测点:
- 设备枚举:操作系统层面能否识别到该设备,VID/PID是否正确;
- 出图能力:能否成功打开视频流并持续获取帧;
- 分辨率匹配:驱动上报的分辨率列表是否和sensor规格一致。
在实际实现时,出图能力用持续采集100帧来验证,只要前20帧内有连续稳定的有效帧就算通过。分辨率则读取设备支持的所有模式,与配置文件里的期望值做比对。这一步很多人会忽略,等到后面画质测试时才发现分辨率不对,再回头排查就浪费时间了。
2.2 画质判断层:从主观打分到客观阈值
基础层通过之后,进入画质判断。我把画质拆成五个维度,每个维度都有明确的判定方式:
| 检测维度 | 测试方式 | 判定规则 |
|---|---|---|
| 坏点检测 | 拍摄均匀白板,统计异常亮点/暗点 | 固定位置连续5帧异常则计为坏点,允许2个以内 |
| 灰阶分辨 | 观察灰阶测试卡,统计可分辨的灰阶等级 | 可分辨灰阶数不低于8级(8bit图) |
| 色彩倾向 | 拍摄标准色卡,对比中性灰区域RGB均值 | 任意通道偏差不超过读取白平衡误差范围+10 |
| 对焦状态 | 拍摄带有纹理的测试卡,计算拉普拉斯方差 | 清晰度评分低于经验阈值为疑似失焦 |
| 帧率稳定性 | 统计连续300帧的帧间隔,计算变异系数 | CV值大于10%判定为帧率波动异常 |
有人可能会问,坏点检测为什么规定"拍白板",而不是直接对着桌面拍?因为坏点检测的本质是找sensor自身的像素响应异常,这个异常与入射光强没有关系,只有在均匀照明下,正常像素的响应应当完全一致,坏点才会凸显出来。你在杂乱的画面里去判断某个点是不是坏点,噪声分不清是不是场景真实纹理,误报率会直线上升。
灰阶分辨这个指标很多人不重视,实际上它是判断摄像头动态范围是否缩水的快速手段。8bit sensor理论上至少能分辨出256个灰阶中的大部分,如果画面存在严重的对比度压缩或者gamma异常,可分辨灰阶数会明显掉到五六级以下,人眼看小图可能只觉得"画面灰蒙蒙的",但工具能直接给出量化结果。
2.3 工作状态层:帧率、曝光与自动参数
很多摄像头实际使用中"看起来正常",但工作状态其实并不健康。最典型的表现是帧率虚高或者虚低。USB摄像头受带宽影响极大,尤其当你同时挂了多个UVC设备时,某个设备的带宽可能被压缩到只有标称帧率的四分之一,画面变得很卡,而任务管理器里看起来一切正常。工具需要统计真实帧间隔,而不是读取驱动上报的标称帧率。
曝光稳定性也是隐藏雷区。我遇到过一颗摄像头,静止画面对焦、色彩都正常,但每次自动曝光收敛之后又开始缓慢漂移,画面一会亮一会暗。如果只测静态帧完全发现不了,必须用统计方式观察连续若干帧的平均亮度变化曲线。工具里我设计了一个"亮度稳定性"检查项:连续采集150帧,计算每帧Y通道均值,如果相邻帧亮度跳变超过10个灰阶且持续震荡超过30帧,就报"自动曝光异常"。
2.4 工具不做的事:边界与取舍
再强调一遍工具不做什么:不做成像质量全流程评估,不做sensor与镜头的匹配分析,不做多摄像头同步精度测试。这些属于光学实验室的范畴,需要专用光源、测试卡和测量设备,硬塞进一个通用工具里只会让逻辑变得脆弱。我的原则是:工具只做"确定性问题"的测试——好就是好,不好就是不好,不搞模糊地带。那些"画质有点差,但说不上哪里差"的问题,留给人工通过画面缩略图去主观判断。
为了避免主观判断过于随意,我在工具里还加了一个被人说"多此一举"的功能:测试过程自动截图存档。每次测试结束,自动把检测时刻的画面保存到以设备ID+时间戳命名的文件夹里。后来说"这个镜头好像有问题"的时候,翻出截图一看,画面里确实有明显的镜头眩光,但工具测的是sensor性能,镜头瑕疵不是它的管辖范围。截图存档这个动作挽救了不止一次扯皮。
3. 用Python+OpenCV搭最小原型:核心代码与选型理由
3.1 技术选型:为什么是Python+OpenCV
确定功能边界后,我面临一个选型问题:用什么语言和框架来实现。当时我有几个候选:C++调用DirectShow/UVC驱动接口、Python+OpenCV、还有一些现成的硬件测试框架。最终选了Python+OpenCV,理由其实很朴素:生态成熟,调试速度快,跨平台,OpenCV的VideoCapture对USB摄像头和CSI摄像头的抽象足够好。C++性能确实好,但开发周期长,改起来麻烦,对这个场景属于杀鸡用牛刀。
这里我要特别说一句:VideoCapture用起来简单,不代表底层简单。它封装了V4L2/UVC协议层的复杂交互,你不需要自己处理USB控制传输、流控协商这些细节。正因为它封装得深,一旦遇到问题,排查起来会直接面对大量底层机制,这反而是好事——逼着你理解采集链路,而不是停留在"能出图就行"的层面上。后面第四节会详细讲我踩过的底层坑。
3.2 采集初始化的关键代码:设备索引与环境变量
工具的第一步是枚举设备并打开视频流。OpenCV里面最经典的写法是cv2.VideoCapture(0),表示打开第0号摄像头。但这里有个大坑:设备索引是运行时分配的,插拔顺序变了索引就变了。我的做法是先用cv2.CaptureDeviceList()(不同版本接口略有差异,Windows下为MSMF后端)列出所有设备名,再让用户通过界面选择或者通过命令行参数指定设备ID。
import cv2 import numpy as np import time import csv import os from datetime import datetime def open_camera(device_id, width=1920, height=1080, fps=30): """打开指定索引的摄像头,并尝试设置采集参数""" cap = cv2.VideoCapture(device_id, cv2.CAP_DSHOW) # 设置分辨率与帧率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) cap.set(cv2.CAP_PROP_FPS, fps) # 验证实际参数 actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) actual_fps = cap.get(cv2.CAP_PROP_FPS) print(f"实际采集参数: {actual_width}x{actual_height} @ {actual_fps:.1f}fps") if not cap.isOpened(): raise RuntimeError(f"设备 {device_id} 打开失败") return cap在Windows下使用cv2.CAP_DSHOW后端和默认后端的行为差异巨大。默认后端是MSMF(Media Foundation),对某些老式UVC摄像头兼容性差,会出现打开极慢、分辨率异常、帧率锁死等问题。DSHOW(DirectShow)虽然老,但兼容性意外地好,几乎所有的UVC摄像头都支持,而且打开速度快。后续实测中也发现,对于需要热插拔的场景,DSHOW后端的容错性更好。
3.3 帧率稳定性检测:别被驱动上报的值骗了
帧率检测模块是这个工具里最重要的部分之一,它的逻辑很简单:记录每一帧返回的时间戳,计算相邻两帧的时间间隔,统计间隔的均值、标准差和变异系数。变异系数就是标准差除以均值,用百分比表示,数值越小说明帧间隔越均匀。
def check_frame_rate(cap, sample_frames=300): """通过采集时间戳统计真实帧率与稳定性""" timestamps = [] # 存储每帧到达的时间戳 valid_frames = 0 for _ in range(sample_frames): ret, frame = cap.read() if not ret: continue valid_frames += 1 timestamps.append(time.perf_counter()) if valid_frames < 10: return {"status": "ERROR", "msg": "有效帧不足"} intervals = [] for i in range(1, len(timestamps)): intervals.append(timestamps[i] - timestamps[i-1]) mean_interval = np.mean(intervals) std_interval = np.std(intervals) cv_value = (std_interval / mean_interval) * 100 if mean_interval > 0 else 999 real_fps = 1.0 / mean_interval if mean_interval > 0 else 0 return { "status": "PASS" if cv_value < 10 else "WARN", "real_fps": round(real_fps, 2), "cv_value": round(cv_value, 2), "total_frames": valid_frames }这里有一个容易被忽略的细节:时间戳应该用time.perf_counter()而不是time.time()。time.time()是基于系统时钟的,如果你的系统时钟被NTP同步调整过(比如某开发板使用网络同步),测出来的帧间隔会出现瞬间跳变,导致误报帧率不稳定。perf_counter()是单调递增的性能计数器,不受时钟调整影响。
3.4 坏点与灰阶检测:均匀照明是前提
坏点检测算法本身不复杂:拍一张均匀白板图像,转成灰度图,统计显著偏离邻域均值的像素点。实现上有两个关键细节。一是要拍多帧取平均值,降低随机噪声的影响;二是要与周围邻域对比而不是与全局均值对比,因为镜头边缘的暗角效应会导致全局亮度不均,直接全局阈值会把暗角边缘误判成坏点。
def detect_dead_pixel(image_gray, threshold=45): """基于局部邻域灰度差的坏点检测""" blurred = cv2.GaussianBlur(image_gray, (5, 5), 0) diff = cv2.absdiff(image_gray, blurred) # 标记显著偏离邻域的像素,连续多帧确认 mask = (diff > threshold).astype(np.uint8) * 255 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bad_points = [] for cnt in contours: area = cv2.contourArea(cnt) if 0 < area <= 4: # 单像素级别异常 x, y = cnt[0][0][0], cnt[0][0][1] bad_points.append((x, y, area)) return bad_points灰阶分辨检测需要一个标准灰阶测试卡。测试卡上有若干级灰阶色块,从纯黑到纯白。算法统计摄像头画面里各个色块的平均灰度值,计算相邻灰阶的差值,如果两个相邻灰阶在灰度直方图上完全重叠(差值小于5),则判定该灰阶等级无法分辨。灰阶分辨数低于8级,我会判定动态范围明显不足,需要人工进一步确认是sensor设置问题还是硬件问题。
3.5 测试结果输出:CSV记录和截图存档
每次测试完毕,工具自动生成一份CSV格式的测试报告。字段包括:设备ID、测试时间、各项检测结果、关键指标数值、截图文件路径。这样的设计是为了让工具可以被集成到更上层的数据系统里,比如工厂的MES系统可以直接读取CSV做统计分析。对于开发和调试场景,我建议同时把结果实时打印到终端,这样不需要打开文件就能看到当前设备的健康状态。
def save_report(device_id, results, screenshot_path): """将测试结果写入CSV报告""" report_file = f"report_{device_id}_{datetime.now():%Y%m%d_%H%M%S}.csv" with open(report_file, mode="w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["项目", "结果", "备注"]) for item in results: writer.writerow([item["name"], item["status"], item["detail"]]) writer.writerow(["截图路径", screenshot_path, ""]) print(f"测试报告已保存: {report_file}")输出这份报告的时候,我加了一行备注栏。这是血的教训:最早版本只有"PASS/FAIL",没有备注,后来同一个型号的摄像头连续报坏点超标,我硬是找不到原因。加上备注之后才发现,那批镜头表面镀膜有瑕疵,在均匀白板条件下整个画面都存在微弱的膜层干涉条纹,算法把条纹误判成了大量"坏点"。备注栏把当时的曝光参数、照明条件、测试卡型号都记录下来,定位问题的时间从几个小时缩短到了几分钟。
4. 实测中的四个顽固问题与排查全程
4.1 设备索引漂移:不是bug,是Windows设备管理机制
工具第一版做完,在一个有四个USB摄像头的测试台上跑,我发现了第一个问题:每次设备重新拔插之后,VideoCapture(0)打开的可能不再是原来那个设备,甚至有时候打开失败。这其实是Windows设备枚举的动态分配机制在起作用,USB控制器为每个设备分配的下线索引是随插入顺序变化的。
解决办法是依靠设备的唯一标识来匹配。在Windows下,通过WMI接口可以查询摄像头设备实例ID或者硬件ID路径(如USB\VID_0C45&PID_6520\12345678),这个路径对同一个物理设备是稳定的。工具里加了一个--list-devices参数,把每个摄像头对应的设备实例ID和描述打印出来,用户通过界面明确指定要测试哪个设备。看起来是个很小的问题,但如果不处理,产线上一台机器插两三个摄像头时,测试员根本不知道自己在测的是哪个。
def list_uvc_devices_windows(): """通过WMI查询当前系统内所有UVC设备信息""" import subprocess, json result = subprocess.run( ["powershell", "-Command", "Get-CimInstance Win32_PnPEntity | " "Where-Object {$_.PNPClass -eq 'Camera'} | " "Select-Object Name, DeviceID, Status | ConvertTo-Json"], capture_output=True, text=True, check=True ) data = json.loads(result.stdout) if isinstance(data, dict): data = [data] return [(d["Name"], d["DeviceID"], d["Status"]) for d in data]4.2 帧率"看起来"达标,但实际丢帧严重
帧率检测模块刚上线时有个奇怪现象:统计结果CV值很小,帧率也接近标称值,但实际录制出来的视频明显卡顿。后来我把单帧时间戳打出来才发现,虽然平均帧间隔正常,但帧间隔呈周期性突变:通常是连续20帧间隔均匀(约3.3ms,对应30fps),然会突然出现一次380ms的停顿,然后再恢复正常。平均下来帧率看起来是准的,变异系数也不高,但人眼对周期性卡顿极其敏感。
原因在于USB带宽竞争和缓冲区机制:当多个摄像头同时占满同一USB控制器的带宽时,某个设备会在排队等待传输。缓冲区里已经存了一些帧,所以程序读取时不会阻塞,但一旦缓冲区被耗尽,camera需要等待下一个可用带宽窗口,就会出现周期性停顿。但从应用层看,读取操作仍然几乎瞬时完成,因为数据是从缓冲区里拿的。
解决思路是在帧间隔检测中增加"最大间隔"指标。即使平均帧率正常,只要相邻帧间隔的最大值超过25ms,就报"帧率不稳定"。同时允许用户通过参数指定更严格的阈值,比如针对需要视频通话级别的应用,最大帧间隔超过50ms直接判定失败。这个教训说明,单一统计量永远是有盲区的,做检测就要把最坏情况也纳入考量。
4.3 坏点检测误报:把暗角和灰尘误判成坏点
坏点检测模块最初用的方案是全局灰度阈值:整帧灰度图的均值减去某个固定值作为阈值,低于阈值的像素视为坏点。这个方案在均匀照明下问题不大,但一旦使用了廉价镜头,画面边缘会出现严重的暗角,边缘区域整体比中心暗30%以上。结果就是边缘的每一个正常像素都被判定为"暗点",一张图上能报出上千个坏点。
我改成基于高斯模糊的邻域对比方案之后,情况好了很多,但还是存在两个误报源。第一个是灰尘颗粒:sensor表面或者镜头镜片上的灰尘,在画面上呈现为固定的小黑点或小亮点,灰度特征和坏点非常接近。工具本身无法区分灰尘和坏点,但灰尘通常有轻微的面积和模糊边界,而真实坏点通常是锐利的单像素。我用轮廓面积做了过滤,只统计面积小于等于4像素的异常点。第二个误报源是镜片内的反射鬼影,这种通常面积大、形状不规则,不会混入坏点统计,但仍需要人工通过截图确认。
这里的关键认知是:测试工具的价值不在于"完美判定",而在于"减少人工注意力的消耗"。即便它不能百分之百区分灰尘和坏点,把可疑区域在截图里用红框标出来,人工复检时只需要看红框所在位置即可。
4.4 白平衡测试的"假阳性":不同色温下陷入死循环
最后说一个跟画质指标相关的坑。最初的白平衡检测逻辑是:采集一帧标准灰卡图像,将RGB三通道均值归一化,计算出当前色温与参考色温的偏差。偏差小于阈值就判定正常。但实际测试中发现,某些摄像头在检测时正好处于自动白平衡收敛过程中,第一帧偏差很大,第二帧接近正常,第三帧又偏了——整个收敛过程长达好几秒,导致工具反复报错。
我后来改为连续采集30帧,计算每帧色温偏差的均值和收敛趋势:如果前10帧偏差较大,但后续20帧偏差逐步缩小且最终稳定在阈值以内,就判定"白平衡收敛正常"。如果最终偏差仍然超限,再考虑是否为光源色温超出sensor校正范围。这个逻辑改动之后,测试稳定性大幅提升,几乎不再出现"十分钟前测是好的,十分钟后测就挂了"的诡异情况。
提示:做任何摄像头测试工具,都要留一个"人工复核"的出口。机器可以帮你过滤掉95%的正常设备,但剩下5%的边界情况永远需要人眼判断。自动化不是为了取代人,而是让人把精力花在真正值得关注的异常上。
5. 从单测走向批量化:产测脚本与数据闭环
5.1 批次管理与判定汇总
当工具从个人使用走向多人协作时,单设备的CSV报告已经不够用了。你需要一个批次维度的汇总视角。比如到货50台摄像头,你需要知道这50台里有多少台通过了全部测试,哪些设备失败、失败在哪一项、失败项是否有共性。这能帮你迅速判断:是散片品质问题,还是某个生产批次系统性不良率偏高。
我在工具里增加了一个"批次模式":测试开始前,输入批次号(比如到货单号或者生产批次号),工具会把该批次所有设备的测试结果汇总生成一个总表。除了通过率之外,还会统计每个失败项出现的频次。这个功能最开始被吐槽"不过就是套了层循环而已",但真正用起来之后,它成了采购验收时跟供应商沟通的有力工具。
| 批次号 | 测试总数 | 通过数 | 失败数 | 常见失败项 |
|---|---|---|---|---|
| B20250117 | 120 | 114 | 6 | 坏点超标(4台),帧率波动(2台) |
| B20250120 | 80 | 79 | 1 | 色彩偏差(1台) |
供应商看到这种表格基本无话可说,每台失败设备都有独立截图和日志,可以直接追溯到具体模组序列号。以前人工抽检的时候,供应商经常说"我们出厂前都测过了,你这个算不算误判"。现在拿出记录来,同一批次多个指标异常的照片摆在那里,沟通效率完全不同。
5.2 批量测试的工装与照明环境
批量测试还有两个工程问题:一是怎么把摄像头快速、稳定地连接到测试电脑上,二是怎么保证测试环境的一致性。前者我用的方案是USB集线器+固定测试支架,一台电脑外接8个摄像头,通过通道切换一次测一个。后者是保证测试环境光统一:均匀白板照明、固定测试卡距离、封闭遮光罩。
这里特别要提醒:测试环境光的影响远比想象中大。同样一颗摄像头,在普通办公室灯光下测白平衡,和标准测试光源下测白平衡,结果差异可能在5%到10%之间。如果你拿着两台不同的电脑在不同房间测同一个摄像头,得到的数据几乎没有可比性。所以批量化测试之前,务必先固化环境配置:光源型号和色温、照度计读数、测试卡摆放位置、遮光罩规格,全部记录在测试SOP里。
5.3 压力测试和长时间稳定性
除了功能检测,还有一种测试场景也很常见:长时间老化测试。摄像头安装到设备上之后,需要连续运行数小时甚至数天来确认不会因为过热导致画面异常、不会因为驱动内存泄露导致视频流断开、不会因为sensor寄存器被干扰导致参数漂移。
工具的长时间模式做了三件事:周期性重复功能检测并记录结果、保活监控视频流(如果超过5秒取不到帧就自动重连并记录)、持续记录sensor温度(如果能读取的话)。跑完24小时之后,直接看时间轴上有没有异常点即可。这个模式在DT(可靠性测试)阶段帮了大忙。之前有一个项目跑老化,运行大概17小时的时候画面开始出现横向条纹,但人工不可能24小时盯着屏幕。正是长时间模式自动记录了条纹出现的时间点和后续几十帧画面,工程师才定位到是sensor在高温下的寄存器漂移,而不是摄像头硬件彻底损坏。
5.4 扩展思路:集成到持续集成流程
如果团队做的是嵌入式摄像头产品,软件迭代频繁,这台工具还可以更进一步:接入到持续集成流水线里。提交驱动代码后,自动触发摄像头测试,摄像头对着标准测试卡,流水线自动跑一遍核心检测项,把结果回传到看板。代码改动导致sensor寄存器配置变了、曝光参数异常、帧率下降,在合入主分支之前就被工具拦住了。
这套流程的改造成本不高,核心工作是把检测工具包成一个命令行接口,输出JSON格式的结果,然后由CI脚本去解析。我封装了这样一个camera_test_cli入口,支持--device-id、--test-items、--output-format这些参数。对于产线使用,仍然用带界面的版本;对于自动化环境,全部走命令行加参数。
提示:给命令行工具做测试时,务必让输出结果可以被机器解析,而不是依赖人去读屏幕上打印的文字。JSON或XML格式是最基本的要求,否则无法接入任何自动化系统。
6. 一些记录在最后的经验
写这套工具的整个过程,让我重新理解了"测试工具"三个字的含义。它不是一个功能清单的堆砌,也不只是一堆算法的组合,而是一种沟通协议:三分钟后,你如何让一个不懂摄像头的人,清晰地知道这颗摄像头能不能用、为什么不能用、需要用哪个层面的知识去解决问题。当你把工具放到产线工人面前时,界面上不应该出现"CVP"、"Gamma"、甚至"帧率"这样的词,只应该有:设备编号、测试结果、失败项描述、下一步建议。技术细节全部折叠到日志和调试页里面。
至于代码层面,这套工具从最早一百多行的采集检测脚本,慢慢扩展成现在的样子,中间经历了四个版本的迭代。每次迭代都不是因为我"想加个功能",而是因为碰到了真实的问题。设备索引漂移是测试台多接了摄像头才暴露的,坏点误报是换了廉价镜头模组才被逼出来的,白平衡波动是某个供应商的固件行为不标准才不得不处理的。提前造轮子不如用到再修,反而让最终方案更贴合实际。
如果你也要做类似的摄像头简易测试工具,我建议你先想清楚三个问题:测试对象是谁——USB还是CSI还是网络摄像头;测试环境是否可控——有无标准照明、测试卡、固定支架;测试结果给谁看——产线工人要颜色标记,研发工程师要参数日志,采购需要统计汇总。这三个问题的答案会直接决定你的工具长什么样,比任何现成的代码模板都管用。