前两年接手一个工业质检的项目,甲方给了三千张手机拍的零件图,说要“自动识别有没有划痕”。我当时觉得这活儿不难,跑个边缘检测加阈值分割就完事了。结果第一版脚本跑下来,一半的图识别结果全是噪点。折腾了两周才把问题一个个抠出来。图像处理这行的坑,大多不在算法本身,而在数据进算法之前的那几步。
下面这几个问题,是我和同事这几年反复踩到的,写出来给刚入行的朋友省点时间。

颜色不对,八成是色彩空间搞混了
这是最经典的坑。OpenCV 的 imread 默认读出来是 BGR 顺序,PIL 和 matplotlib 用的是 RGB。你把 OpenCV 读进来的图直接丢给 matplotlib 显示,人脸会变蓝。我第一次遇到时以为是摄像头白平衡坏了,查了半天硬件。后来养成习惯:脚本里所有读图统一走一个 load_image 函数,读进来立刻 cvtColor 转成统一的顺序,绝不散落在各个角落。另外用 cv2.imshow 写的调试代码,上线前记得删,那玩意儿在无显示器的服务器上跑会直接崩。

还有个隐藏版本:手机照片带 EXIF 旋转信息。cv2.imread 不读 EXIF,竖着拍的照片读进来是横的,后面的人脸框、OCR 坐标全歪。用 PIL 的 ImageOps.exif_transpose 转一下再进流程就没事了。

缩放、通道、数据类型,这三处最容易翻车
- 缩放选错插值:缩小时用 INTER_LINEAR 容易出锯齿和摩尔纹,缩到一半以下建议 INTER_AREA;放大用 INTER_CUBIC 或 LANCZOS,INTER_NEAREST 只适合标签图
- RGBA 直接存 JPEG:会报错或者背景变黑,正确做法是和纯色底做 alpha 合成,再转三通道
- uint8 相减溢出:两张图做差,负数会回绕成 250 多,画面全是脏点。先 astype(np.int16) 再算,或者直接用 cv2.absdiff
这三条看着基础,但我在 code review 里见过的 bug,一半以上出在这儿。尤其是 uint8 溢出,它不报错,只是结果悄悄变错,最难查。
真实场景的分割失败,多半是光照问题
回到那个质检项目。我用 Otsu 全局阈值跑了一遍,白天拍的图效果挺好,下午四点以后拍的整片糊成黑的。原因是侧光让画面一边亮一边暗,一个全局阈值不可能同时适配两边。改成先做 CLAHE 均衡化,再上自适应阈值(blockSize 取图像宽度的二十分之一,C 值给 5 到 10),召回率从 60% 出头提到了 88%。
降噪也走过弯路。椒盐噪声用 3x3 中值滤波就够,换成高斯模糊反而把细划痕一起抹平了。降噪和保边缘本来就是矛盾的,做质检这类任务,宁可留着噪点也别丢细节。
批量处理那块踩的是内存的坑。三千张 4000x3000 的图一次性读进内存,16G 的机器直接挂掉。改成生成器逐张读、处理完立刻释放,多进程数量控制在 CPU 核数减一,跑完一轮大概 12 分钟,比原来稳得多。做图像处理,先让流程扛得住数据量,再谈精度。
说到底,图像处理里真正难的不是调哪个算子,而是搞清楚你的图在进算法之前经历了什么。色彩空间、EXIF、dtype、光照,这些琐碎的东西决定了后面所有结果的上限。我的习惯是每接一个新数据源,先花一天把它看清楚:抽 20 张图,分别打印尺寸、通道数、dtype、像素直方图,肉眼过一遍。这个习惯帮我省下的返工时间,比任何调参技巧都值。