Mask operations on matrices 矩阵掩码操作
@prev_tutorial{tutorial_how_to_scan_images}
https://blog.csdn.net/weixin_42142612/article/details/88881229
@next_tutorial{tutorial_mat_operations}
Mask operations on matrices are quite simple. The idea is that we recalculate each pixels value in an image according to a mask matrix (also known as kernel). This mask holds values that will adjust how much influence neighboring pixels (and the current pixel) have on the new pixel value. From a mathematical point of view we make a weighted average, with our specified values.
矩阵上的掩码操作非常简单。其思想是根据掩码矩阵(也称为内核)重新计算图像中的每个像素值。这个掩码包含的值将调整相邻像素(和当前像素)对新像素值的影响程度。从数学的角度看,我们用指定的值做加权平均。
Our test case我们的测试用例
Let us consider the issue of an image contrast enhancement method. Basically we want to apply for every pixel of the image the following formula:
\f[I(i,j) = 5*I(i,j) - [ I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]\f]\f[\iff I(i,j)*M, \text{where }
M = \bordermatrix{ _i\backslash ^j & -1 & 0 & +1 \cr
-1 & 0 & -1 & 0 \cr
0 & -1 & 5 & -1 \cr
+1 & 0 & -1 & 0 \cr
}\f]
The first notation is by using a formula, while the second is a compacted version of the first by using a mask. You use the mask by putting the center of the mask matrix (in the upper case noted by the zero-zero index) on the pixel you want to calculate and sum up the pixel values multiplied with the overlapped matrix values. It’s the same thing, however in case of large matrices the latter notation is a lot easier to look over.
第一种表示法是使用公式,而第二种表示法是使用掩码对第一种表示法进行压缩的版本。使用掩码时,将掩码矩阵的中心(在大写的情况下由zero-zero索引表示)放在要计算的像素上,并将像素值与重叠矩阵值相乘并求和。这是一样的,但是对于大型矩阵,后一种表示法更容易理解。
Code代码
@add_toggle_cpp
You can download this source code from here
or look in the
OpenCV source code libraries sample directory at
samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp
.
@include samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp
@end_toggle
The Basic Method基本方法
Now let us see how we can make this happen by using the basic pixel access method or by using the filter2D() function.
现在让我们看看如何使用基本的像素访问方法或使用filter2D()函数来实现这一点。
Here’s a function that will do this:
@add_toggle_cpp
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp basic_method
At first we make sure that the input images data is in unsigned char format. For this we use the @ref cv::CV_Assert function that throws an error when the expression inside it is false.
首先,我们确保输入的图像数据是unsigned char格式的。为此,我们使用cv::CV_Assert函数,当其中的表达式为false时抛出错误.
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp 8_bit
@end_toggle
We create an output image with the same size and the same type as our input. As you can see in the @ref tutorial_how_to_scan_images_storing “storing” section, depending on the number of channels we may have one or more subcolumns.
我们创建一个与输入大小和类型相同的输出图像。正如您在tutorial_how_to_scan_images_storage“storage”部分中所看到的,根据通道的数量,我们可能有一个或多个子列.
@add_toggle_cpp
We will iterate through them via pointers so the total number of elements depends on this number.我们将通过指针遍历它们,因此元素的总数取决于这个数字.
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp create_channels
@end_toggle
@add_toggle_cpp
We’ll use the plain C [] operator to access pixels. Because we need to access multiple rows at the same time we’ll acquire the pointers for each of them (a previous, a current and a next line). We need another pointer to where we’re going to save the calculation. Then simply access the right items with the [] operator. For moving the output pointer ahead we simply increase this (with one
byte) after each operation:
我们将使用普通的C[]操作符来访问像素。因为我们需要同时访问多行,所以我们将为每一行获取指针(前一行、当前行和下一行)。我们需要另一个指针指向我们要保存计算的地方。然后只需使用[]操作符访问正确的项。为了将输出指针向前移动,我们只需在每次操作之后增加这个值(用一个字节):
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp basic_method_loop
On the borders of the image the upper notation results inexistent pixel ocations (like minus one minus one). In these points our formula is undefined. A simple solution is to not apply the kernel in these points and, for example, set the pixels on the borders to zeros:
在图像的边界上,上面的符号导致不存在像素定位(如-1 -1)。在这些点上,我们的公式没有定义。一个简单的解决方案是不在这些点上应用内核,例如,将边框上的像素设置为零:
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp borders
@end_toggle
We need to access multiple rows and columns which can be done by adding or subtracting 1 to the current center (i,j). Then we apply the sum and put the new value in the Result matrix.
我们需要访问多个行和列,这可以通过向当前中心(i,j)添加或减去1来完成。然后我们应用求和并将新值放入结果矩阵中。
The filter2D function filter2D函数
Applying such filters are so common in image processing that in OpenCV there exist a function that will take care of applying the mask (also called a kernel in some places). For this you first need to define an object that holds the mask:
应用这样的过滤器在图像处理中非常常见,以至于在OpenCV中存在一个函数来处理应用掩码(在某些地方也称为内核)。为此,您首先需要定义一个包含掩码的对象:
@add_toggle_cpp
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp kern
@end_toggle
Then call the filter2D() function specifying the input, the output image and the kernel to use:
然后调用filter2D()函数,指定要使用的输入、输出图像和内核:
@add_toggle_cpp
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp filter2D
@end_toggle
The function even has a fifth optional argument to specify the center of the kernel, a sixth for adding an optional value to the filtered pixels before storing them in K and a seventh one for determining what to do in the regions where the operation is undefined (borders).
该函数甚至有第五个可选参数来指定内核的中心,第六个参数用于在将筛选后的像素存储为K之前向其添加一个可选值,第七个参数用于确定在未定义操作的区域(边界)中执行什么操作。
This function is shorter, less verbose and, because there are some optimizations, it is usually faster than the hand-coded method. For example in my test while the second one took only 13 milliseconds the first took around 31 milliseconds. Quite some difference.
这个函数更短,更简洁,而且由于有一些优化,它通常比手工编码的方法更快。例如,在我的测试中,第二个只用了13毫秒,而第一个用了31毫秒。这个是非常大的区别。
For example:
@add_toggle_cpp
Check out an instance of running the program on our YouTube
channel .
@youtube{7PF1tAU9se4}
@end_toggle
学习mat_mask_operations.cpp
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
static void help(char* progName)
{
cout << endl
<< "This program shows how to filter images with mask: the write it yourself and the"
<< "filter2d way. " << endl
<< "Usage:" << endl
<< progName << " [image_path -- default ../data/lena.jpg] [G -- grayscale] " << endl << endl;
}
void Sharpen(const Mat& myImage,Mat& Result);
int main( int argc, char* argv[])
{
help(argv[0]);
const char* filename = argc >=2 ? argv[1] : "../data/lena.jpg";
Mat src, dst0, dst1;
if (argc >= 3 && !strcmp("G", argv[2]))
src = imread( filename, IMREAD_GRAYSCALE);
else
src = imread( filename, IMREAD_COLOR);
if (src.empty())
{
cerr << "Can't open image [" << filename << "]" << endl;
return -1;
}
namedWindow("Input", WINDOW_AUTOSIZE);
namedWindow("Output", WINDOW_AUTOSIZE);
imshow( "Input", src );
double t = (double)getTickCount();
Sharpen( src, dst0 );
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Hand written function time passed in seconds: " << t << endl;
imshow( "Output", dst0 );
//waitKey();
//![kern]
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
//![kern]
t = (double)getTickCount();
//![filter2D]
filter2D( src, dst1, src.depth(), kernel );
//![filter2D]
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Built-in filter2D time passed in seconds: " << t << endl;
imshow( "Output", dst1 );
waitKey();
return 0;
}
//! [basic_method]
void Sharpen(const Mat& myImage,Mat& Result)
{
//! [8_bit]
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
//! [8_bit]
//! [create_channels]
const int nChannels = myImage.channels();
Result.create(myImage.size(),myImage.type());
//! [create_channels]
//! [basic_method_loop]
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]);
}
}
//! [basic_method_loop]
//! [borders]
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));
//! [borders]
}
//! [basic_method]
扩展学习和理解,可以参考我的另一篇博客:
https://blog.csdn.net/weixin_42142612/article/details/80840544