opencv学习笔记-03

我们将探索以下问题的答案:

  • 如何遍历图像中的每一个像素?
  • OpenCV的矩阵值是如何存储的?
  • 如何测试我们所实现算法的性能?
  • 查找表是什么?为什么要用它?

高效的遍历像素的方法

C风格运算符

说到性能,经典的C风格运算符[](指针)访问要更胜一筹. 因此,我们推荐的效率最高的查找表赋值方法,还是下面的这种:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices,只接受uchar类型的Mat
    CV_Assert(I.depth() != sizeof(uchar));     

    int channels = I.channels();

    int nRows = I.rows * channels; //想象一下像素排列的方式,bgr连在一起,排在一个位置
    int nCols = I.cols;

    if (I.isContinuous())	//I.isContinuous()返回bool,判断数组是否为一行
    {
        nCols *= nRows;
        nRows = 1;         
    }

    int i,j;
    uchar* p; 
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];             
        }
    }
    return I; 
}
CV_Assert()函数与C++标准库中的assert()函数功能基本相同。
assert 的作用是现计算表达式 expression ,如果其值为假(即为 0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。
Mat::depth()函数求矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么depth则是CV_16S。depth也是一系列的预定义值, 将type的预定义值去掉通道信息就是depth值: CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F
很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用 isContinuous() 来去判断矩阵是否是连续存储的. 

迭代法 The iterator (safe) method

在高性能法(the efficient way)中,我们可以通过遍历正确的 uchar 域并跳过行与行之间可能的空缺-你必须自己来确认是否有空缺,来实现图像扫描,迭代法则被认为是一种以更安全的方式来实现这一功能。在迭代法中,你所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     
    
    const int channels = I.channels();
    switch(channels)
    {
    case 1: 
        {
            MatIterator_<uchar> it, end; 
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];
            break;
        }
    case 3: 
        {
            MatIterator_<Vec3b> it, end; 
            for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    
    return I; 
}

为了得到最优的结果,你最好自己编译并运行这些程序. 为了更好的表现性能差异,我用了一个相当大的图片(2560 X 1600). 性能测试这里用的是彩色图片,结果是数百次测试的平均值.

Efficient Way(C[]) 79.4717 milliseconds
Iterator 83.7201 milliseconds

矩阵指针

mat.ptr<type>(row)[col]
//对于Mat的ptr函数,返回的是<>中的模板类型指针,指向的是()中的第row行的起点
//通常<>中的类型和Mat的元素类型应该一致
//然后再用该指针去访问对应col列位置的元素

mat.data
 /*data会从 Mat 中返回指向矩阵第一行第一列的指针。
 注意如果该指针为NULL则表明对象里面无输入,所以这是一种简单的检查图像是否被成功读入的方法。
 当矩阵是连续存储时,我们就可以通过遍历 data 来扫描整个图像。*/
uchar* p = I.data;
for( unsigned int i =0; i < ncol*nrows; ++i)
    *p++ = table[*p];

掩码操作(掩模操作)filter2D函数

filter2D( InputArray src, OutputArray dst, int ddepth,
                            InputArray kernel, Point anchor=Point(-1,-1),
                            double delta=0, int borderType=BORDER_DEFAULT );
                            
InputArray src: 输入图像
OutputArray dst: 输出图像,和输入图像具有相同的尺寸和通道数量
int ddepth: 目标图像深度,如果没写将生成与原图像深度相同的图像。
				原图像和目标图像支持的图像深度如下:
				src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F
			    src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F
			    src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F
			    src.depth() = CV_64F, ddepth = -1/CV_64F
			    当ddepth输入值为-1时,目标图像和原图像深度保持一致。
InputArray kernel: 卷积核(或者是相关核),一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,可以先使用split()函数将图像通道事先分开。
Point anchor: 内核的基准点(anchor),其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。	
double delta: 在储存目标图像前可选的添加到像素的值,默认值为0	
int borderType: 像素向外逼近的方法,默认值是BORDER_DEFAULT,即对全部边界进行计算。

示例代码:

#include 
#include 
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
    Mat srcImage = imread("lena.jpg");
    //判断图像是否加载成功
    if(srcImage.data)
        cout << "图像加载成功!" << endl << endl;
    else
    {
        cout << "图像加载失败!" << endl << endl;
        return -1;
    }
    namedWindow("srcImage", WINDOW_AUTOSIZE);
    imshow("srcImage", srcImage);
    Mat kern = (Mat_<char>(3,3) << 0, -1 ,0,
                                   -1, 5, -1,
                                   0, -1, 0);
    Mat dstImage;
    filter2D(srcImage,dstImage,srcImage.depth(),kern);
    namedWindow("dstImage",WINDOW_AUTOSIZE);
    imshow("dstImage",dstImage);
    
    waitKey(0);
    return 0;
}

图像求和(混合blending)

线性混合 (linear blending) 是什么以及有什么用处.
如何使用 addWeighted 进行两幅图像求和

addWeighted( src1, alpha, src2, beta, 0.0, dst);
Warning 因为我们对 src1 和 src2 求 和 ,它们必须要有相同的尺寸(宽度和高度)和类型。
可以利用Mat构造函数实现矩阵的裁剪,同时也可以利用resize函数进行图像的重新缩放;
具体代码为:
Mat n_mat = Mat(src1, Range(0,256),Range(0,256));
Mat n_mat = Mat(src1,Rect(0,0,logo.cols,logo.rows));
resize(src,src1.size())

这里 gamma 对应于上面代码中被设为 0.0 的参数。gamma为加的常数。

你可能感兴趣的:(C++学习)