我们将探索以下问题的答案:
说到性能,经典的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 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( 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;
}
线性混合 (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为加的常数。