OpenCV实战——使用邻居访问扫描图像
创始人
2024-05-04 07:26:42
0

OpenCV实战——使用邻居访问扫描图像

    • 0. 前言
    • 1. 图像锐化
    • 2. 邻居访问扫描图像
    • 3. 锐化滤波器
    • 4. 完整代码
    • 相关链接

0. 前言

在图像处理中,有时需要根据某个像素的相邻像素的值计算该像素位置的值。当这个邻域包括上一行和下一行的像素时,就需要同时扫描图像的多行像素,本节中,我们将介绍如何通过邻居访问扫描图像。

1. 图像锐化

为了说明邻域扫描方法,我们将应用一个基于拉普拉斯算子的处理函数来锐化图像。在图像处理,如果从图像中减去它的拉普拉斯算子,图像边缘会被放大,从而得到更清晰的图像。锐化值计算如下:

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

其中,left 是紧接在当前像素左侧的像素,up 是上一行的邻居像素,依此类推。接下来,我们介绍如何实现锐化函数。

2. 邻居访问扫描图像

(1) 我们将创建一个带有输入和输出图像的锐化函数,并不使用原地处理,即函数需要提供输出图像:

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

(2) 分配输出结果图像,通过 channels() 函数获取输入图像的通道数:

result.create(image.size(), image.type());
int nchannels= image.channels();

(3) 接下来,我们循环处理图像中的每一行。图像扫描使用三个指针完成,一个指向当前行,一个指向前一行,另一个指向下一行。此外,由于每个像素计算都需要访问其邻居,因此无法计算图像第一行和最后一行的像素以及第一列和最后一列的像素的值:

for (int j=1; jconst uchar* previous = image.ptr(j-1);const uchar* current = image.ptr(j);const uchar* next = image.ptr(j+1);uchar* output = result.ptr(j);for (int i=channels; i<(image.cols-1)*nchannels; i++){*output++ = cv::saturate_cast(5*current[i]-current[i-nchannels]-current[i+nchannels]-previous[i]-next[i]);}
}

以上代码可以在灰度和彩色图像上工作。如果我们将此函数应用于测试彩色图像,可以得到以下结果:

图像锐化

为了访问前一行和下一行的相邻像素,必须定义附加指针,然后在扫描循环内访问这些行中的像素。
在计算输出像素值时,会根据运算结果调用 cv::saturate_cast 模板函数,这是因为应用于像素的数学表达式可能会导致超出允许像素值范围的结果(即低于 0 或高于 255)。解决方案是将像素值重置到 [0, 255] 范围内,将负值改为 0 并将超过 255 的值改为 255,这正是 cv::saturate_cast 函数的作用。此外,如果输入参数是浮点数,则结果将四舍五入为最接近的整数。我们也可以将此函数与其他类型一起使用,以确保结果保持在此类型定义的范围内。
由于其邻域未完全定义而无法处理的边界像素需要单独处理。在这里,我们简单的将它们设为 0;在复杂情况下,可以对这些像素执行特殊计算,但在大多数情况下,花时间处理这些极少数像素是没有意义的。我们可以使用两种特殊的方法将这些边缘像素设置为 0,可以使用 rowcol,它们返回一个特殊的 cv::Mat 实例,该实例由参数中指定的单行感兴趣区域 (region of interest, ROI) (或单列 ROI) 组成。这里不需要进行复制,因为如果修改这个一维矩阵的元素,它们在原始图像中也会被修改,我们可以通过调用 setTo() 方法实现,setTo() 方法可以为矩阵的所有元素分配值:

result.row(0).setTo(cv::Scalar(0));

以上代码可以将值 0 分配给结果图像第一行的所有像素。在三通道彩色图像的情况下,需要使用 cv::Scalar(a,b,c) 指定要分配给像素的每个通道的三个值。

3. 锐化滤波器

当对像素邻域进行计算时,通常用核矩阵表示它,核描述了如何组合计算中涉及的像素以获得所需的结果。本节中使用的锐化滤波器核如下:

[0−10−15−10−10]\begin{bmatrix} 0 & -1 & 0\\ -1 & 5 & -1 \\ 0 & -1 & 0 \end{bmatrix} ​0−10​−15−1​0−10​

通常,当前像素对应于核的中心,核的每个单元格中的值表示乘以相应像素的因子。然后将计算所有乘法的总和得到核应用于像素的结果。核的大小对应于邻域的大小(此处为 3 x 3)。使用这种表示,可以看出,锐化滤波器的计算方法:当前像素的水平和垂直邻居乘以 -1,而当前像素乘以 5。将核应用于图像不仅仅是一种方便的表示,同时也是信号处理中卷积概念的基础,核定义了一个应用于图像的滤波器。
由于滤波是图像处理中的一个常见操作,OpenCV 定义了一个特殊的函数来执行这个任务——cv::filter2D 函数。要使用此函数,只需要使用矩阵的形式定义一个核,然后使用图像和核调用该函数,并返回滤波后的图像。因此,使用 cv::filter2D 函数,可以很容易重新定义锐化函数:

void sharpen2D(const cv::Mat &image, cv::Mat &result) {// 创建3x3核,所有元素初始化为0cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));// 为核赋值kernel.at(1,1) = 5.0;kernel.at(0,1) = -1.0;kernel.at(2,1) = -1.0;kernel.at(1,0) = -1.0;kernel.at(1,2) = -1.0;// 图像滤波cv::filter2D(image, result, image.depth(), kernel);
}

使用此函数可以得到与上一小节中代码完全相同的结果(并且具有相同的效率),如果输入彩色图像,则相同的内核将应用于所有三个通道。在使用较大尺寸的核时,cv::filter2D 函数更加高效。

4. 完整代码

#include 
#include 
#include 
#include void sharpen(const cv::Mat &image, cv::Mat &result) {result.create(image.size(), image.type());int nchannels = image.channels();for (int j=1; j // 循环除第一行和最后一行外的所有行const uchar* previous = image.ptr(j-1);const uchar* current = image.ptr(j);const uchar* next = image.ptr(j+1);uchar* output = result.ptr(j);for (int i=nchannels; i<(image.cols-1)*nchannels; i++) {*output++ = cv::saturate_cast(5*current[i]-current[i-nchannels]-current[i+nchannels]-previous[i]-next[i]);}}// 将未处理的像素置0result.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));
}// 使用迭代器,该函数的输入图像必须为灰度图像
void sharpenIterator(const cv::Mat &image, cv::Mat &result) {// 输入图像必须为灰度图像CV_Assert(image.type()==CV_8UC1);// 初始化迭代器cv::Mat_::const_iterator it = image.begin() + image.cols;cv::Mat_::const_iterator itend = image.end() - image.cols;cv::Mat_::const_iterator itup = image.begin();cv::Mat_::const_iterator itdown = image.begin() + 2*image.cols;// 设置输出图像和迭代器result.create(image.size(), image.type());cv::Mat_::iterator itout = result.begin() + result.cols;for (; it!=itend; ++it,++itout,++itup,++itdown) {*itout = cv::saturate_cast(*it * 5 - *(it-1) - *(it+1) - *itup - *itdown);}// 将未处理的像素置0result.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));
}// 使用核
void sharpen2D(const cv::Mat &image, cv::Mat &result) {// 构造3x3核,并将所有元素初始化为0cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));// 为核元素赋值kernel.at(1, 1) = 5.0;kernel.at(0, 1) = -1.0;kernel.at(2, 1) = -1.0;kernel.at(1, 0) = -1.0;kernel.at(1, 2) = -1.0;// 图像滤波cv::filter2D(image, result, image.depth(), kernel);
}int main() {cv::Mat image = cv::imread("1.png");if (!image.data) return 0;cv::Mat result;double time = static_cast(cv::getTickCount());sharpen(image, result);time = (static_cast(cv::getTickCount())-time) / cv::getTickFrequency();std::cout << "time = " << time << "s" << std::endl;cv::namedWindow("Image");cv::imshow("Image", result);// 使用灰度模式打开图像image = cv::imread("1.png", 0);time = static_cast(cv::getTickCount());sharpenIterator(image, result);time = (static_cast(cv::getTickCount())-time) / cv::getTickFrequency();std::cout << "time gray level = " << time << "s" << std::endl;cv::namedWindow("Sharpened Image");cv::imshow("Sharpened Image", result);// 测试sharpen2Dimage = cv::imread("1.png");time = static_cast(cv::getTickCount());sharpen2D(image, result);time = (static_cast(cv::getTickCount())-time) / cv::getTickFrequency();std::cout << "time sharpen 2D = " << time << "s" << std::endl;cv::namedWindow("Image Filter 2D");cv::imshow("Image Filter 2D", result);cv::waitKey();return 0;
}

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作

相关内容

热门资讯

安卓系统自带的网页,功能与特色... 你有没有发现,每次打开安卓手机,那熟悉的系统界面里总有一个默默无闻的小家伙——安卓系统自带的网页浏览...
美咖云系统安卓版,开启智能生活... 你有没有发现,最近手机上多了一个叫“美咖云系统安卓版”的小家伙?它就像一个魔法师,轻轻一点,就能让你...
安卓系统推荐最好的手机,盘点性... 你有没有想过,拥有一部性能卓越的手机,就像是拥有了移动的宝藏库?在这个信息爆炸的时代,一部好手机不仅...
安卓11系统能精简吗,释放潜能 你有没有发现,随着手机越来越智能,系统也越来越庞大?安卓11系统,这个最新的操作系统,是不是也让你觉...
安卓自动重启系统软件,揭秘原因... 手机突然自动重启,是不是感觉整个人都不好了?别急,今天就来和你聊聊这个让人头疼的安卓自动重启系统软件...
苹果手机x刷安卓系统,探索安卓... 你有没有想过,你的苹果手机X竟然也能刷上安卓系统?是的,你没听错,就是那个一直以来都和我们苹果手机X...
安卓系统智商低吗,智商低下的真... 你有没有想过,为什么安卓系统的智商总被调侃得好像有点低呢?是不是觉得它总是慢吞吞的,有时候还犯点小错...
安卓系统手机联系人,揭秘你的社... 你有没有发现,手机里的联系人列表就像是一个小小的社交圈呢?里面藏着我们的亲朋好友、工作伙伴,甚至还有...
安卓系统免费铃声下载,打造个性... 手机里那首老掉牙的铃声是不是让你觉得有点out了呢?别急,今天就来给你支个招,让你轻松给安卓手机换上...
安卓系统用哪个桌面好,打造个性... 你有没有发现,手机桌面可是我们每天都要面对的“脸面”呢?换一个好看的桌面,心情都能跟着好起来。那么,...
虚拟大师是安卓10系统,功能与... 你知道吗?最近在手机圈里,有个新玩意儿引起了不小的轰动,那就是虚拟大师!而且,更让人惊喜的是,这个虚...
安卓系统与苹果优缺点,系统优缺... 说到手机操作系统,安卓和苹果绝对是两大巨头,它们各有各的特色,就像两道不同的美味佳肴,让人难以抉择。...
安卓win双系统主板,融合与创... 你有没有想过,一台电脑如果既能流畅运行安卓系统,又能轻松驾驭Windows系统,那该有多爽啊?没错,...
安卓系统可精简软件,轻松提升手... 你有没有发现,手机里的安卓系统越来越庞大,软件也越装越多,有时候感觉手机就像个“大肚子”,不仅运行速...
安卓系统基于linux的代码,... 你有没有想过,那个陪伴你每天刷抖音、玩游戏、办公的安卓系统,其实背后有着一套复杂的基于Linux的代...
苹果和安卓的拍照系统,谁更胜一... 你有没有发现,现在手机拍照已经成为我们生活中不可或缺的一部分呢?无论是记录生活的点滴,还是捕捉美丽的...
苹果和安卓系统不同吗,系统差异... 你有没有想过,为什么你的手机里装的是苹果的iOS系统,而朋友的手机却是安卓系统呢?这两种系统,看似都...
安卓系统有多少级,揭秘其多级架... 你有没有想过,那个陪伴我们日常生活的安卓系统,它其实有着丰富的层级结构呢?没错,就是那个让我们的手机...
华为鸿蒙系统与安卓的,技术融合... 你知道吗?最近科技圈可是炸开了锅,华为鸿蒙系统与安卓的较量成为了大家热议的话题。这不,今天我就来给你...
什么安卓手机是苹果系统,搭载苹... 你有没有想过,为什么有些人宁愿花大价钱买苹果手机,而有些人却对安卓手机情有独钟呢?其实,这个问题背后...