OpenCV使用鄰居訪問(wèn)掃描圖像的操作方法
0. 前言
在圖像處理中,有時(shí)需要根據(jù)某個(gè)像素的相鄰像素的值計(jì)算該像素位置的值。當(dāng)這個(gè)鄰域包括上一行和下一行的像素時(shí),就需要同時(shí)掃描圖像的多行像素,本節(jié)中,我們將介紹如何通過(guò)鄰居訪問(wèn)掃描圖像。
1. 圖像銳化
為了說(shuō)明鄰域掃描方法,我們將應(yīng)用一個(gè)基于拉普拉斯算子的處理函數(shù)來(lái)銳化圖像。在圖像處理,如果從圖像中減去它的拉普拉斯算子,圖像邊緣會(huì)被放大,從而得到更清晰的圖像。銳化值計(jì)算如下:
sharpened_pixel= 5*current-left-right-up-down;
其中,left
是緊接在當(dāng)前像素左側(cè)的像素,up
是上一行的鄰居像素,依此類推。接下來(lái),我們介紹如何實(shí)現(xiàn)銳化函數(shù)。
2. 鄰居訪問(wèn)掃描圖像
(1) 我們將創(chuàng)建一個(gè)帶有輸入和輸出圖像的銳化函數(shù),并不使用原地處理,即函數(shù)需要提供輸出圖像:
void sharpen(const cv::Mat &image, cv::Mat &result)
(2) 分配輸出結(jié)果圖像,通過(guò) channels()
函數(shù)獲取輸入圖像的通道數(shù):
result.create(image.size(), image.type()); int nchannels= image.channels();
(3) 接下來(lái),我們循環(huán)處理圖像中的每一行。圖像掃描使用三個(gè)指針完成,一個(gè)指向當(dāng)前行,一個(gè)指向前一行,另一個(gè)指向下一行。此外,由于每個(gè)像素計(jì)算都需要訪問(wèn)其鄰居,因此無(wú)法計(jì)算圖像第一行和最后一行的像素以及第一列和最后一列的像素的值:
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=channels; i<(image.cols-1)*nchannels; i++){ *output++ = cv::saturate_cast<uchar>( 5*current[i]-current[i-nchannels]-current[i+nchannels]-previous[i]-next[i]); } }
以上代碼可以在灰度和彩色圖像上工作。如果我們將此函數(shù)應(yīng)用于測(cè)試彩色圖像,可以得到以下結(jié)果:
為了訪問(wèn)前一行和下一行的相鄰像素,必須定義附加指針,然后在掃描循環(huán)內(nèi)訪問(wèn)這些行中的像素。
在計(jì)算輸出像素值時(shí),會(huì)根據(jù)運(yùn)算結(jié)果調(diào)用 cv::saturate_cast
模板函數(shù),這是因?yàn)閼?yīng)用于像素的數(shù)學(xué)表達(dá)式可能會(huì)導(dǎo)致超出允許像素值范圍的結(jié)果(即低于 0
或高于 255
)。解決方案是將像素值重置到 [0, 255]
范圍內(nèi),將負(fù)值改為 0
并將超過(guò) 255
的值改為 255
,這正是 cv::saturate_cast<uchar>
函數(shù)的作用。此外,如果輸入?yún)?shù)是浮點(diǎn)數(shù),則結(jié)果將四舍五入為最接近的整數(shù)。我們也可以將此函數(shù)與其他類型一起使用,以確保結(jié)果保持在此類型定義的范圍內(nèi)。
由于其鄰域未完全定義而無(wú)法處理的邊界像素需要單獨(dú)處理。在這里,我們簡(jiǎn)單的將它們?cè)O(shè)為 0
;在復(fù)雜情況下,可以對(duì)這些像素執(zhí)行特殊計(jì)算,但在大多數(shù)情況下,花時(shí)間處理這些極少數(shù)像素是沒(méi)有意義的。我們可以使用兩種特殊的方法將這些邊緣像素設(shè)置為 0
,可以使用 row
或 col
,它們返回一個(gè)特殊的 cv::Mat
實(shí)例,該實(shí)例由參數(shù)中指定的單行感興趣區(qū)域 (region of interest
, ROI
) (或單列 ROI
) 組成。這里不需要進(jìn)行復(fù)制,因?yàn)槿绻薷倪@個(gè)一維矩陣的元素,它們?cè)谠紙D像中也會(huì)被修改,我們可以通過(guò)調(diào)用 setTo()
方法實(shí)現(xiàn),setTo()
方法可以為矩陣的所有元素分配值:
result.row(0).setTo(cv::Scalar(0));
以上代碼可以將值 0
分配給結(jié)果圖像第一行的所有像素。在三通道彩色圖像的情況下,需要使用 cv::Scalar(a,b,c)
指定要分配給像素的每個(gè)通道的三個(gè)值。
3. 銳化濾波器
當(dāng)對(duì)像素鄰域進(jìn)行計(jì)算時(shí),通常用核矩陣表示它,核描述了如何組合計(jì)算中涉及的像素以獲得所需的結(jié)果。本節(jié)中使用的銳化濾波器核如下:
通常,當(dāng)前像素對(duì)應(yīng)于核的中心,核的每個(gè)單元格中的值表示乘以相應(yīng)像素的因子。然后將計(jì)算所有乘法的總和得到核應(yīng)用于像素的結(jié)果。核的大小對(duì)應(yīng)于鄰域的大小(此處為 3 x 3
)。使用這種表示,可以看出,銳化濾波器的計(jì)算方法:當(dāng)前像素的水平和垂直鄰居乘以 -1
,而當(dāng)前像素乘以 5
。將核應(yīng)用于圖像不僅僅是一種方便的表示,同時(shí)也是信號(hào)處理中卷積概念的基礎(chǔ),核定義了一個(gè)應(yīng)用于圖像的濾波器。
由于濾波是圖像處理中的一個(gè)常見(jiàn)操作,OpenCV
定義了一個(gè)特殊的函數(shù)來(lái)執(zhí)行這個(gè)任務(wù)——cv::filter2D
函數(shù)。要使用此函數(shù),只需要使用矩陣的形式定義一個(gè)核,然后使用圖像和核調(diào)用該函數(shù),并返回濾波后的圖像。因此,使用 cv::filter2D
函數(shù),可以很容易重新定義銳化函數(shù):
void sharpen2D(const cv::Mat &image, cv::Mat &result) { // 創(chuàng)建3x3核,所有元素初始化為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); }
使用此函數(shù)可以得到與上一小節(jié)中代碼完全相同的結(jié)果(并且具有相同的效率),如果輸入彩色圖像,則相同的內(nèi)核將應(yīng)用于所有三個(gè)通道。在使用較大尺寸的核時(shí),cv::filter2D
函數(shù)更加高效。
4. 完整代碼
#include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> 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++) { // 循環(huán)除第一行和最后一行外的所有行 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)); } // 使用迭代器,該函數(shù)的輸入圖像必須為灰度圖像 void sharpenIterator(const cv::Mat &image, cv::Mat &result) { // 輸入圖像必須為灰度圖像 CV_Assert(image.type()==CV_8UC1); // 初始化迭代器 cv::Mat_<uchar>::const_iterator it = image.begin<uchar>() + image.cols; cv::Mat_<uchar>::const_iterator itend = image.end<uchar>() - image.cols; cv::Mat_<uchar>::const_iterator itup = image.begin<uchar>(); cv::Mat_<uchar>::const_iterator itdown = image.begin<uchar>() + 2*image.cols; // 設(shè)置輸出圖像和迭代器 result.create(image.size(), image.type()); cv::Mat_<uchar>::iterator itout = result.begin<uchar>() + result.cols; for (; it!=itend; ++it,++itout,++itup,++itdown) { *itout = cv::saturate_cast<uchar>(*it * 5 - *(it-1) - *(it+1) - *itup - *itdown); } // 將未處理的像素置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)); } // 使用核 void sharpen2D(const cv::Mat &image, cv::Mat &result) { // 構(gòu)造3x3核,并將所有元素初始化為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); } int main() { cv::Mat image = cv::imread("1.png"); if (!image.data) return 0; cv::Mat result; double time = static_cast<double>(cv::getTickCount()); sharpen(image, result); time = (static_cast<double>(cv::getTickCount())-time) / cv::getTickFrequency(); std::cout << "time = " << time << "s" << std::endl; cv::namedWindow("Image"); cv::imshow("Image", result); // 使用灰度模式打開(kāi)圖像 image = cv::imread("1.png", 0); time = static_cast<double>(cv::getTickCount()); sharpenIterator(image, result); time = (static_cast<double>(cv::getTickCount())-time) / cv::getTickFrequency(); std::cout << "time gray level = " << time << "s" << std::endl; cv::namedWindow("Sharpened Image"); cv::imshow("Sharpened Image", result); // 測(cè)試sharpen2D image = cv::imread("1.png"); time = static_cast<double>(cv::getTickCount()); sharpen2D(image, result); time = (static_cast<double>(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; }
到此這篇關(guān)于OpenCV使用鄰居訪問(wèn)掃描圖像的文章就介紹到這了,更多相關(guān)OpenCV鄰居訪問(wèn)掃描圖像內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VS2022調(diào)試通過(guò)??禂z像頭煙火識(shí)別SDK的實(shí)現(xiàn)
本文主要介紹了VS2022調(diào)試通過(guò)??禂z像頭煙火識(shí)別SDK的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C++?如何將Lambda轉(zhuǎn)換成函數(shù)指針
這篇文章主要介紹了C++?如何將Lambda轉(zhuǎn)換成函數(shù)指針,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11基于C++ bitset常用函數(shù)及運(yùn)算符(詳解)
下面小編就為大家?guī)?lái)一篇基于C++ bitset常用函數(shù)及運(yùn)算符(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11Unity3D實(shí)現(xiàn)經(jīng)典小游戲Pacman
這篇文章主要介紹了基于Unity3D制作一做個(gè)經(jīng)典小游戲Pacman,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Unity3D有一定的幫助,感興趣的小伙伴可以了解一下2021-12-12C語(yǔ)言詳細(xì)分析貪心策略中最小生成樹(shù)的Prime算法設(shè)計(jì)與實(shí)現(xiàn)
最小生成樹(shù)的問(wèn)題還是比較熱門(mén)的,最經(jīng)典的莫過(guò)于Prime算法和Kruskal算法了,這篇博文我會(huì)詳細(xì)講解Prime算法的設(shè)計(jì)思想與具體代碼的實(shí)現(xiàn),不要求數(shù)據(jù)結(jié)構(gòu)學(xué)的有多好,只要跟著我的思路來(lái),一步一步的分析,調(diào)試,終能成就自己,那就讓我們開(kāi)始吧2022-05-05C++ GDI實(shí)現(xiàn)圖片格式轉(zhuǎn)換
GDI+(Graphics Device Interface Plus)是一種用于圖形繪制和圖像處理的應(yīng)用程序編程接口(API),在Windows平臺(tái)上廣泛使用,本文就來(lái)介紹一下如何使用GDI實(shí)現(xiàn)圖片格式轉(zhuǎn)換吧2023-12-12