OpenCV 通过Mat遍历图像的方法汇总

目录
  • 方法一、直接对图像像素修改.at<typename>(i,j)
  • 二、用指针.ptr<uchar>(k)来遍历输入图像,数组[]生成输出图像
  • 三、用指针.ptr<uchar>(k)来遍历输入图像,指针方式生成输出图像
  • 四、用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合位运算生成输出图像
  • 五、用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合取模运算生成输出图像
  • 六、连续图像isContinuous()函数方法。
  • 七、迭代器Mat_iterator方法。

我们在实际应用中对图像进行的操作,往往并不是将图像作为一个整体进行操作,而是对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题。

Color Reduce
还是使用经典的Reduce Color的例子,即对图像中的像素表达进行量化。如常见的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)种颜色。

并对图像降色彩后的彩色直方图进行统计。

方法一、直接对图像像素修改.at<typename>(i,j)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。

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;
        }
    }
}

通过上面的例子我们可以看出,at方法取图像中的点的用法:

image.at<uchar>(i,j):取出灰度图像中i行j列的点。
image.at<Vec3b>(i,j)[k]:取出彩色图像中i行j列第k通道的颜色点,k=[0,1,2],分别代表B,G,R。

其中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;

二、用指针.ptr<uchar>(k)来遍历输入图像,数组[]生成输出图像

上面的例程中可以看到,我们实际喜欢把原图传进函数内,但是在函数内我们对原图像进行了修改,而将原图作为一个结果输出,很多时候我们需要保留原图,这样我们需要一个原图的副本。

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)。

值得说明的是:程序中将3通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。也就是一幅图像数据最开始的三个值,是最左上角的那像素的三个通道的值。

但是这种用法不能用在行与行之间,因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。但是我们可以申明一个连续的空间来存储图像,这个话题引入下面最为高效的遍历图像的机制。

三、用指针.ptr<uchar>(k)来遍历输入图像,指针方式生成输出图像

与上述方法二遍历图像的方法相同,而生成输出图像的方式从数组换成了指针的方式。因此只需改动一句话。

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++ = *inData++ / div*div + div / 2;
        }
    }
}

四、用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合位运算生成输出图像

与上述方法遍历图像的方法相同,而生成输出图像的方式从加减乘除基本四则运算的方式换成了位运算的方式。

这里特别需要注意的是,位运算的优先级是低于乘除加减的,所以一定要在位运算加括号。

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();
    //对数换底公式log a(b) = log b/log a
    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    // mask used to round the pixel value  e.g. for div=16, mask= 0xF0
    uchar mask = 0xFF << n;
    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++ = (*inData++ & mask)  + div / 2;
        }
    }
}

五、用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合取模运算生成输出图像

与上述方法遍历图像的方法相同,而生成输出图像的方式从位运算的方式换成了取模运算的方式。

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();

    int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
    // mask used to round the pixel value  e.g. for div=16, mask= 0xF0
    uchar mask = 0xFF << n;
    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++)
        {
            int Data = *inData++;
            *outData++ = Data - Data%div + div / 2;
        }
    }
}

六、连续图像isContinuous()函数方法。

上面已经提到过了,一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,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();

七、迭代器Mat_iterator方法。

下面的方法可以让我们来为图像中的像素声明一个迭代器:

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

开始。

上面7种方法中,第4种方法的效率最高!

到此这篇关于OpenCV 通过Mat遍历图像的几种方法的文章就介绍到这了,更多相关OpenCV遍历图像内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • opencv实现回形遍历像素算法

    本文实例为大家分享了opencv实现回形遍历像素算法的具体代码,供大家参考,具体内容如下 代码实现 # -*- coding:utf-8 -*- import cv2 import numpy as np cv2.namedWindow('img', 0) def traversePixelByCycloidLine(image): """ 从一副灰度图像的中心开始向边缘按回形线的方式遍历所有像素,每个像素只能访问一次. 我目前实现了基本的算法, 但存在以下问题: 1) 只支

  • OpenCV 通过Mat遍历图像的方法汇总

    目录 方法一.直接对图像像素修改.at<typename>(i,j) 二.用指针.ptr<uchar>(k)来遍历输入图像,数组[]生成输出图像 三.用指针.ptr<uchar>(k)来遍历输入图像,指针方式生成输出图像 四.用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合位运算生成输出图像 五.用指针.ptr<uchar>(k)来遍历输入图像,指针方式结合取模运算生成输出图像 六.连续图像isContinuous()函数方法. 七

  • OpenCV实现特征检测和特征匹配方法汇总

    目录 1.SURF 2.SIFT 3.ORB 4.FAST 5.Harris角点 一幅图像中总存在着其独特的像素点,这些点我们可以认为就是这幅图像的特征,成为特征点.计算机视觉领域中的很重要的图像特征匹配就是一特征点为基础而进行的,所以,如何定义和找出一幅图像中的特征点就非常重要.这篇文章我总结了视觉领域最常用的几种特征点以及特征匹配的方法. 在计算机视觉领域,兴趣点(也称关键点或特征点)的概念已经得到了广泛的应用, 包括目标识别. 图像配准. 视觉跟踪. 三维重建等. 这个概念的原理是, 从图

  • Python OpenCV读取中文路径图像的方法

    引言 这几天做点小东西,涉及到OpenCV读取中文图像的问题 如果直接读取中文路径的图像,往往返回[] import cv2 cv_im = cv2.imread('老干妈.jpg') 缘起 偶然发现opencv 读取图像,解决imread不能读取中文路径的问题文章,代码简单有效,可以参考下文章底部附录 im = cv2.imdecode(np.fromfile(im_name,dtype=np.uint8),-1) 但是作者代码注释中说该方法读取的图像的通道就会变为RGB,但是我实验仍为BGR

  • PHP遍历数组的方法汇总

    今天有个朋友问我一个问题php遍历数组的方法,告诉她了几个.顺便写个文章总结下,如果总结不全还请朋友们指出 第一.foreach() foreach()是一个用来遍历数组中数据的最简单有效的方法. <?php $urls= array('aaa','bbb','ccc','ddd'); foreach ($urls as $url){ echo "This Site url is $url! <br />"; } ?> 显示结果: This Site url i

  • 基于PHP遍历数组的方法汇总分析

    1. foreach()foreach()是一个用来遍历数组中数据的最简单有效的方法.#example1: 复制代码 代码如下: <?php$colors= array('red','blue','green','yellow');foreach ($colorsas$color){echo "Do you like $color? <br />";}?> 显示结果:Do you like red? Do you like blue? Do you like g

  • java通过jni调用opencv处理图像的方法

    1. 建立java文件 public class getImageFeature { static{ System.loadLibrary("getImageFeatureDll"); } public native int getImageFeatureByName(String filename); public native int getImageFeatureByMemory(); public static void main(String[] args) { getIma

  • python+opencv图像分割实现分割不规则ROI区域方法汇总

    在图像分割领域,一个重要任务便是分割出感兴趣(ROI)区域.如果是简易的矩形ROI区域其实是非常容易分割的,opencv的官方python教程里也有教到最简易的矩形ROI分割(剪裁),其本质是多维数组(矩阵)的切片.但是现实情况中,ROI是不规则的多边形,也可能是曲线边界,那么该如何分割出来呢?下面总结几种思路. 可能只提供核心部分的代码示例,具体应用要结合你自己的项目来修正. 一.已知边界坐标,直接画出多边形 例:最基础的画个四边形 # 定义四个顶点坐标 pts = np.array([[10

  • opencv读取视频并保存图像的方法

    问题重述 ​ 实习项目要做安全帽目标检测,拿到了公司给的一些视频数据,使用Opencv读取视频并每隔 1 s 1s 1s存储一副图像,下面是一些视频数据 实现步骤 添加依赖库 import cv2 import os 定义视频路径和图像存储路径 video_path = './未戴安全帽视频01/' image_path = './images/' 读取视频文件 video_files = [i for i in os.listdir(video_path) if i.split('.')[-1

  • OpenCV实现图像平滑处理的方法汇总

    目录 1 均值滤波 2 方框滤波 3 高斯滤波 4 中值滤波 5 双边滤波 6 2D卷积(自定义卷积核实现卷积) 本文是OpenCV图像视觉入门之路的第10篇文章,本文详细的在图像上面进行了图像均值滤波.方框滤波 .高斯滤波.中值滤波.双边滤波.2D卷积等操作. 1 均值滤波 OpenCV 中的简单滤波(例如均值滤波或中值滤波)可能会使图像变得模糊,因为它们会平滑图像中的细节和边缘.如果滤波的程度过强,则可能会导致图像失真,使其看起来更差.因此,滤波的程度需要适当地控制,以获得最佳效果. imp

  • jQuery遍历节点方法汇总(推荐)

    1.children()方法:$('div').children()---遍历查找div元素的所有子元素节点 <p>Hello</p> <div> <span>Hello Again</span> <p class="box">您好!</p> </div> <p>And Again</p> <script type="text/javascript&q

随机推荐