今天在看矩形滤波的时候忽然脑子短路,把一些概念全弄混了,现总结一下,以便下次再混的时候可以参考确认下,自己的理解,有错的地方还请指正。

         首先,在Opencv2中基本上都是用的Mat来表示图像了,C++的函数调用中基本上也都是Mat图,从根本上说,一张图像是一个由数值组成的矩阵,矩阵的每一个元素代表一个像素。对于灰度图像而言,像素有8位无符号数表示,其中0代表黑色,255代表白色。那么矩阵和图像间到底是一个什么样的关系呢。

         第一:Mat图有行和列,即cv::Mat中有公有成员变量cols和rows,注意,这里的cols就是图像的宽度width,rows就是图像的高度height。这个width和height我们可以在其它Opencv的成员中得到,比如矩形Rect,而矩形Rect就是一个经常会用到的结构了,我自己接触到的就包括鼠标选择矩形区域、框住目标的矩形区域、滤波器矩形模版、目标的矩形特征、矩形内的运算等等。可以说Rect是一个非常常用的结构,也是Opencv里非常有用的一个结构,本质上矩形区域就是图像的一个子部分,或者说图像矩阵的一个子矩阵。

         这里我引用《OpenCV学习笔记(四十一)——再看基础数据结构core》中关于Rect的介绍,Rect_类有些意思,成员变量x、y、width、height,分别为左上角点的坐标和矩形的宽和高。常用的成员函数有Size()返回值为一个Size,area()返回矩形的面积,contains(Point)用来判断点是否在矩形内,inside(Rect)函数判断矩形是否在该矩形内,tl()返回左上角点坐标,br()返回右下角点坐标。

         第二:Mat图中的图像像素位置表示和矩阵中元素的表示。这里引用《访问Mat图像中每个像素的值》中几张图来表示Mat矩阵中存数据的关系。单通道灰度图数据存放格式:

                                             

opencv 像素配准 opencv像素值_像素点

                      

多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:

                           

opencv 像素配准 opencv像素值_迭代器_02

                  

这时,大家得注意了,二维矩阵的行和列用来表示一个元素,并且一般是从0标号开始,所以实际上是有m+1列,也就是说宽度width是m+1的,行类似。还有就是Mat.at(int y, int x)来访问一个像素,这时候的y表示的行号,x表示的列号,相对应的就是x表示水平的宽,y表示的竖直的高,只不过x和y都是从0开始的标号。容易搞混的地方就在于一些矩阵的相减了,相减完后怎么表示像素位置,这个时候一般比较难把握,但是只要明白矩阵里x,y,width,height的关系,搞清楚就容易多了。

Mat dst;
 int height = dst.rows;     int width = dst.cols;
for (int i = 0; i < height; i++) {             for (int j = 0; j < width; j++) {
//假如以十字形遍历索引,则十字中心(i * width + j),上下分别是((i -1)* width + j)((i +1)* width + j)
//左右分别是(i * width + j-1)(i * width + j+1)
//这里的i是代表行数,j代表列数,即所在的行的第几列                 int index = i * width + j;                 //像素值                         int data = (int)dst.data[index];                                 }
            }             
 
 
//=====================================指针法==================================================//
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
Mat img=imread("f:/1.jpg");
int height=img.rows;
int width=img.cols;
for(int i=0;i<height;i++)
{
unsigned char *data=img.data+i*width*img.channels();
for(int j=0;j<width;j++)
{
int r=*(data+j*img.channels());
int g=*(data+j*img.channels()+1);
int b=*(data+j*img.channels()+2);
cout<<r<<" "<<g<<" "<<b<<endl;
}
}        
img.release();
system("pause");
return 1;
}
//============================================================================================//

推荐使用C++格式,比较方便使用

#include "WangSetup.h"
 
#include <iostream>
#include <cv.h>
#include <highgui.h>
 
using namespace std;
 
int main()
{
//C++ Format
cv::Mat img = cv::imread("lena.jpg");
//取img中(30, 20)这个像素点的bgr信息
cv::Vec3b bgr = img.at<cv::Vec3b>(30, 20);
cout << "B: " << (unsigned int)bgr.val[0] << ", ";
cout << "G: " << (unsigned int)bgr.val[1] << ", ";
cout << "R: " << (unsigned int)bgr.val[2] << endl;
 
 
//C Format
IplImage *img2 = cvLoadImage("lena.jpg");        //8UC3, (0,0)B, (0,0)G, (0,0)R, (0,1)B, ...
char *ptr = img2->imageData       //图像首地址
+ img2->widthStep * 30        //每行大小 * 行数
+ 3 * 20;                    //BGR占3个大小空间 * 列数
printf("B: %d, G: %d, R: %d\n", (uchar)ptr[0], (uchar)ptr[1], (uchar)ptr[2]);
cvReleaseImage(&img2);
 
return 0;
}

 

 

 

1.用动态地址操作像素:

Mat srcImage(100, 100, CV_8UC3, Scalar(200,20,100));
  
     imshow("显示图像", srcImage);
  
  
     int rowNumber = srcImage.rows;
     int colNumber = srcImage.cols;
  
  
     for (int i = 0; i < rowNumber; i++)
     {
         for (int j = 0; j < colNumber; j++)
         {
             if (srcImage.at<Vec3b>(i, j)[0] > 180) 
             {
                 srcImage.at<Vec3b>(i, j)[0] = 0;            
             }
             
             if (srcImage.at<Vec3b>(i, j)[1] < 50) 
             {
                 srcImage.at<Vec3b>(i, j)[1] = 255;
             }
  
             if (srcImage.at<Vec3b>(i, j)[2] < 120) 
             {
                 srcImage.at<Vec3b>(i, j)[2] = 0;
             }
  
         }
     }

      imshow("处理后的图像", srcImage); cv::mat的成员函数: .at(int y, int x)可以用来存取图像中对应坐标为(x,y)的元素坐标。(Mat类中的cols和rows给出了图像的宽和高。而成员函数at(int x, int y)可以用来存取图像的元素。)由于at方法本身不会对任何数据类型进行转化,故一定要确保指定的数据类型和矩阵中的数据类型相符合。 假设提前已知一幅图像img的数据类型为 unsigned char型灰度图(单通道),对像素的赋值操作为image.at<uchar>(i,j) = value。而对于彩色图像,每个像素由三个部分构成:蓝色通道、绿色通道和红色通道(BGR),对于一个包含彩色图像的Mat,会返回一个由三个8位数组组成的量。OpenCV将此类型定义为Vec3b,即由三个unsigned char组成的向量。这也解释了为什么存取彩色图像像素的代码可以写成:image.at<Vec3b>(i,j)[channel] = value;

以下是统计canndy后的0像素点与255像素点之间的数量的比值:

#define _CRT_SECURE_NO_WARNINGS
  
 #include <iostream>
 #include <opencv2/opencv.hpp>
  
 using namespace std;
 using namespace cv;
  
 int main() 
 {
     Mat graySrc = imread("../../11.bmp", 0);
  
     Mat canImage;
     Canny(graySrc, canImage, 60, 120);
  
     int PicZero = 0;
     int PicFull = 0;
  
     for (int i = 0; i < graySrc.rows; ++i) 
     {
         for (int j = 0; j < graySrc.cols; ++j) 
         {
             if (canImage.at<unsigned char>(i, j) == 0) 
             {
                 PicZero++;
             }
             else
             {
                 PicFull++;
             }    
         }
     
     }
  
     cout << "0像素点比255像素点的比值为" << (double)PicZero / PicFull << endl;
     system("pause");
 }

2.用指针的方法:

有时候我们需要遍历Mat中的每一个像素点,并且对像素点进行处理,这里以图像所有像素点都减去div(div属于int类型)

void colorReduce(Mat& inputImage, Mat& outputImage, int div)
 {
     // 参数准备
     outputImage = inputImage.clone();
  
     int rowNumber = outputImage.rows;
     int colNumber = outputImage.cols*outputImage.channels();
  
     for (int i = 0; i < rowNumber; i++)
     {
         // 获取第i行的首地址
         uchar* data = outputImage.ptr<uchar>(i);
  
         for (int j = 0; j < colNumber; j++)  // 列循环
         {
             // 开始处理每一个像素值,每一个像素值都减去div
             data[j] = data[j] - div;
         }
     }
 }

也可以写成如下形式:

Mat inverseColor1(Mat srcImage) 
 
{
     Mat tempImage = srcImage.clone();
     int row = tempImage.rows;
     int col = tempImage.cols * tempImage.channels();
  
     for (int i = 0; i < row; ++i) 
     {
         const unsigned char* sourcedata = srcImage.ptr(i);
         unsigned char* data = tempImage.ptr(i);
         for (int j = 0; j < col; j++)
         {
             data[j] = sourcedata[j] - div;
         }
     }
     return tempImage;
 }

此时是定义了两个指针类型: const unsigned char*和 unsigned char*,其中const unsigned char* 中的内容只能够被读取,不能被修改。

特别需要注意的是:Mat中每一行元素的个数=列数*通道数

如需要打印M,

    Mat M(3, 2, CV_8UC3, Scalar(0, 0, 255));     cout << M << endl; 打印结果为:验证了每一行元素的个数为: 列数*通道数

另外需要注意的是:Mat 除了拥有成员变量cols,rows,成员函数channels()之外,还提供了ptr函数可以返回得到图像任意行的首地址。

3.用迭代器Matlterator_:

        Matlterator_是Mat数据操作的迭代器,:begin()表示指向Mat数据的起始迭代器,:end()表示指向Mat数据的终止迭代器。迭代器方法是一种更安全的用来遍历图像的方式,首先获取到数据图像的矩阵起始,再通过递增迭代实现移动数据指针。

Mat inverseColor4(Mat srcImage) 
 {
     Mat tempImage = srcImage.clone();
  
     // 初始化原图像迭代器
     MatConstIterator_<Vec3b> srcIterStart = srcImage.begin<Vec3b>();
     MatConstIterator_<Vec3b> srcIterEnd = srcImage.end<Vec3b>();
  
     // 初始化输出图像迭代器
     MatIterator_<Vec3b> resIterStart = tempImage.begin<Vec3b>();
     MatIterator_<Vec3b> resIterEnd = tempImage.end<Vec3b>();
  
     while (srcIterStart != srcIterEnd) 
     {
         (*resIterStart)[0] = 255 - (*srcIterStart)[0];
         (*resIterStart)[1] = 255 - (*srcIterStart)[1];
         (*resIterStart)[2] = 255 - (*srcIterStart)[2];
  
         srcIterStart++;
         resIterStart++;
     }
  
     return tempImage;
  
 }

 

 

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)种颜色。

方法零:.ptr和[]操作符 Mat最直接的访问方法是通过.ptr<>函数得到一行的指针,并用[]操作符访问某一列的像素值。

// using .ptr and []
 void colorReduce0(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols * image.channels(); // total number of elements per line
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
                   data[i]= data[i]/div*div + div/2;
             }                  
       }
 }

方法一:.ptr和指针操作 除了[]操作符,我们可以移动指针*++的组合方法访问某一行中所有像素的值。

// using .ptr and * ++ 
 void colorReduce1(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols * image.channels(); // total number of elements per line
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
                  *data++= *data/div*div + div/2;
             } // end of row                 
       }
 }

方法二:.ptr、指针操作和取模运算 方法二和方法一的访问方式相同,不同的是color reduce用模运算代替整数除法

// using .ptr and * ++ and modulo
 void colorReduce2(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols * image.channels(); // total number of elements per line
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
                   int v= *data;
                   *data++= v - v%div + div/2;
             } // end of row                 
       }
 }

方法三:.ptr、指针运算和位运算 由于进行量化的单元div通常是2的整次方,因此所有的乘法和除法都可以用位运算表示。

// using .ptr and * ++ and bitwise
 void colorReduce3(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols * image.channels(); // total number of elements per line
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
             *data++= *data&mask + div/2;
             } // end of row                 
       }
 }

方法四:指针运算 方法四和方法三量化处理的方法相同,不同的是用指针运算代替*++操作。

// direct pointer arithmetic
 void colorReduce4(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols * image.channels(); // total number of elements per line
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       int step= image.step; // effective width
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       // get the pointer to the image buffer
       uchar *data= image.data;
       for (int j=0; j<nr; j++) {
           for (int i=0; i<nc; i++) {
             *(data+i)= *data&mask + div/2;
             } // end of row                 
             data+= step;  // next line
       }
 }

方法五:.ptr、*++、位运算以及image.cols * image.channels() 这种方法就是没有计算nc,基本是个充数的方法。

// using .ptr and * ++ and bitwise with image.cols * image.channels()
 void colorReduce5(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<image.cols * image.channels(); i++) {
             *data++= *data&mask + div/2;
             } // end of row                 
       }
 }

 

方法六:连续图像 Mat提供了isContinuous()函数用来查看Mat在内存中是不是连续存储,如果是则图片被存储在一行中。

// using .ptr and * ++ and bitwise (continuous)
 void colorReduce6(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols * image.channels(); // total number of elements per line
       if (image.isContinuous())  {
           // then no padded pixels
           nc= nc*nr; 
           nr= 1;  // it is now a 1D array
        }
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
             *data++= *data&mask + div/2;
             } // end of row                 
       }
 }

方法七:continuous+channels 与方法六基本相同,也是充数的。

// using .ptr and * ++ and bitwise (continuous+channels)
 void colorReduce7(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols ; // number of columns
       if (image.isContinuous())  {
           // then no padded pixels
           nc= nc*nr; 
           nr= 1;  // it is now a 1D array
        }
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       for (int j=0; j<nr; j++) {
           uchar* data= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
             *data++= *data&mask + div/2;
             *data++= *data&mask + div/2;
             *data++= *data&mask + div/2;
             } // end of row                 
       }
 }

方法八:Mat _iterator 真正有区别的方法来啦,用Mat提供的迭代器代替前面的[]操作符或指针,血统纯正的官方方法~

// using Mat_ iterator 
 void colorReduce8(cv::Mat &image, int div=64) {
       // get iterators
       cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
       cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::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;
       }
 }

 

方法九:Mat_ iterator 和位运算 把方法八中的乘除法换成位运算。

// using Mat_ iterator and bitwise
 void colorReduce9(cv::Mat &image, int div=64) {
       // div must be a power of 2
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       // get iterators
       cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
       cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
       for ( ; it!= itend; ++it) {
         (*it)[0]= (*it)[0]&mask + div/2;
         (*it)[1]= (*it)[1]&mask + div/2;
         (*it)[2]= (*it)[2]&mask + div/2;
       }
 }

方法十:MatIterator_ 和方法八基本相同。

// using MatIterator_ 
 void colorReduce10(cv::Mat &image, int div=64) {
       cv::Mat_<cv::Vec3b> cimage= image;
       cv::Mat_<cv::Vec3b>::iterator it=cimage.begin();
       cv::Mat_<cv::Vec3b>::iterator itend=cimage.end();
       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;
       }
 }
  

方法十一:图像坐标

// using (j,i)
 void colorReduce11(cv::Mat &image, int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols; // number of columns
       for (int j=0; j<nr; j++) {
           for (int i=0; i<nc; i++) {
                   image.at<cv::Vec3b>(j,i)[0]=     image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
                   image.at<cv::Vec3b>(j,i)[1]=     image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
                   image.at<cv::Vec3b>(j,i)[2]=     image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
             } // end of row                 
       }
 }

方法十二:创建输出图像 之前的方法都是直接修改原图,方法十二新建了输出图像,主要用于后面的时间对比。

// with input/ouput images
 void colorReduce12(const cv::Mat &image, // input image 
                  cv::Mat &result,      // output image
                  int div=64) {
       int nr= image.rows; // number of rows
       int nc= image.cols ; // number of columns
       // allocate output image if necessary
       result.create(image.rows,image.cols,image.type());
       // created images have no padded pixels
       nc= nc*nr; 
       nr= 1;  // it is now a 1D array
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       for (int j=0; j<nr; j++) {
           uchar* data= result.ptr<uchar>(j);
           const uchar* idata= image.ptr<uchar>(j);
           for (int i=0; i<nc; i++) {
             *data++= (*idata++)&mask + div/2;
             *data++= (*idata++)&mask + div/2;
             *data++= (*idata++)&mask + div/2;
           } // end of row                 
       }
 }

方法十三:重载操作符 Mat重载了+&等操作符,可以直接将两个Scalar(B,G,R)数据进行位运算和数学运算。

// using overloaded operators
 void colorReduce13(cv::Mat &image, int div=64) {
       int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
       // mask used to round the pixel value
       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
       // perform color reduction
       image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);
 }

时间对比 通过迭代二十次取平均时间,得到每种方法是运算时间如下。

可以看到,指针*++访问和位运算是最快的方法;而不断的计算image.cols*image.channles()花费了大量重复的时间;另外迭代器访问虽然安全,但性能远低于指针运算;通过图像坐标(j,i)访问时最慢的,使用重载操作符直接运算效率最高。