OpenCV使用鄰居訪問掃描圖像的操作方法
0. 前言
在圖像處理中,有時需要根據(jù)某個像素的相鄰像素的值計算該像素位置的值。當(dāng)這個鄰域包括上一行和下一行的像素時,就需要同時掃描圖像的多行像素,本節(jié)中,我們將介紹如何通過鄰居訪問掃描圖像。
1. 圖像銳化
為了說明鄰域掃描方法,我們將應(yīng)用一個基于拉普拉斯算子的處理函數(shù)來銳化圖像。在圖像處理,如果從圖像中減去它的拉普拉斯算子,圖像邊緣會被放大,從而得到更清晰的圖像。銳化值計算如下:
sharpened_pixel= 5*current-left-right-up-down;
其中,left 是緊接在當(dāng)前像素左側(cè)的像素,up 是上一行的鄰居像素,依此類推。接下來,我們介紹如何實現(xiàn)銳化函數(shù)。
2. 鄰居訪問掃描圖像
(1) 我們將創(chuàng)建一個帶有輸入和輸出圖像的銳化函數(shù),并不使用原地處理,即函數(shù)需要提供輸出圖像:
void sharpen(const cv::Mat &image, cv::Mat &result)
(2) 分配輸出結(jié)果圖像,通過 channels() 函數(shù)獲取輸入圖像的通道數(shù):
result.create(image.size(), image.type()); int nchannels= image.channels();
(3) 接下來,我們循環(huán)處理圖像中的每一行。圖像掃描使用三個指針完成,一個指向當(dāng)前行,一個指向前一行,另一個指向下一行。此外,由于每個像素計算都需要訪問其鄰居,因此無法計算圖像第一行和最后一行的像素以及第一列和最后一列的像素的值:
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)用于測試彩色圖像,可以得到以下結(jié)果:

為了訪問前一行和下一行的相鄰像素,必須定義附加指針,然后在掃描循環(huán)內(nèi)訪問這些行中的像素。
在計算輸出像素值時,會根據(jù)運算結(jié)果調(diào)用 cv::saturate_cast 模板函數(shù),這是因為應(yīng)用于像素的數(shù)學(xué)表達式可能會導(dǎo)致超出允許像素值范圍的結(jié)果(即低于 0 或高于 255)。解決方案是將像素值重置到 [0, 255] 范圍內(nèi),將負值改為 0 并將超過 255 的值改為 255,這正是 cv::saturate_cast<uchar> 函數(shù)的作用。此外,如果輸入?yún)?shù)是浮點數(shù),則結(jié)果將四舍五入為最接近的整數(shù)。我們也可以將此函數(shù)與其他類型一起使用,以確保結(jié)果保持在此類型定義的范圍內(nèi)。
由于其鄰域未完全定義而無法處理的邊界像素需要單獨處理。在這里,我們簡單的將它們設(shè)為 0;在復(fù)雜情況下,可以對這些像素執(zhí)行特殊計算,但在大多數(shù)情況下,花時間處理這些極少數(shù)像素是沒有意義的。我們可以使用兩種特殊的方法將這些邊緣像素設(shè)置為 0,可以使用 row 或 col,它們返回一個特殊的 cv::Mat 實例,該實例由參數(shù)中指定的單行感興趣區(qū)域 (region of interest, ROI) (或單列 ROI) 組成。這里不需要進行復(fù)制,因為如果修改這個一維矩陣的元素,它們在原始圖像中也會被修改,我們可以通過調(diào)用 setTo() 方法實現(xiàn),setTo() 方法可以為矩陣的所有元素分配值:
result.row(0).setTo(cv::Scalar(0));
以上代碼可以將值 0 分配給結(jié)果圖像第一行的所有像素。在三通道彩色圖像的情況下,需要使用 cv::Scalar(a,b,c) 指定要分配給像素的每個通道的三個值。
3. 銳化濾波器
當(dāng)對像素鄰域進行計算時,通常用核矩陣表示它,核描述了如何組合計算中涉及的像素以獲得所需的結(jié)果。本節(jié)中使用的銳化濾波器核如下:

通常,當(dāng)前像素對應(yīng)于核的中心,核的每個單元格中的值表示乘以相應(yīng)像素的因子。然后將計算所有乘法的總和得到核應(yīng)用于像素的結(jié)果。核的大小對應(yīng)于鄰域的大小(此處為 3 x 3)。使用這種表示,可以看出,銳化濾波器的計算方法:當(dāng)前像素的水平和垂直鄰居乘以 -1,而當(dāng)前像素乘以 5。將核應(yīng)用于圖像不僅僅是一種方便的表示,同時也是信號處理中卷積概念的基礎(chǔ),核定義了一個應(yīng)用于圖像的濾波器。
由于濾波是圖像處理中的一個常見操作,OpenCV 定義了一個特殊的函數(shù)來執(zhí)行這個任務(wù)——cv::filter2D 函數(shù)。要使用此函數(shù),只需要使用矩陣的形式定義一個核,然后使用圖像和核調(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)用于所有三個通道。在使用較大尺寸的核時,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);
// 使用灰度模式打開圖像
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;
}到此這篇關(guān)于OpenCV使用鄰居訪問掃描圖像的文章就介紹到這了,更多相關(guān)OpenCV鄰居訪問掃描圖像內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳談全排列next_permutation() 函數(shù)的用法(推薦)
下面小編就為大家?guī)硪黄斦勅帕衝ext_permutation() 函數(shù)的用法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03
C++實現(xiàn)教職工管理系統(tǒng)課程設(shè)計
這篇文章主要為大家詳細介紹了C++實現(xiàn)教職工管理系統(tǒng)課程設(shè)計,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
c++ 子類構(gòu)造函數(shù)初始化及父類構(gòu)造初始化的使用
這篇文章主要介紹了c++ 子類構(gòu)造函數(shù)初始化及父類構(gòu)造初始化的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
c++基礎(chǔ)語法:構(gòu)造函數(shù)與析構(gòu)函數(shù)
構(gòu)造函數(shù)用來構(gòu)造一個對象,主要完成一些初始化工作,如果類中不提供構(gòu)造函數(shù),編譯器會默認的提供一個默認構(gòu)造函數(shù)(參數(shù)為空的構(gòu)造函數(shù)就是默認構(gòu)造函數(shù)) ;析構(gòu)函數(shù)是隱式調(diào)用的,delete對象時候會自動調(diào)用完成對象的清理工作2013-09-09
C++ clock()解析如何使用時鐘計時的應(yīng)用
本篇文章是對c++中的clock()函數(shù)進行了詳細的分析介紹,需要的朋友參考下2013-06-06

