OpenCV操作畫素深入理解
在瞭解了影象的基礎知識和OpenCV的基礎知識和操作以後,接下來我們要做的就對畫素進行操作,我們知道了影象的本質就是一個矩陣,那麼一個矩陣中儲存了那麼多的畫素,我們如何來操作呢?下面通過幾個例子來看看畫素的操作。
這個是原圖,接下來的例子都是對這個圖片進行操作的。
訪問畫素出現雪花效果
我們需要有雪花的效果,這裡的雪花其實就是一個個白色的點,白色在畫素值是255,所以我們的思路就是在一個影象上面的矩陣中的一些畫素值轉成值為255的,如果是彩色的影象的話就是三個通道,那麼就是分別對三個通道的值都轉為255,三通道在OpenCV裡面是按照藍綠紅的順序排布的。
- 程式碼實現
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <random>
// Add white noise to an image
void white(cv::Mat image, int n) {
// C++11 random number generator
std::default_random_engine generator;
std::uniform_int_distribution<int> randomRow(0, image.rows - 1);
std::uniform_int_distribution<int> randomCol(0, image.cols - 1);
int i,j;
for (int k=0; k<n; k++) {
// 生成圖形位置
i= randomCol(generator);
j= randomRow(generator);
if (image.type() == CV_8UC1) { //灰度影象
// 單通道8位
image.at<uchar>(j,i)= 255;
} else if (image.type() == CV_8UC3) { // color image
// 3通道,分別是藍綠紅
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 255;
// or simply:
// image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
}
}
}
int main()
{
// open the image
cv::Mat image= cv::imread("boldt.jpg",1);
// call function to add noise
white(image,3000);
// display result
cv::namedWindow("Image1");
cv::imshow("Image1",image);
// write on disk
cv::imwrite("salted.bmp",image);
cv::waitKey();
// test second version
cv::Mat image2= cv::imread("boldt.jpg",0);//0表示灰度圖
white(image2, 500);
cv::namedWindow("Image2");
cv::imshow("Image2",image2);
cv::waitKey();
return 0;
}
- 效果
指標遍歷畫素:減少影象中顏色的數量
彩色影象由三通道畫素組成,每個通道表示紅、綠、藍三原色中一種顏色的亮度值,每個數值都是8位無符號字元型別,因此顏色總數為 256×256×256,即超過 1600 萬種顏色。
所以有時候為了降低分析的複雜性,需要減少影象中顏色的數量。
一種實現方法是把RGB空間細分到大小相等的方塊中。例如,如果把每種顏色數量減少到 1/8,那麼顏色總數就變為 32×32×32。將舊影象中的每個顏色值劃分到一個方塊,該方塊的中間值就是新的顏色值;新影象使用新的顏色值,顏色數就減少了。
所以在這裡的減少影象中顏色的數量的思路的步驟是:
- 假設 N 是減色因子,將影象中每個畫素的值除以 N(這裡假 定使用整數除法,不保留餘數)。
- 然後將結果乘以 N,得到 N 的倍數,並且剛好不超過原始畫素 值。
- 加上 N / 2,就得到相鄰的 N 倍數之間的中間值。
- 對所有 8 位通道值重複這個過程,就會得到 (256 / N) × (256 / N) × (256 / N)種可能的顏色值。
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
void reduceColor(cv::Mat image, int div = 64)
{
int nl = image.rows;//行數
int nc = image.cols * image.channels();//每一行的畫素數量
for (int i = 0; i < nl; ++i) {
//獲取行i的地址
auto data = image.ptr<uchar>(i);
for (int j = 0; j < nc; ++j) {
//處理每一個畫素
data[j] = data[j]/div*div + div/2;
}
}
}
int main()
{
cv::Mat image = cv::imread("boldt.jpg");
reduceColor(image, 64);
cv::namedWindow("image");
cv::imshow("image", image);
cv::waitKey();
return 0;
}
- 效果圖
當然剛剛的這個程式碼是用普通的遍歷完成的,我們也可以用迭代器完成對畫素的遍歷
void reduceColorNew(cv::Mat image, int div=64) {
// div must be a power of 2
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0) + 0.5);
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
uchar div2 = div >> 1; // div2 = div/2
// get iterators
cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
// scan all pixels
for ( ; it!= itend; ++it) {
// process each pixel ---------------------
(*it)[0]&= mask;
(*it)[0]+= div2;
(*it)[1]&= mask;
(*it)[1]+= div2;
(*it)[2]&= mask;
(*it)[2]+= div2;
// end of pixel processing ----------------
}
}
掃描影象並訪問相鄰畫素
在影象處理中經常有這樣的處理函式,它在計算每個畫素的數值時,需要使用周邊畫素的值。 如果相鄰畫素在上一行或下一行,就需要同時掃描影象的多行。
我們將使用一個銳化影象的處理函式。它基於拉普拉斯運算元。在影象處理領域有一個眾所周知的結論:如果從影象中減去拉普拉斯運算元部分,影象 的邊緣就會放大,因而影象會變得更加尖銳。
實現思路:
- 影象掃描中使用了三個指標
- 一個表 示當前行、一個表示上面的行、一個表示下面的行
- 另外,因為在計算每一個畫素時都需要訪問 與它相鄰的畫素,所以有些畫素的值是無法計算的,比如第一行、最後一行和第一列、最後一列 的畫素。
#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()); // allocate if necessary
int nchannels= image.channels();
for (int j= 1; j<image.rows-1; j++) { // for all rows (except first and last)
const uchar* previous= image.ptr<const uchar>(j-1); // previous row
const uchar* current= image.ptr<const uchar>(j); // current row
const uchar* next= image.ptr<const uchar>(j+1); // next row
uchar* output= result.ptr<uchar>(j); // output row
for (int i=nchannels; i<(image.cols-1)*nchannels; i++) {
// apply sharpening operator
*output++= cv::saturate_cast<uchar>(5*current[i]-current[i-nchannels]-current[i+nchannels]-previous[i]-next[i]);
}
}
// Set the unprocess pixels to 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));
}
int main()
{
cv::Mat image= cv::imread("boldt.jpg");
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 << std::endl;
cv::namedWindow("Image");
cv::imshow("Image",result);
cv::waitKey();
return 0;
}
時間:time= 0.00339625