OpenCV使用鄰居訪問掃描圖像的操作方法
0. 前言
在圖像處理中,有時需要根據某個像素的相鄰像素的值計算該像素位置的值。當這個鄰域包括上一行和下一行的像素時,就需要同時掃描圖像的多行像素,本節(jié)中,我們將介紹如何通過鄰居訪問掃描圖像。
1. 圖像銳化
為了說明鄰域掃描方法,我們將應用一個基于拉普拉斯算子的處理函數來銳化圖像。在圖像處理,如果從圖像中減去它的拉普拉斯算子,圖像邊緣會被放大,從而得到更清晰的圖像。銳化值計算如下:
sharpened_pixel= 5*current-left-right-up-down;
其中,left
是緊接在當前像素左側的像素,up
是上一行的鄰居像素,依此類推。接下來,我們介紹如何實現(xiàn)銳化函數。
2. 鄰居訪問掃描圖像
(1) 我們將創(chuàng)建一個帶有輸入和輸出圖像的銳化函數,并不使用原地處理,即函數需要提供輸出圖像:
void sharpen(const cv::Mat &image, cv::Mat &result)
(2) 分配輸出結果圖像,通過 channels()
函數獲取輸入圖像的通道數:
result.create(image.size(), image.type()); int nchannels= image.channels();
(3) 接下來,我們循環(huán)處理圖像中的每一行。圖像掃描使用三個指針完成,一個指向當前行,一個指向前一行,另一個指向下一行。此外,由于每個像素計算都需要訪問其鄰居,因此無法計算圖像第一行和最后一行的像素以及第一列和最后一列的像素的值:
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]); } }
以上代碼可以在灰度和彩色圖像上工作。如果我們將此函數應用于測試彩色圖像,可以得到以下結果:
為了訪問前一行和下一行的相鄰像素,必須定義附加指針,然后在掃描循環(huán)內訪問這些行中的像素。
在計算輸出像素值時,會根據運算結果調用 cv::saturate_cast
模板函數,這是因為應用于像素的數學表達式可能會導致超出允許像素值范圍的結果(即低于 0
或高于 255
)。解決方案是將像素值重置到 [0, 255]
范圍內,將負值改為 0
并將超過 255
的值改為 255
,這正是 cv::saturate_cast<uchar>
函數的作用。此外,如果輸入參數是浮點數,則結果將四舍五入為最接近的整數。我們也可以將此函數與其他類型一起使用,以確保結果保持在此類型定義的范圍內。
由于其鄰域未完全定義而無法處理的邊界像素需要單獨處理。在這里,我們簡單的將它們設為 0
;在復雜情況下,可以對這些像素執(zhí)行特殊計算,但在大多數情況下,花時間處理這些極少數像素是沒有意義的。我們可以使用兩種特殊的方法將這些邊緣像素設置為 0
,可以使用 row
或 col
,它們返回一個特殊的 cv::Mat
實例,該實例由參數中指定的單行感興趣區(qū)域 (region of interest
, ROI
) (或單列 ROI
) 組成。這里不需要進行復制,因為如果修改這個一維矩陣的元素,它們在原始圖像中也會被修改,我們可以通過調用 setTo()
方法實現(xiàn),setTo()
方法可以為矩陣的所有元素分配值:
result.row(0).setTo(cv::Scalar(0));
以上代碼可以將值 0
分配給結果圖像第一行的所有像素。在三通道彩色圖像的情況下,需要使用 cv::Scalar(a,b,c)
指定要分配給像素的每個通道的三個值。
3. 銳化濾波器
當對像素鄰域進行計算時,通常用核矩陣表示它,核描述了如何組合計算中涉及的像素以獲得所需的結果。本節(jié)中使用的銳化濾波器核如下:
通常,當前像素對應于核的中心,核的每個單元格中的值表示乘以相應像素的因子。然后將計算所有乘法的總和得到核應用于像素的結果。核的大小對應于鄰域的大小(此處為 3 x 3
)。使用這種表示,可以看出,銳化濾波器的計算方法:當前像素的水平和垂直鄰居乘以 -1
,而當前像素乘以 5
。將核應用于圖像不僅僅是一種方便的表示,同時也是信號處理中卷積概念的基礎,核定義了一個應用于圖像的濾波器。
由于濾波是圖像處理中的一個常見操作,OpenCV
定義了一個特殊的函數來執(zhí)行這個任務——cv::filter2D
函數。要使用此函數,只需要使用矩陣的形式定義一個核,然后使用圖像和核調用該函數,并返回濾波后的圖像。因此,使用 cv::filter2D
函數,可以很容易重新定義銳化函數:
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); }
使用此函數可以得到與上一小節(jié)中代碼完全相同的結果(并且具有相同的效率),如果輸入彩色圖像,則相同的內核將應用于所有三個通道。在使用較大尺寸的核時,cv::filter2D
函數更加高效。
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)); } // 使用迭代器,該函數的輸入圖像必須為灰度圖像 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; // 設置輸出圖像和迭代器 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) { // 構造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); // 使用灰度模式打開圖像 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); // 測試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; }
到此這篇關于OpenCV使用鄰居訪問掃描圖像的文章就介紹到這了,更多相關OpenCV鄰居訪問掃描圖像內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
VS2022調試通過??禂z像頭煙火識別SDK的實現(xiàn)
本文主要介紹了VS2022調試通過海康攝像頭煙火識別SDK的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02C語言詳細分析貪心策略中最小生成樹的Prime算法設計與實現(xiàn)
最小生成樹的問題還是比較熱門的,最經典的莫過于Prime算法和Kruskal算法了,這篇博文我會詳細講解Prime算法的設計思想與具體代碼的實現(xiàn),不要求數據結構學的有多好,只要跟著我的思路來,一步一步的分析,調試,終能成就自己,那就讓我們開始吧2022-05-05