您的位置:首页 > 运维架构

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: