OpenCV3——core组件进阶

颜色空间缩减

以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;
forint i= 0;i < 256;++i)//遍历所有像素
pi[i] = divideWith*(i/divideWith);//颜色缩减
//调用函数(I是输入,J是输出)
forint 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。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值