本节内容:
- 访问像素值
- 用指针扫描图像
- 用迭代器扫描图像
- 编写高效的图像扫描循环
- 扫描图像并访问相邻像素
- 实现简单的图像运算
- 图像重映射
访问像素值
准备工作:
创建一个简单函数,用它在图像中加入椒盐噪声.
实现如下:
void salt(cv::Mat image, int n){ int i, j; for(int k = 0; k < n; k ++){ i = std::rand()%image.cols; j = std::rand()%image.rows; if(image.type() == CV_8UC1){ //灰度图 image.at<uchar>(j,i) = 255; }else { image.at<cv::Vec3b>(j,i)[0] = 255; image.at<cv::Vec3b>(j,i)[1] = 255; image.at<cv::Vec3b>(j,i)[2] = 255; } } }
完整代码:
#include <cstdlib> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> void salt(cv::Mat image, int n){ int i, j; for(int k = 0; k < n; k ++){ i = std::rand()%image.cols; j = std::rand()%image.rows; if(image.type() == CV_8UC1){ //灰度图 image.at<uchar>(j,i) = 255; }else { image.at<cv::Vec3b>(j,i)[0] = 255; image.at<cv::Vec3b>(j,i)[1] = 255; image.at<cv::Vec3b>(j,i)[2] = 255; } } } int main(){ cv::Mat image = cv::imread("./data/rgb.png"); salt(image,3000); cv::namedWindow("image"); cv::imshow("Image", image); cv::waitKey(0); cv::imwrite("./data/output.png", image); return 0; }
效果如图:
用指针扫描图像
准备工作:
先完成一个简单任务:减少图像中的颜色数量。
原理:如果N是减少因子,那么对于像素的每一个频道,除以N(由于是整除,所以余数会丢失)。如果将结果再乘以N,那么结果将会小于原来的像素值。然后再加上N/2你将会获得两个相邻N倍数中间的那个数,然后你就得到了一个256/NX256/NX256/N颜色数的图像。下面是我们所写的函数,函数处理是in-place的,也就是说处理结果直接赋值给源图像,不占用额外的存储空间。
代码:
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> void colorReduce(cv::Mat image, int div){ int nl = image.rows; int nc = image.cols * image.channels(); for(int j = 0; j < nl; j++){ uchar* data = image.ptr<uchar>(j); for(int i = 0; i < nc; i++){ data[i] = data[i]/div*div + div/2; } } } int main(){ cv::Mat image = cv::imread("./data/beauty.jpg"); colorReduce(image, 64); cv::namedWindow("Image"); cv::imshow("Image",image); cv::waitKey(0); cv::imwrite("./data/output.jpg", image); return 0; }
补充:还可以使用为运算来计算data的值;
uchar mask = 0xFF<<n; //例如:div = 16,则 mask = 0xF0
*data &= mask;
*data++ += div>>1;
处理前: 处理后:
用迭代器扫描图像
准备工作:仍然使用上面减色程序。
使用Mat_模板类内部定义的iterator类型 Mat_<cv::Vec3b>::iterator it;
修改后的减色程序如下:
void colorReduce(cv::Mat &image, int div){ cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>(); cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>(); for( ; it != itend; ++it){ (*it)[0] = (*it)[0]/div*div + div/2; (*it)[1] = (*it)[1]/div*div + div/2; (*it)[2] = (*it)[2]/div*div + div/2; } }
效果同上;
编写高效的图像扫描循环
在前面的章节,我们已经提到过了不同的访问图像像素的方法。现在我们将比较一下这些不同方法的效率。因为代码的效率执行时间是我们必须要考虑的。但是代码效率最大化带来来的往往是减少了代码的可读性。所以只要适当的在核心地方增加代码的效率。
为了计算代码的执行时间,我们使用OpenCV中的cv::getTickCount()方法。这个方法给出从开机到现在的时钟周期数。因为我们想要的时间单位是毫秒,所以我们还要使用cv::getTickFrequency()方法。这个给出了每一秒的始终周期数。然后计算方法如下:
double duration; duration = static_cast<double>(cv::getTickCount()); colorReduce(image); // the function to be tested duration = static_cast<double>(cv::getTickCount())-duration; duration /= cv::getTickFrequency(); // the elapsed time in ms
最后得出的结果是:使用位计算得到的结果速度最快!
扫描图像并访问相邻像素
准备工作:
这里使用一个锐化的处理函数. 如果从图像中减去拉普拉斯算子部分,图像的边缘就会放大,因而使图像更尖锐.
用以下的方法计算锐化的数值: sharpened_pixel = 5 * current -left - right - up -down;
实现:
/******************************************************** > File name:sharpen > author: yoyo > Source from:xiang gao > time:2016.7.18 Monday 17:22 > *************************************************************/ #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace std; 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++){ 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]); } } 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(int argc, char * argv[]){ cv::Mat image = cv::imread("/home/yoyo/openCV/data/beauty.jpg"); cv::Mat image1 = image; cv::imshow("image1", image1); sharpen(image1 ,image); // cv::namedWindow("image"); cv::imshow("Image", image); cv::waitKey(0); cv::imwrite("/home/yoyo/openCV/data/sharpen.jpg", image); return 0; }
效果:
前: 后:
实现简单的图像运算
图像就是普通的矩阵,可以进行加减乘除运算,因此可以用多种不同的方式组合图像.
准备工作:两张相等大小的图像;
参考资料:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/adding_images/adding_images.html
代码:
#include <cv.h> #include <highgui.h> #include <iostream> using namespace cv; int main( int argc, char** argv ) { double alpha = 0.5; double beta; double input; Mat src1, src2, dst; /// Ask the user enter alpha std::cout<<" Simple Linear Blender "<<std::endl; std::cout<<"-----------------------"<<std::endl; std::cout<<"* Enter alpha [0-1]: "; std::cin>>input; /// We use the alpha provided by the user iff it is between 0 and 1 if( alpha >= 0 && alpha <= 1 ) { alpha = input; } /// Read image ( same size, same type ) src1 = imread("../../images/LinuxLogo.jpg"); src2 = imread("../../images/WindowsLogo.jpg"); if( !src1.data ) { printf("Error loading src1 "); return -1; } if( !src2.data ) { printf("Error loading src2 "); return -1; } /// Create Windows namedWindow("Linear Blend", 1); beta = ( 1.0 - alpha ); addWeighted( src1, alpha, src2, beta, 0.0, dst); imshow( "Linear Blend", dst ); waitKey(0); return 0; }