OpenCV_Tutorials——CORE MODULE.THE CORE FUNCTIONALITY—— Mask operations on matrices

在矩阵中的掩码操作

        在矩阵中的掩码操作非常简单。我们根据掩码矩阵(也就是所谓的核)来重新计算图像中的每一个像素值。掩码的数值将会调整对相邻的像素(以及当前像素)转变为新的像素值的影响。从数学的角度上来说,使用我们指定的数值类似于加权平均值。

测试案例

       我们来考虑一下一个图像的对比度增强问题。我们想要将图像中的每一个像素都应用于下面的的方程当中:

       I(i,j)=5*I(i,j)-[I(i-1,j)+I(i+1,j]+I(i,j-1)+I(i,j+1)]<=====>I(i,j)*M,这里M=

 

       这里第一个式子是要使用的方程,第二个是使用掩码后的第一个方程的压缩版本。你在使用掩码的时候,通过把掩码矩阵的中心(在上面的例子中就是坐标的(0,0)位置)放置在你想要计算的位置,然后将与之重叠的掩码值相乘,最后将所有的结果进行求和就可以得出最后的结果。在后面的大的矩阵中,你会更加容易的看到这些都是一样的。

       现在,让我们看一下我们如何通过使用基本的像素读取技术或者使用filter2D函数来实现。

基本的方法

        例子:

 

void Sharpen(const Mat & myImage ,Mat &Result)
{
CV_Assert(myImage.depth()==CV_8U);
Result.create(myImage.size(),myImage.type());
const int nChannels=myImage.channels();
for(int j=1;j<myImage.rows-1;++j)
{
const uchar*previous=myImage.ptr<uchar>(j-1);
const uchar*current=myImage.ptr<uchar>(j);
const uchar*next=myImage.ptr<uchar>(j+1);
uchar*output=Result.ptr<uchar>(j);

for(int i=nChannels;i<nChannels*(myImage.cols-1);++i)
{
*output++=saturate_cast<uchar>(5*current[i]-current[i-nChannels]-current[i+nChannels]-previous[i]-next[i]);
}
}
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));
}

}

 


 

As a computer vision library, OpenCV deals a lot with image pixels that are often encoded in a compact, 8- or 16-bit per channel, form and thus have a limited value range. Furthermore, certain operations on images, like color space conversions, brightness/contrast adjustments, sharpening, complex interpolation (bi-cubic, Lanczos) can produce values out of the available range. If you just store the lowest 8 (16) bits of the result, this results in visual artifacts and may affect a further image analysis. To solve this problem, the so-called saturation arithmetics is used. For example, to store r, the result of an operation, to an 8-bit image, you find the nearest value within the 0..255 range:

I(x,y)= \min ( \max (\textrm{round}(r), 0), 255)

Similar rules are applied to 8-bit signed, 16-bit signed and unsigned types. This semantics is used everywhere in the library. In C++ code, it is done using the saturate_cast<> functions that resemble standard C++ cast operations. See below the implementation of the formula provided above:

I.at<uchar>(y, x) = saturate_cast<uchar>(r);

where cv::uchar is an OpenCV 8-bit unsigned integer type. In the optimized SIMD code, such SSE2 instructions as paddusb, packuswb, and so on are used. They help achieve exactly the same behavior as in C++ code.

Note

Saturation is not applied when the result is 32-bit integer.


 

      首先,我们确保输入的图像数据是unsigned char 格式的。我们使用CV_Assert函数,当表达式内部结果是false的时候,抛出错误。

      

CV_Assert(myImage.detpth()==CV_8U);

      我们创建了一个和输入相同大小相同类型的输出图像。就像我们在“How The image matrix stored in the memory?”这一章节中看到的一样,根据通道数量的不同,可能会有一个或者更多的子列。我们使用指针(pointer)迭代遍历他们,因此元素总数在这里是这样确定的。

Result.create(myImage.size(),myImage.type());
const int nChannels=myImage.channels();

     我们在这里使用C语言中[]操作符来读取像素。这里我们需要同时读取多个行,因此我们为每一个需要的行设置指针(一个是previous,一个current以及一个next line)。我们需要另一个指针来存储计算结果。用简单的[]操作符来读取想要读取的元素。我们通过简单的在每一个操作完成之后递增(增加一个byte)来向前移动输出指针。

forint j=1;j<myImage.rows-1;++j)
{
const uchar* previous=myImage.ptr<uchar>(j-1);
const uchar* current=myImage.ptr<uchar>(j);
const uchar* next=myImage.pr<uchar>(j+1);
uchar* output=Result.ptr<uchar>(j);
for(int i=nChannels;i<nChannels*(myImage.cols-1);++i)
{
*output++=saturate_cast<uchar>(5*current[i]-current[i-nChannels]-current[i+nChannels]-previous[i]-next[i];
}
}

    在上面所示的程序会导致图像的边界不存在像素问题(类似于-1- -1)。我们的方程在这里并没有将这种情况作出定义。一个简单的解决方案是不在这些区域使用核,例如设置这些边界像素为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));

filter2D函数

        在图像处理使用过滤器中是稀疏平常的事情,在OpenCV中存在一个关注使用掩码(在某些地方同样被称作核)的函数。你首先应当做的事情是定义一个Mat对象来存放这个掩码:

Mat kern=(Mat_<char>(3,3)<<0,-1,0,
                          -1,5,-1,
                           0,-1,0);

        在调用filter2D函数的时候指定输入,输出图像以及所使用的内核:

 

filter2D(I,K,I.depth(),kern);

 

         这个函数甚至拥有第五个可原则的参数来指定核的中心,以及第六个参数来决定在未定义(边界)行为的区域处进行什么操作。使用这个函数拥有一些有点:更短,更精简而且因为在实现的时候使用了一些优化技术,因此在使用时要比手动编码的方法更快一些。例如在我的测试中,第二个方法使用了13微秒,第一个方法使用了31微秒。确实有一些差距。

例如:

 

 

      你可以从这里下载源码或者从OpenCV源代码库的事例文件夹的samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp位置中查看源码。

 

 

posted @ 2015-05-15 10:47  你猜你猜啊  阅读(224)  评论(0编辑  收藏  举报