减少颜色数目

来源:互联网 发布:软件测试shell脚本 编辑:程序博客网 时间:2024/05/18 01:43

预备知识:
如常见的RGB24图像有256×256×256中颜色,通过Reduce Color将每个通道的像素减少8倍至256/8=32种,则图像只有32×32×32种颜色。假设量化减少的倍数是N,则代码实现时就是简单的value/N*N,通常我们会再加上N/2以得到相邻的N的倍数的中间值,最后图像被量化为(256/N)×(256/N)×(256/N)种颜色。
data[i]是整数(假设原来是120),x/div得到的是商(1),余数被舍弃,再×div得到的是64,再加上div/2就是64+32=98。推广点我们可以想到64~127之间的数经过上述运算得到的都是98,其他区间的数可以依此类推。这样就起到了压缩色彩空间的作用。data[i]相当于*(data+i)“

版本一:void ReduceColor(Mat& image,int div){    int n1=image.rows;//行数    int n2=image.cols*image.channels();//每行的列数    for(int i=0;i<n1;i++){        uchar* data=image.ptr<uchar>(i);//每行的首地址,利用ptr        for(int j=0;j<n2;j++)        {            data[j]=data[j]-data[j]%(div)+div/2;        }    }}int main(){    Mat image=imread("F:\\opencv_test\\14.jpg");    namedWindow("1");    imshow("1",image);    //Mat image2=image.clone();    ReduceColor(image,64);    namedWindow("2");    imshow("2",image);    waitKey(0);    return 0;}

其他的颜色缩减公式:
data[j]=data[j]/div*div+div/2;
*data++=*data/div*div+div/2;

版本二:通用版本,允许用户指定,输入和输出图像。
之前的例子,变换直接作用在输入图像上的,我们称之为In_place变换。这种方式,不需要额外的图像来保存输出的结果,可以节省一定的内存。但是一些情况下,用户不希望原始图像被改变。最简单创建一个图像深拷贝的方式是调用clone函数,Mat imageclone=image.clone();ReduceColor(imageclone); 实际中我们可以实现这一机制,当我们输入输出是同意变量时仍采用的是IN_place方式,否则用户必须提供一个Mat的实例;
代码当中首先用creat函数创建一个与输入图像的尺寸和类型相同的矩阵,注意creat函数创建的图像内存都是连续的,不会对图像的行进行填补,分配的内存大小为total()*elemSize()

#include<opencv2\opencv.hpp>using namespace cv;void ReduceColor(Mat& image,Mat& result,int div){    result.create(image.rows,image.cols,image.type());    int n1=image.rows;//行数    int n2=image.cols*image.channels();//每行的列数    for(int i=0;i<n1;i++){        uchar* data_out=result.ptr<uchar>(i);        uchar* data=image.ptr<uchar>(i);//每行的首地址,利用ptr        for(int j=0;j<n2;j++)        {            //data[j]=data[j]/div*div+div/2;与下面语句等价            //*data++=*data/div*div+div/2;            data_out[j]=data[j]-data[j]%(div)+div/2;        }    }}int main(){    Mat result,image=imread("F:\\opencv_test\\14.jpg");    namedWindow("1");    imshow("1",image);    //Mat image2=image.clone();    ReduceColor(image,result,64);    namedWindow("2");    imshow("2",result);    waitKey(0);    return 0;}

版本三:高效遍历连续图像(这种方法最高效)
考虑到效率,图像可能会在行尾,扩大若干个像素;但是当不对图像进行行填补的时候,图像可以视为一个W*H的一维数组;可以通过Mat的一个成员函数isContinuous来判断这幅图像是否进行了填补,如果isContinuous返回值为真,说明没有对图像进行填补;因此,在一些图像中我们可以利用图像的连续性,把整个处理过程用一个循环完成;则上面的颜色缩减函数可以重写;

void ReduceColor(Mat& image,int div){    int rn=image.rows;    int cn=image.cols*image.channels();    if(image.isContinuous()){        cn=rn*cn;        rn=1;    }    for(int i=0;i<rn;i++){        uchar* data=image.ptr<uchar>(i);        for(int j=0;j<cn;j++)        {            data[j]=data[j]/div*div+div/2;        }    }}

版本四:使用底层指针运算
在Mat中,图像数据以usigned char形式保存在一块内存中,这块内存的首地址可以通过data成员变量得到,其中data是一个usigned char的指针,则循环可以如下方式开始:
uchar* data=image.data;从当前一行到下一行可以用指针加上行宽得到,data+=image.step;其中step代表图像的行宽(包括填补的像素),则可以通过如下方式得到第j行、第i列元素的地址data=image.data+j*image.step+i*elemSize(); 建议不使用这种方式,容易出错,且不适应于带感兴趣区域(ROI)的图像;

void ReduceColor(Mat& image,int div){    int rn=image.rows;    int cn=image.cols*image.channels();    uchar* data=image.data;    for(int i=0;i<rn;i++){        for(int j=0;j<cn;j++)        {            data[j]=data[j]/div*div+div/2;        }        data=data+image.step;    }           }

版本五:使用迭代器遍历图像
迭代器的定义方式Mat_::iterator iter=image.begin();其中begin和end也需要用模板化的版本;因为这里使用的是彩色图像,所以返回类型是Vec3b,每个颜色分量可以使用[]得到;

void ReduceColor(Mat& image,int div){Mat_<Vec3b>::iterator iter=image.begin<Vec3b>();//Mat_<Vec3b>::iterator iter2=image.end<Vec3b>();while(iter!=image.end<Vec3b>())//也可以用for循环{    (*iter)[0]=(*iter)[0]/div*div+div/2;    (*iter)[1]=(*iter)[1]/div*div+div/2;    (*iter)[2]=(*iter)[2]/div*div+div/2;    iter++;}           }

版本六:使用图像坐标

void ReduceColor(Mat& image,int div){int rn=image.rows ;int cn=image.cols;if(image.channels()==1){    for(int i=0;i<rn;i++)        for(int j=0;j<cn;j++)            image.at<uchar>(i,j)=image.at<uchar>(i,j)/div*div+div/2;}  else if(image.channels()==3){for(int i=0;i<rn;i++){    for(int j=0;j<cn;j++){        image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2;        image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2;        image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2;    }}}}

计算函数的运行时间:
getTickCount()返回上次开机算起的时钟周期数,由于我们需要的是函数运行的时间,因此需要另外一个函数getTickFrequency(),该函数返回每秒内的周期数。注意,返回时间的单位是秒

int main(){    Mat result,image=imread("F:\\opencv_test\\14.jpg");    double dur;    dur=static_cast<double>(getTickCount());    ReduceColor(image,64);    dur=static_cast<double>(getTickCount())-dur;//static_cast进行强制类型转换,转成double形    dur=dur/getTickFrequency();    namedWindow("2");    imshow("2",image);    cout<<dur<<endl;    waitKey(0);    return 0;}
0 0
原创粉丝点击