扫描图像并访问相邻像素 - sumpig/OpenCV GitHub Wiki

在图像处理中经常有这样的处理函数,它在计算每个像素的数值时,需要使用周边像素的值。如果相邻像素在上一行或下一行,就需要同时扫描图像的多行。本节将介绍实现方法。

我们将使用一个锐化图像的处理函数。在图像处理领域有一个众所周知的结论:如果从图像中减去拉普拉斯算子部分,图像的边缘就会放大,因而图像会变得更加尖锐。

可以用以下方法计算锐化的数值:

sharpened_pixel = 5 * current - left - right - up - down;

实现

图像扫描中使用了三个指针,一个表示当前行、一个表示上面的行、一个表示下面的行。

另外,因为在计算每一个像素时都需要访问与它相邻的像素,所以有些像素的值是无法计算的,比如第一行、最后一行和第一列、最后一列的像素。这个循环可以这样写:

void sharpen(const cv::Mat &image, cv::Mat &result) {

    result.create(image.size(), image.type());
    int nchannels = image.channels(); // 获得通道数

    // 处理所有行(除了第一行和最后一行)
    for (int j=1; j<image.rows-1; j++) {

        const uchar* previous = image.ptr<const uchar>(j-1); // 上一行
        const uchar* current = image.ptr<const uchar>(j); // 当前行
        const uchar* next = image.ptr<const uchar>(j+1); // 下一行
        uchar* output = result.ptr<uchar>(j); // 输出行

        for (int i=nchannels; i<(image.cols-1)*nchannels; i++) {

            // 应用锐化算子
            *output++ = cv::saturate_cast<uchar>(
            5 * current[i] - current[i-nchannels] - 
            current[i+nchannels] - previous[i] - next[i]);
        }
    }
    
    // 把未处理的像素设为0
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows-1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols-1).setTo(cv::Scalar(0));
}

原理

在计算输出像素的值时,我们调用了 cv::saturate_cast 模板函数,并传入运算结果。这是因为计算像素的数学表达式的结果经常超出允许的范围(即小于0 或大于255)。使用这个函数可把结果调整到8 位无符号数的范围内,具体做法是把小于0 的数值调整为0,大于255 的数值调整为255——这就是 cv::saturate_cast 函数的作用。此外,如果输入参数是浮点数,就会得到最接近的整数。

setTo 方法将对矩阵中的所有元素赋值。对于三通道彩色图像,需要使用 cv::Scalar(a,b,c) 来指定三个数值,分别对像素的每个通道赋值。


扩展

滤波是图像处理中的常见操作,OpenCV 专门为此定义了一个函数,即 cv::filter2D。要使用这个函数,只需要定义一个内核(以矩阵的形式),调用函数并传入图像和内核,即可返回滤波后的图像。因此,使用这个函数重新定义锐化函数非常容易:

void sharpen2D(const cv::Mat &image, cv::Mat &result) {

    // 构造内核(所有入口都初始化为0)
    cv::Mat kernel(3,3,CV_32F,cv::Scalar(0));

    // 对内核赋值
    kernel.at<float>(1,1)= 5.0;
    kernel.at<float>(0,1)= -1.0;
    kernel.at<float>(2,1)= -1.0;
    kernel.at<float>(1,0)= -1.0;
    kernel.at<float>(1,2)= -1.0;

    // 对图像滤波
    cv::filter2D(image, result, image.depth(), kernel);
}

这种实现方式得到的结果与前面的完全相同(执行效率也相同)。如果处理的是彩色图像,三个通道可以应用同一个内核。使用大内核的filter2D 函数是特别有利的,因为这时它使用了更高效的算法。

⚠️ **GitHub.com Fallback** ⚠️