3. 图像采集与预处理

图像采集,是整个视觉检测流程的起点。说白了,你算法再牛,如果源头数据就出了问题,后面全是白搭。我见过太多项目,花了大把时间调模型,最后发现是相机没配好,或者图像格式搞错了。

这一节,我们就来聊聊从相机SDK调用到图像预处理的完整链路。嗯,都是实战中绕不开的硬骨头。

3.1 相机SDK调用:与硬件对话的第一步

工业相机不像手机摄像头,插上就能用。你得通过厂商提供的SDK(软件开发工具包)来跟它通信。国内用得最多的就是海康威视和大华的相机。

我个人习惯,先把SDK的初始化流程理清楚。不管哪家,核心步骤都差不多:

  1. 枚举设备:找到所有连接的相机。
  2. 创建句柄:建立与相机的连接通道。
  3. 打开设备:激活相机。
  4. 设置参数:曝光、增益、帧率等。
  5. 开始采集:启动图像流。
  6. 获取图像:从缓冲区取一帧数据。
  7. 停止采集 & 关闭设备:释放资源。

下面是一个海康SDK调用的简化示例,我把它封装成了一个类,方便复用:

#include "MvCameraControl.h"

class HikCamera {
public:
    bool Init() {
        // 枚举设备
        MV_CC_DEVICE_INFO_LIST stDeviceList;
        MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
        if (stDeviceList.nDeviceNum == 0) {
            printf("没有找到相机!\n");
            return false;
        }
        // 创建句柄并打开设备
        MV_CC_CreateHandle(&handle_, stDeviceList.pDeviceInfo[0]);
        MV_CC_OpenDevice(handle_);
        return true;
    }

    bool GrabImage(cv::Mat &img) {
        unsigned char *pData = nullptr;
        MV_FRAME_OUT_INFO_EX stInfo;
        // 获取一帧图像数据
        MV_CC_GetOneFrameTimeout(handle_, pData, &stInfo, 1000);
        // 转换为OpenCV Mat格式
        img = cv::Mat(stInfo.nHeight, stInfo.nWidth, CV_8UC3, pData).clone();
        return true;
    }

    void Close() {
        MV_CC_StopGrabbing(handle_);
        MV_CC_CloseDevice(handle_);
        MV_CC_DestroyHandle(handle_);
    }

private:
    void *handle_ = nullptr;
};
避坑指南: 我曾经在一个项目中,因为忘记调用 MV_CC_StartGrabbing(),结果一直拿不到图像,排查了半天。另外,记得在程序退出前释放所有资源,否则下次可能无法打开相机。

3.2 图像格式转换:BGR、RGB与Gray

相机采集到的原始数据,通常是Bayer格式(拜耳阵列),需要经过ISP(图像信号处理)转换成我们熟悉的RGB或BGR。而OpenCV默认使用BGR顺序,这一点跟很多其他库(比如PIL、matplotlib)不一样。

为什么会这样? 历史原因。早期Windows的BMP格式就是BGR,OpenCV沿用了这个习惯。所以你在用OpenCV显示图像时,如果发现颜色不对,十有八九是通道顺序搞反了。

转换其实很简单,用OpenCV的 cvtColor 函数就行:

cv::Mat bgr_img, rgb_img, gray_img;

// BGR 转 RGB
cv::cvtColor(bgr_img, rgb_img, cv::COLOR_BGR2RGB);

// BGR 转 灰度图
cv::cvtColor(bgr_img, gray_img, cv::COLOR_BGR2GRAY);

// RGB 转 BGR
cv::cvtColor(rgb_img, bgr_img, cv::COLOR_RGB2BGR);

你想想看,如果模型训练时用的是RGB数据,而你推理时喂进去的是BGR,那结果肯定一塌糊涂。所以,务必确认你的数据流中通道顺序的一致性。

我的建议: 在项目初期,就统一约定好使用BGR顺序。因为OpenCV的很多函数(如imread、imshow)都默认BGR,这样可以减少不必要的转换。

3.3 图像滤波:去噪与保边

工业现场环境复杂,图像难免会有噪声。滤波的目的就是去除这些噪声,同时尽量保留图像的边缘和细节。常用的有三种:高斯滤波、中值滤波和双边滤波。

滤波类型 原理 适用场景 优点 缺点
高斯滤波 用高斯核的加权平均 高斯噪声(传感器热噪声) 平滑效果好,速度快 会模糊边缘
中值滤波 用邻域像素的中值代替 椒盐噪声(脉冲噪声) 保边效果好,对椒盐噪声极佳 对高斯噪声效果一般
双边滤波 同时考虑空间距离和像素值差异 需要保边去噪的场景 保边效果最好 计算量大,速度慢

代码实现也很直接:

cv::Mat src, dst;

// 高斯滤波,核大小5x5,sigma=1.5
cv::GaussianBlur(src, dst, cv::Size(5, 5), 1.5);

// 中值滤波,核大小5x5
cv::medianBlur(src, dst, 5);

// 双边滤波,d=9,sigmaColor=75,sigmaSpace=75
cv::bilateralFilter(src, dst, 9, 75, 75);
实战经验: 在检测小尺寸缺陷(如划痕、脏污)时,慎用高斯滤波。它会把微弱的缺陷信号也平滑掉。我一般先用中值滤波去除椒盐噪声,再用双边滤波做轻度平滑,效果往往更好。

3.4 知识体系总览

下面这张图,把图像采集与预处理的核心流程串起来了。你可以把它当作一个检查清单,每次搭建视觉系统时,对照着走一遍。

图像采集与预处理流程 相机SDK调用 图像格式转换 图像滤波 高斯滤波 中值滤波 双边滤波 可用图像 注意:滤波选择需根据 实际噪声类型和检测目标

嗯,到这里,图像采集与预处理的核心内容就讲完了。从相机SDK的调用,到格式转换,再到滤波去噪,每一步都有坑,但也都有对应的解法。记住,好的开始是成功的一半,把源头数据处理好,后面的算法工作会轻松很多。