Goal:建立 OpenCV 專案與學習讀寫影像像素
練習一些基本的影像處理函數:add noise、color reduction、image enhancement、 image addition和remapping
學習以不同方式處理像素:scanning an image with pointers和scanning an image with iterators
學習撰寫高效的影像掃描迴圈
作業環境
Window 11
Visual Studio 2019
OpenCV 4.5.5
CMake 3.24.3
CPU:11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz 2.42 GHz
RAM:16.0 GB
Salt and Pepper Noise (saltImage.cpp)
Salt and pepper noise:圖像中常見的一種雜訊,通訊受到干擾或傳輸錯誤等產生原因。 saltImage.cpp在圖像隨機選擇一些像素,將他們改為白色或黑色。
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <random>
#include <iostream>
#include <opencv2/opencv.hpp>
// Add salt noise to an image
//選擇隨機像素設成白色
void salt_pepper(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,ii,jj;
for (int k=0; k<n; k++) {
//salt
// random image coordinate 隨機生成的位置
i= randomCol(generator);
j= randomRow(generator);
//用type區分灰階和彩色影像
if (image.type() == CV_8UC1) { // gray-level image 灰階影像
// single-channel 8-bit image 灰階只要改一個8-bit
image.at<uchar>(j,i)= 255;
} else if (image.type() == CV_8UC3) { // color image 彩色影像
// 3-channel image 彩色要改3個(RGB)
//image.at<cv::Vec3b>(j, i)[channel] = value;
image.at<cv::Vec3b>(j,i)[0]= 255; //藍:channel=0
image.at<cv::Vec3b>(j,i)[1]= 255; //綠:channel=1
image.at<cv::Vec3b>(j,i)[2]= 255; //紅:channel=2
/*也可以簡化為 image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
or simply:
image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);*/
}
//pepper選擇隨機像素設成黑色
ii = randomCol(generator);
jj = randomRow(generator);
//用type區分灰階和彩色影像
if (image.type() == CV_8UC1) { // gray-level image 灰階影像
// single-channel 8-bit image 灰階只要改一個8-bit
image.at<uchar>(jj, ii) = 0;
}
else if (image.type() == CV_8UC3) { // color image 彩色影像
// 3-channel image 彩色要改3個(RGB)
//image.at<cv::Vec3b>(j, i)[channel] = value;
image.at<cv::Vec3b>(jj, ii)[0] = 0; //藍:channel=0
image.at<cv::Vec3b>(jj, ii)[1] = 0; //綠:channel=1
image.at<cv::Vec3b>(jj, ii)[2] = 0; //紅:channel=2
//也可以簡化為 image.at<cv::Vec3b>(j, i) = cv::Vec3b(0, 0, 0);
}
}
}
int main()
{
// open the image
cv::Mat image= cv::imread("C:/Users/User/Desktop/woonwoodog.png",1);//彩色
cv::resize(image, image, cv::Size(420, 560));//改變圖像大小
// call function to add noise 加入salt noise
salt_pepper(image,10000);
// display result
cv::namedWindow("saltImage");//定義視窗
cv::imshow("saltImage",image);//顯示圖像
// write on disk
cv::imwrite("salted.bmp",image);
/*
cv::waitKey();
等待按鍵。
括號內可輸入等待毫秒數,若輸入0,則代表一直等待按鍵
*/
// test second version 試試另一種方法,但只能用灰階影像
cv::Mat image2= cv::imread("C:/Users/User/Desktop/woonwoodog.png",0);//灰階
cv::resize(image2, image2, cv::Size(420, 560));
salt_pepper(image2, 5000);
cv::namedWindow("saltImage_gray");
cv::imshow("saltImage_gray",image2);
cv::waitKey();
return 0;
}
cv::Mat image= cv::imread("woonwoodog.png",1);
0:代表灰階影像
1:代表彩色影像
ERROR
Release 的其他相依性錯誤,將opencv_world455b.lib
更改為opencv_world455.lib,再重新開啟專案即可運作
原始影像
salt(image,3000);
salt(image2,500);
salt_pepper(image,10000);
salt_pepper(image2, 5000);
藍點
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 0;
image.at<cv::Vec3b>(j,i)[2]= 0;
紅點
image.at<cv::Vec3b>(j,i)[0]= 0;
image.at<cv::Vec3b>(j,i)[1]= 0;
image.at<cv::Vec3b>(j,i)[2]= 255;
綠點
image.at<cv::Vec3b>(j,i)[0]= 0;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 0;
Color Reduction (colorReduce.cpp)
Color Reduce:減少圖像中顏色數量,重新設定圖像顏色,新圖像使用到的顏色減少。
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
// 1st version
// see recipe Scanning an image with pointers
//直接修改輸入的像素值
void colorReduce(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines 行數
int nc= image.cols * image.channels(); // total number of elements per line 每行的元素量
for (int j=0; j<nl; j++) {
// get the address of row j 得到j的位址
uchar* data= image.ptr<uchar>(j);//ptr可以直接訪問圖像中一行的起始位址
for (int i=0; i<nc; i++) {
// process each pixel 處理每個像素
data[i]= data[i]/div*div + div/2;
// end of pixel processing 像素處理結束
} // end of line 一行結束
}
}
// version with input/ouput images
// see recipe Scanning an image with pointers
void colorReduceIO(const cv::Mat &image, // input image
cv::Mat &result, // output image
int div = 64) {
int nl = image.rows; // number of lines
int nc = image.cols; // number of columns
int nchannels = image.channels(); // number of channels
// allocate output image if necessary
result.create(image.rows, image.cols, image.type());
for (int j = 0; j<nl; j++) {
// get the addresses of input and output row j
const uchar* data_in = image.ptr<uchar>(j);
uchar* data_out = result.ptr<uchar>(j);
for (int i = 0; i<nc*nchannels; i++) {
// process each pixel ---------------------
data_out[i] = data_in[i] / div*div + div / 2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 1
// this version uses the dereference operator *
void colorReduce1(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
uchar div2 = div >> 1; // div2 = div/2
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data++= *data/div*div + div2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 2
// this version uses the modulo operator
void colorReduce2(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
uchar div2 = div >> 1; // div2 = div/2
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
int v= *data;
*data++= v - v%div + div2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 3
// this version uses a binary mask
void colorReduce3(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
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= 1<<(n-1); // div2 = div/2
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i = 0; i < nc; i++) {
// process each pixel ---------------------
*data &= mask; // masking
*data++ |= div2; // add div/2
// end of pixel processing ----------------
} // end of line
}
}
// Test 4
// this version uses direct pointer arithmetic with a binary mask
void colorReduce4(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0) + 0.5);
int step= image.step; // effective width
// 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 the pointer to the image buffer
uchar *data= image.data;
for (int j=0; j<nl; j++) {
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*(data+i) &= mask;
*(data+i) += div2;
// end of pixel processing ----------------
} // end of line
data+= step; // next line
}
}
// Test 5
// this version recomputes row size each time
void colorReduce5(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
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
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<image.cols * image.channels(); i++) {
// process each pixel ---------------------
*data &= mask;
*data++ += div/2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 6
// this version optimizes the case of continuous image
void colorReduce6(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
if (image.isContinuous()) {
// then no padded pixels
nc= nc*nl;
nl= 1; // it is now a 1D array
}
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
// this loop is executed only once
// in case of continuous images
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data &= mask;
*data++ += div2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 7
// this versions applies reshape on continuous image
void colorReduce7(cv::Mat image, int div=64) {
if (image.isContinuous()) {
// no padded pixels
image.reshape(1, // new number of channels
1) ; // new number of rows
}
// number of columns set accordingly
int nl= image.rows; // number of lines
int nc= image.cols*image.channels() ; // number of columns
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
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data &= mask;
*data++ += div2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 8
// this version processes the 3 channels inside the loop with Mat_ iterators
void colorReduce8(cv::Mat image, int div=64) {
// get iterators
cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
uchar div2 = div >> 1; // div2 = div/2
for ( ; it!= itend; ++it) {
// process each pixel ---------------------
(*it)[0]= (*it)[0]/div*div + div2;
(*it)[1]= (*it)[1]/div*div + div2;
(*it)[2]= (*it)[2]/div*div + div2;
// end of pixel processing ----------------
}
}
// Test 9
// this version uses iterators on Vec3b
void colorReduce9(cv::Mat image, int div=64) {
// get iterators
cv::MatIterator_<cv::Vec3b> it= image.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> itend= image.end<cv::Vec3b>();
const cv::Vec3b offset(div/2,div/2,div/2);
for ( ; it!= itend; ++it) {
// process each pixel ---------------------
*it= *it/div*div+offset;
// end of pixel processing ----------------
}
}
// Test 10
// this version uses iterators with a binary mask
void colorReduce10(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 ----------------
}
}
// Test 11
// this versions uses ierators from Mat_
void colorReduce11(cv::Mat image, int div=64) {
// get iterators
cv::Mat_<cv::Vec3b> cimage= image;
cv::Mat_<cv::Vec3b>::iterator it=cimage.begin();
cv::Mat_<cv::Vec3b>::iterator itend=cimage.end();
uchar div2 = div >> 1; // div2 = div/2
for ( ; it!= itend; it++) {
// process each pixel ---------------------
(*it)[0]= (*it)[0]/div*div + div2;
(*it)[1]= (*it)[1]/div*div + div2;
(*it)[2]= (*it)[2]/div*div + div2;
// end of pixel processing ----------------
}
}
// Test 12
// this version uses the at method
void colorReduce12(cv::Mat image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols; // number of columns
uchar div2 = div >> 1; // div2 = div/2
for (int j=0; j<nl; j++) {
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
image.at<cv::Vec3b>(j,i)[0]= image.at<cv::Vec3b>(j,i)[0]/div*div + div2;
image.at<cv::Vec3b>(j,i)[1]= image.at<cv::Vec3b>(j,i)[1]/div*div + div2;
image.at<cv::Vec3b>(j,i)[2]= image.at<cv::Vec3b>(j,i)[2]/div*div + div2;
// end of pixel processing ----------------
} // end of line
}
}
// Test 13
// this version uses Mat overloaded operators
void colorReduce13(cv::Mat image, int div=64) {
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
// perform color reduction
image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);
}
// Test 14
// this version uses a look up table
void colorReduce14(cv::Mat image, int div=64) {
cv::Mat lookup(1,256,CV_8U);
for (int i=0; i<256; i++) {
lookup.at<uchar>(i)= i/div*div + div/2;
}
cv::LUT(image,lookup,image);
}
#define NTESTS 15
#define NITERATIONS 10
int main()
{
// read the image
cv::Mat image = cv::imread("C:/Users/User/Desktop/woonwoodog.png");
//cv::resize(image, image, cv::Size(420, 560));
// time and process the image
const int64 start = cv::getTickCount();
colorReduce(image, 64);
//Elapsed time in seconds
double duration = (cv::getTickCount() - start) / cv::getTickFrequency();
// display the image
std::cout << "Duration= " << duration << "secs" << std::endl;
cv::namedWindow("Image",0);
cv::imshow("Image", image);
cv::waitKey();
// test different versions of the function
int64 t[NTESTS], tinit;
// timer values set to 0
for (int i = 0; i<NTESTS; i++)
t[i] = 0;
cv::Mat images[NTESTS];
cv::Mat result;
// the versions to be tested
typedef void(*FunctionPointer)(cv::Mat, int);
FunctionPointer functions[NTESTS] = { colorReduce, colorReduce1, colorReduce2, colorReduce3, colorReduce4,
colorReduce5, colorReduce6, colorReduce7, colorReduce8, colorReduce9,
colorReduce10, colorReduce11, colorReduce12, colorReduce13, colorReduce14};
// repeat the tests several times
int n = NITERATIONS;
for (int k = 0; k<n; k++) {
std::cout << k << " of " << n << std::endl;
// test each version
for (int c = 0; c < NTESTS; c++) {
images[c] = cv::imread("C:/Users/User/Desktop/woonwoodog.png");
cv::resize(images[c], images[c], cv::Size(420, 560));
// set timer and call function
tinit = cv::getTickCount();
functions[c](images[c], 64);
t[c] += cv::getTickCount() - tinit;
std::cout << ".";
}
std::cout << std::endl;
}
// short description of each function
std::string descriptions[NTESTS] = {
"original version:",
"with dereference operator:",
"using modulo operator:",
"using a binary mask:",
"direct ptr arithmetic:",
"row size recomputation:",
"continuous image:",
"reshape continuous image:",
"with iterators:",
"Vec3b iterators:",
"iterators and mask:",
"iterators from Mat_:",
"at method:",
"overloaded operators:",
"look-up table:",
};
for (int i = 0; i < NTESTS; i++) {
cv::namedWindow(descriptions[i]);
cv::imshow(descriptions[i], images[i]);
}
// print average execution time
std::cout << std::endl << "-------------------------------------------" << std::endl << std::endl;
for (int i = 0; i < NTESTS; i++) {
std::cout << i << ". " << descriptions[i] << 1000.*t[i] / cv::getTickFrequency() / n << "ms" << std::endl;
}
cv::waitKey();
return 0;
}
原始影像
Test0
original version
Test1
with dereference operator
Test2
using modulo operator
Test3
using a binary mask
Test4
direct ptr arithmetic
Test5
row size recomputation
Test6
continuous image
Test7
reshape continuous image
Test8
with iterators
Test9
Vec3b iterators
Test10
iterators and mask
Test11
iterators from Mat_
Test12
at method
Test13
overloaded operators
Test14
look-up table
每張圖片執行的時間
Contrast Enhancement (contrast.cpp)
Contrast Enhancement:通過加強圖像的邊緣對比度使圖像變清晰。
#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));
}
// same function but using iterator
// this one works only for gray-level image
void sharpenIterator(const cv::Mat &image, cv::Mat &result) {
// must be a gray-level image
CV_Assert(image.type() == CV_8UC1);
// initialize iterators at row 1
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;
// setup output image and iterator
result.create(image.size(), image.type()); // allocate if necessary 判斷是否需要分配圖像數據
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); //sharpen formula
}
// Set the unprocessed pixels to 0 沒有處理的像素設為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));
}
// using kernel
void sharpen2D(const cv::Mat &image, cv::Mat &result) {
// Construct kernel (all entries initialized to 0)
cv::Mat kernel(3,3,CV_32F,cv::Scalar(0));//3x3 初始化為0
// assigns kernel values
/*
-1 -1 -1
-1 5 -1
-1 -1 -1
*/
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;
//filter the image 對圖像濾波
cv::filter2D(image,result,image.depth(),kernel);
}
int main()
{
// test sharpen function
cv::Mat image= cv::imread("C:/Users/User/Desktop/test2.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);
// test sharpenIterator
// open the image in gray-level
image= cv::imread("C:/Users/User/Desktop/test2.jpg",0);
time = static_cast<double>(cv::getTickCount());
sharpenIterator(image, result);
time= (static_cast<double>(cv::getTickCount())-time)/cv::getTickFrequency();
std::cout << "time 3= " << time << std::endl;
cv::namedWindow("Sharpened Image");
cv::imshow("Sharpened Image",result);
// test sharpen2D
image= cv::imread("C:/Users/User/Desktop/test2.jpg");
time = static_cast<double>(cv::getTickCount());
sharpen2D(image, result);
time= (static_cast<double>(cv::getTickCount())-time)/cv::getTickFrequency();
std::cout << "time 2D= " << time << std::endl;
cv::namedWindow("Image 2D");
cv::imshow("Image 2D",result);
cv::waitKey();
return 0;
}
原始影像
Image
Sharpened Image gray-level
Image 2D
Image
Sharpened Image gray-level
Image 2D
原圖
Image Addition (addImages.cpp)
Image Addition:圖像就是普通的矩陣,可以進行加、減、乘、除運算,因此可以用多種方式組合圖像。
addImages.cpp:用多張照片疊加成一張圖像。
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
int main()
{
cv::Mat image1;
cv::Mat image2;
// open images
image1= cv::imread("C:/Users/User/Desktop/woonwoodog.jpg");
image2= cv::imread("C:/Users/User/Desktop/add2.jpg");
if (!image1.data)
return 0;
if (!image2.data)
return 0;
cv::namedWindow("Image 1");
cv::imshow("Image 1",image1);
cv::namedWindow("Image 2");
cv::imshow("Image 2",image2);
cv::Mat result;
// add two images
cv::addWeighted(image1,0.7,image2,0.9,0.,result);
cv::namedWindow("result");
cv::imshow("result",result);
// using overloaded operator
result= 0.7*image1+0.9*image2;
cv::namedWindow("result with operators");
cv::imshow("result with operators",result);
image2= cv::imread("C:/Users/User/Desktop/add2.jpg",0);
// create vector of 3 images
std::vector<cv::Mat> planes;
// split 1 3-channel image into 3 1-channel images
cv::split(image1,planes);
// add to blue channel(0:藍色/1:綠色/2:紅色)
planes[0]+= image2;
// merge the 3 1-channel images into 1 3-channel image
cv::merge(planes,result);
cv::namedWindow("Result on blue channel");
cv::imshow("Result on blue channel",result);
cv::waitKey();
return 0;
}
ERROR
圖像檔案太大
修改後即可執行
原始影像
前面兩張權重一樣,結果一樣。後面兩張權重不一樣,結果不一樣。
cv::addWeighted(image1,0.7,image2,0.9,0.,result);
result= 0.7*image1+0.9*image2;
cv::addWeighted(image1,0.5,image2,0.5,0.,result);
result= 0.7*image1+0.9*image2;
塞入通道 0
塞入通道 1
塞入通道 2
塞入通道 0、2
Image Distortion (remapping.cpp)
Image Distortion:透過移動像素修改圖像的外觀,這個過程不會修改像素值,而是把每個像素的位置重新映射到新的位置。可以用來創建圖像特效,或者修正因鏡片等原因導致的圖像扭曲。
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <math.h>
// remapping an image by creating wave effects
//remapping,波浪效果
void wave(const cv::Mat &image, cv::Mat &result) {
// the map functions 映射參數
cv::Mat srcX(image.rows,image.cols,CV_32F); // x-map
cv::Mat srcY(image.rows,image.cols,CV_32F); // y-map
// creating the mapping
for (int i=0; i<image.rows; i++) {
for (int j=0; j<image.cols; j++) {
//像素新位置
srcX.at<float>(i,j)= j;//保持在同一列
srcY.at<float>(i,j)= i+3*sin(j/6.0);//原本在第i行的像素,根據sin曲線移動
// horizontal flipping
// srcX.at<float>(i,j)= image.cols-j-1;
// srcY.at<float>(i,j)= i;
}
}
// applying the mapping 應用映射參數
cv::remap(image, // source image
result, // destination image
srcX, // x map
srcY, // y map
cv::INTER_LINEAR); // interpolation method
}
int main()
{
// open image
cv::Mat image= cv::imread("C:/Users/User/Desktop/woonwoodog.png",0);
cv::namedWindow("Image");
cv::imshow("Image",image);
// remap image
cv::Mat result;
wave(image,result);
cv::namedWindow("Remapped image");
cv::imshow("Remapped image",result);
cv::waitKey();
return 0;
}
原始影像
變灰階
波浪效果
波浪效果
水平翻轉
上下翻轉
Reference
OpenCV 4 Computer Vision Application Programming Cookbook, by D. M. Escrivá, R. Laganiere, Fourth Edition, Packt Publishing, 2019