OpenCv图像的遍历常用方法

Home / C++ MrLee 2017-4-11 3494

虽然是opencv中最基本入门的,不过也要好好掌握.打好基础,才可以做出强大的程序.

一、遍历图像的4种方式:at<typename>(i,j)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。下面我们通过一个图像处理中的实际来说明它的用法。
在实际应用中,我们很多时候需要对图像降色彩,因为256*256*256实在太多了,在图像颜色聚类或彩色直方图时,我们需要用一些代表性的颜色代替丰富的色彩空间,我们的思路是将每个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每个颜色段取中间的颜色值作为代表色。
void colorReduce(Mat & image, int div) {
    for (int i = 0; i < image.rows; i++) {
        for (int j = 0; j < image.cols; 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;
        }
    }
}

 

image


通过上面的例子我们可以看出,at方法取图像中的点的用法:
image.at<uchar>(i,j):取出灰度图像中i行j列的点。
image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点。其中uchar,Vec3b都是图像像素值的类型,不要对Vec3b这种类型感觉害怕,其实在core里它是通过typedef Vec<T,N>来定义的,N代表元素的个数,T代表类型。
更简单一些的方法:OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。
Mat_<uchar> im=image;
im(i,j)=im(i,j)/div*div+div/2;

二、高效一点:用指针来遍历图像

上面的例程中可以看到,我们实际喜欢把原图传进函数内,但是在函数内我们对原图像进行了修改,而将原图作为一个结果输出,很多时候我们需要保留原图,这样我们需要一个原图的副本。
void colorReduce(const Mat & image, Mat & outImage, int div) {
    // 创建与原图像等尺寸的图像
    outImage.create(image.size(), image.type());
    int nr = image.rows;
    // 将3通道转换为1通道
    int nl = image.cols * image.channels();
    for (int k = 0; k < nr; k++) {
        // 每一行图像的指针
        const uchar * inData = image.ptr < uchar > (k);
        uchar * outData = outImage.ptr < uchar > (k);
        for (int i = 0; i < nl; i++) {
            outData[i] = inData[i] / div * div + div / 2;
        }
    }
}

 
从上面的例子中可以看出,取出图像中第i行数据的指针:image.ptr<uchar>(i)。
值得说明的是:程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。
但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。

三、更高效的方法

上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。
void colorReduce(const Mat & image, Mat & outImage, int div) {
    int nr = image.rows;
    int nc = image.cols;
    outImage.create(image.size(), image.type());
    if (image.isContinuous() && outImage.isContinuous()) {
        nr = 1;
        nc = nc * image.rows * image.channels();
    }
    for (int i = 0; i < nr; i++) {
        const uchar * inData = image.ptr < uchar > (i);
        uchar * outData = outImage.ptr < uchar > (i);
        for (int j = 0; j < nc; j++) { *
            outData++ = * inData++/div*div+div/2;
        }
    }
}

 
用指针除了用上面的方法外,还可以用指针来索引固定位置的像素:
image.step返回图像一行像素元素的个数(包括空白元素),image.elemSize()返回一个图像像素的大小。
&image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();

四、还有吗?用迭代器来遍历。

下面的方法可以让我们来为图像中的像素声明一个迭代器:
MatIterator_<Vec3b> it;
Mat_<Vec3b>::iterator it;
如果迭代器指向一个const图像,则可以用下面的声明:
MatConstIterator<Vec3b> it; 或者
Mat_<Vec3b>::const_iterator it;
下面我们用迭代器来简化上面的colorReduce程序:
void colorReduce(const Mat & image, Mat & outImage, int div) {
    outImage.create(image.size(), image.type());
    MatConstIterator_ < Vec3b > it_in = image.begin < Vec3b > ();
    MatConstIterator_ < Vec3b > itend_in = image.end < Vec3b > ();
    MatIterator_ < Vec3b > it_out = outImage.begin < Vec3b > ();
    MatIterator_ < Vec3b > itend_out = outImage.end < Vec3b > ();
    while (it_in != itend_in) {
        ( * it_out)[0] = ( * it_in)[0] / div * div + div / 2;
        ( * it_out)[1] = ( * it_in)[1] / div * div + div / 2;
        ( * it_out)[2] = ( * it_in)[2] / div * div + div / 2;
        it_in++;
        it_out++;
    }
}

 
如果你想从第二行开始,则可以从image.begin<Vec3b>()+image.rows开始。
上面4种方法中,第3种方法的效率最高!

五、图像的邻域操作

很多时候,我们对图像处理时,要考虑它的邻域,比如3*3是我们常用的,这在图像滤波、去噪中最为常见,下面我们介绍如果在一次图像遍历过程中进行邻域的运算。
下面我们进行一个简单的滤波操作,滤波算子为[0 –1 0;-1 5 –1;0 –1 0]。
它可以让图像变得尖锐,而边缘更加突出。核心公式即:sharp(i.j)=5*image(i,j)-image(i-1,j)-image(i+1,j)-image(i,j-1)-image(i,j+1)。
void ImgFilter2d(const Mat & image, Mat & result) {
    result.create(image.size(), image.type());
    int nr = image.rows;
    int nc = image.cols * image.channels();
    for (int i = 1; i < nr - 1; i++) {
        const uchar * up_line = image.ptr < uchar > (i - 1); //指向上一行
        const uchar * mid_line = image.ptr < uchar > (i); //当前行
        const uchar * down_line = image.ptr < uchar > (i + 1); //下一行
        uchar * cur_line = result.ptr < uchar > (i);
        for (int j = 1; j < nc - 1; j++) {
            cur_line[j] = saturate_cast < uchar > (5 * mid_line[j] - mid_line[j - 1] - mid_line[j + 1] -
                up_line[j] - down_line[j]);
        }
    }
    // 把图像边缘像素设置为0
    result.row(0).setTo(Scalar(0));
    result.row(result.rows - 1).setTo(Scalar(0));
    result.col(0).setTo(Scalar(0));
    result.col(result.cols - 1).setTo(Scalar(0));
}

 

image


上面的程序有以下几点需要说明:
1,staturate_cast<typename>是一个类型转换函数,程序里是为了确保运算结果还在uchar范围内。
2,row和col方法返回图像中的某些行或列,返回值是一个Mat。
3,setTo方法将Mat对像中的点设置为一个值,Scalar(n)为一个灰度值,Scalar(a,b,c)为一个彩色值。

六、图像的算术运算

Mat类把很多算数操作符都进行了重载,让它们来符合矩阵的一些运算,如果+、-、点乘等。
下面我们来看看用位操作和基本算术运算来完成本文中的colorReduce程序,它更简单,更高效。
将256种灰度阶降到64位其实是抛弃了二进制最后面的4位,所以我们可以用位操作来做这一步处理。
首先我们计算2^8降到2^n中的n:int n=static_cast<int>(log(static_cast<double>(div))/log(2.0));
然后可以得到mask,mask=0xFF<<n;
用下面简直的语句就可以得到我们想要的结果:
result=(image&Scalar(mask,mask,mask))+Scalar(div/2,div/2,div/2);
很多时候我们需要对图像的一个通信单独进行操作,比如在HSV色彩模式下,我们就经常把3个通道分开考虑。
vector<Mat> planes;
// 将image分为三个通道图像存储在planes中
split(image,planes);
planes[0]+=image2;
// 将planes中三幅图像合为一个三通道图像
merge(planes,result);

 

本文链接:https://www.it72.com/12014.htm

推荐阅读
最新回复 (0)
返回