OpenCV3——core组件进阶
2016-10-17 19:46
344 查看
颜色空间缩减
以BGR char无符号为例,我们的彩色图像是BGR三通道,每个通道的取值范围是0-255,那么一个像素的取值范围256*256*256这么多种,这个范围太大了,这就给我们的算法造成了严重的性能影响,所以我们就用到了颜色空间缩减。比如:将0-9范围的像素值定义为0,10-19为10,20-29为20,,,以此类推,那么每个像素值的取值就只有26*26*26种,这一下就将像素值的取值范围缩小了,那么在运算处理像素的时候就简单多了。I = (I/10)*10 (I代表像素值)—> (14/10)*10 = 10。LUT(look up table):
即将上面的颜色缩减的值保存在一个table表中,这样我们在处理图像像素的时候可以不用再次计算,直接对照表中提取对应的值就可以了。
Mat lookUpTable(1,256,CV_8U); uchar* p = lookUpTable.data; for(int i= 0;i < 256;++i)//遍历所有像素 pi[i] = divideWith*(i/divideWith);//颜色缩减 //调用函数(I是输入,J是输出) for(int i= 0;i < times;++i) LUT(I,lookUpTable,J);//调用LUT函数取出对应的像素值
计时函数:
//【3】记录起始时间 double time0 = static_cast<double>(getTickCount()); //getTickCount:CPU自某个事件以来走过的时钟周期数 //【4】调用颜色空间缩减函数 colorReduce(srcImage,dstImage,32); //【5】计算运行时间并输出 time0 = ((double)getTickCount() - time0)/getTickFrequency();//getTickFrequency:CPU一秒所走的时钟周期数 cout<<"\t此方法运行时间为: "<<time0<<"秒"<<endl; //输出运行时间
访问图像像素的三类方法:
方法一:指针访问,用时:0.00665378
void colorReduce(Mat& inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone(); //拷贝实参到临时变量 int rowNumber = outputImage.rows; //行数 int colNumber = outputImage.cols*outputImage.channels(); //列数 x 通道数=每一行元素的个数 //双重循环,遍历所有的像素值 for(int i = 0;i < rowNumber;i++) //行循环 { uchar* data = outputImage.ptr<uchar>(i); //获取第i行的首地址 for(int j = 0;j < colNumber;j++) //列循环 { // ---------【开始处理每个像素】------------- data[j] = data[j]/div*div + div/2; // ----------【处理结束】--------------------- } //行处理结束 } }
方法二:迭代器iterator,用时:0.242588
void colorReduce(Mat& inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone(); //拷贝实参到临时变量 //获取迭代器 Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>(); //初始位置的迭代器 Mat_<Vec3b>::iterator itend = outputImage.end<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; // ------------------------【处理结束】---------------------------- } }
方法三:动态地址计算,用时:0.334131
void colorReduce(Mat& inputImage, Mat& outputImage, int div) { //参数准备 outputImage = inputImage.clone(); //拷贝实参到临时变量 int rowNumber = outputImage.rows; //行数 int colNumber = outputImage.cols; //列数 //存取彩色图像像素 for(int i = 0;i < rowNumber;i++) { for(int j = 0;j < colNumber;j++) { // ------------------------【开始处理每个像素】-------------------- outputImage.at<Vec3b>(i,j)[0] = outputImage.at<Vec3b>(i,j)[0]/div*div + div/2; //蓝色通道 outputImage.at<Vec3b>(i,j)[1] = outputImage.at<Vec3b>(i,j)[1]/div*div + div/2; //绿色通道 outputImage.at<Vec3b>(i,j)[2] = outputImage.at<Vec3b>(i,j)[2]/div*div + div/2; //红是通道 // -------------------------【处理结束】---------------------------- } // 行处理结束 } }
对图像的简单处理方式:
ROI(region of interest)设置图像的感兴趣区域,然后对这个区域的图像进行处理。
方法一:
Mat imageROI= srcImage1(Rect(200,250,logoImage.cols,logoImage.rows));方法二:
Mat imageROI= srcImage1(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));实例:
// 添加掩膜 Mat mask= imread("dota_logo.jpg",0); //将logoImage通过mask复制到imageROI区域中 logoImage.copyTo(imageROI,mask);
掩膜(mask):这个mask是取的需要需要添加到原图中的其它图片的单通道值,比如:将A 复制到B 中,这里的mask指的就是A的灰度图,通过 A.copyTo(B,mask)方法,将A复制到B 中,通过对比相同位置mask的值是否为0来确定是否将A中对应点的像素值完全复制到B中,不为0则复制过去,为0则不复制,即掩盖住不操作,即显示B中原图像的像素值。dst(I) = src(I) if mask(I) != 0.
图象线性混合操作:
将两张大小尺寸类型相同,内容不同的图片各自按照不同的比例混合在一起。方法:
addWeighted(inputarray src1,double alpha,inputarray src2,double bate,double gamma,outputarray dst); //一般传入两个输入以及各自的占比,和一个输出,gamma一般为0,默认还有一个参数 int dtype 输出矩阵的可选深度 默认值为-1,深度为数组值的类型,如:int、long等等
颜色通道的分离、合并:
分离,将多通道的图片分离成一个个单通道的灰度图(图片的矩阵大小还是没有改变,只是通道数减少了,其像素值的大小代表了黑白之间灰色的程度),如:BGR分离成B、G和R三个单通道的灰度图。合并,将几个单通道的灰度图合并一个多通道的彩图。分离:
vector<Mat> channels; split(srcImage,channels);//分离色彩通道为单通道,并保存在向量channels imageBlueChannel= channels.at(0);//B imageGreenChannel= channels.at(1);//G imageGreenChannel= channels.at(2);//R
合并:
merge(channels,srcImage);//上面的逆过程,输出为srcImage
图像的对比度和亮度:
对比度,代表一副图像黑、白之间亮度的差异,越大对比度越大。亮度,代表单位面积上发光(反色光)的强度。
它们两个之间的关系满足如下公式:
g(i,j) = a*f(i,j)+b a代表对比度、b代表亮度
static void ContrastAndBright(int, void *) { // 创建窗口 namedWindow("【原始图窗口】", 1); // 三个for循环,执行运算 g_dstImage(i,j) = a*g_srcImage(i,j) + b for( int y = 0; y < g_srcImage.rows; y++ ) { for( int x = 0; x < g_srcImage.cols; x++ ) { for( int c = 0; c < 3; c++ ) { g_dstImage.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( (g_nContrastValue*0.01)*( g_srcImage.at<Vec3b>(y,x)[c] ) + g_nBrightValue );//通过改变图像中没有像素的值 } } } // 显示图像 imshow("【原始图窗口】", g_srcImage); imshow("【效果图窗口】", g_dstImage); }
saturate_cast 模板函数用于保护像素值溢出,小于0的赋值为0,大于255的赋值为255.
离散傅里叶变换
将图像从空间域变换到频域来处理,原因是很多时候噪声是混合在图片信号中的,在空间域我们很难将其分离,但是通过离散傅里叶变换,我们可以将连续的空间信号转换成离散的频域,这样通过噪声和原图像频域值大小的通过,我们使用滤波器将噪声剔除。//【2】将输入图像延扩到最佳的尺寸,边界用0补充 int m = getOptimalDFTSize(srcImage.rows); int n = getOptimalDFTSize(srcImage.cols); //将添加的像素初始化为0. Mat padded; copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0)); //【3】为傅立叶变换的结果(实部和虚部)分配存储空间。 //将planes数组组合合并成一个多通道的数组complexI Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) }; Mat complexI; merge(planes, 2, complexI); //【4】进行就地离散傅里叶变换 dft(complexI, complexI); //【5】将复数转换为幅值,即=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2)) split(complexI, planes); // 将多通道数组complexI分离成几个单通道数组,planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude Mat magnitudeImage = planes[0]; //【6】进行对数尺度(logarithmic scale)缩放 magnitudeImage += Scalar::all(1); log(magnitudeImage, magnitudeImage);//求自然对数 //【7】剪切和重分布幅度图象限 //若有奇数行或奇数列,进行频谱裁剪 magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2)); //重新排列傅立叶图像中的象限,使得原点位于图像中心 int cx = magnitudeImage.cols / 2; int cy = magnitudeImage.rows / 2; Mat q0(magnitudeImage, Rect(0, 0, cx, cy)); // ROI区域的左上 Mat q1(magnitudeImage, Rect(cx, 0, cx, cy)); // ROI区域的右上 Mat q2(magnitudeImage, Rect(0, cy, cx, cy)); // ROI区域的左下 Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); // ROI区域的右下 //交换象限(左上与右下进行交换) Mat tmp; q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); //交换象限(右上与左下进行交换) q1.copyTo(tmp); q2.copyTo(q1); tmp.copyTo(q2); //【8】归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式 normalize(magnitudeImage, magnitudeImage, 0, 1, CV_MINMAX);
扩展知识
加法运算快还是乘法运算快?这个要看运算量、实现算法和指令集共同决定,但是一般认为数据量很小的时候加法快,当数据量变大的时候乘法快。
1.二进制数的算术运算
二进制数的算术运算包括:加、减、乘、除四则运算,下面分别予以介绍。
(1)二进制数的加法
根据“逢二进一”规则,二进制数加法的法则为:
0+0=0
0+1=1+0=1
1+1=0 (进位为1)
1+1+1=1 (进位为1)
例如:1110和1011相加过程如下:
(2)二进制数的减法
根据“借一有二”的规则,二进制数减法的法则为:
0-0=0
1-1=0
1-0=1
0-1=1 (借位为1)
例如:1101减去1011的过程如下:
(3)二进制数的乘法
二进制数乘法过程可仿照十进制数乘法进行。但由于二进制数只有0或1两种可能的乘数位,导致二进制乘法更为简单。二进制数乘法的法则为:
0×0=0
0×1=1×0=0
1×1=1
例如:1001和1010相乘的过程如下:
由低位到高位,用乘数的每一位去乘被乘数,若乘数的某一位为1,则该次部分积为被乘数;若乘数的某一位为0,则该次部分积为0。某次部分积的最低位必须和本位乘数对齐,所有部分积相加的结果则为相乘得到的乘积。
(4)二进制数的除法
二进制数除法与十进制数除法很类似。可先从被除数的最高位开始,将被除数(或中间余数)与除数相比较,若被除数(或中间余数)大于除数,则用被除数(或中间余数)减去除数,商为1,并得相减之后的中间余数,否则商为0。再将被除数的下一位移下补充到中间余数的末位,重复以上过程,就可得到所要求的各位商数和最终的余数。
例如:100110÷110的过程如下:
所以,100110÷110=110余10。
相关文章推荐
- 【OpenCV】OpenCV3的第四天——core组件进阶
- OpenCV_(5):core组件常用函数
- 五.Core组件进阶(4.ROI区域图像混合)
- 【第二部分-图像处理】第3章 Opencv图像处理进阶-【1 图像处理A-滤波】(imgproc组件、feature2D组件)
- 五.Core组件进阶(5.通道的合并与分离)
- 《OpenCV3编程入门》第五章core组件进阶学习笔记part2
- 《OpenCV3编程入门》学习笔记五:core组件进阶
- 【OpenCV】OpenCV3的第三天——core组件
- OpenCV2入门——core组件之基础画图与算法实现
- OpenCv (正式篇)6----core组件--Mat(最基本的图像数据类型)
- OpenCV2入门——core组件之Mat详解
- [Learning OpenCV入门3] OpenCV数据结构及core组件
- 五.Core组件进阶(1.颜色空间缩减)
- 【第二部分 图像处理】第3章 Opencv图像处理进阶-【1 图像处理E-阈值化】(imgproc组件、feature2D组件)
- [Learning OpenCV入门 3]OpenCV的数据结构及core组件【未完待续】
- core组件进阶
- OpenCV从入门到放弃(三):Core组件细讲
- 五.Core组件进阶(3.访问图像中的像素点)
- 【第二部分-图像处理】第2章 Opencv图像处理初阶(core组件)
- [OpenCV基础] 2.0新版本的core数据结构