参考:
1、https://docs.opencv.org/3.2.0/
2、https://github.com/opencv/opencv/
矩阵上的掩码操作非常简单。 我们的想法是,我们根据掩码矩阵(也称为内核)重新计算图像中的每个像素值。 该掩码保存的值将调整相邻像素(和当前像素)对新像素值的影响程度。 从数学的角度来看,我们用我们指定的数值进行加权平均。
让我们考虑一个图像对比度增强方法的问题。 基本上我们要应用下面的公式于图像的每个像素:
第一种表示法是使用公式,而第二种表示法是使用掩码的第一种表示法。 通过将掩码矩阵的中心(在由零 - 零指数标记的大写字母中)放在要计算的像素上并使用与重叠的矩阵值相乘的像素值相加来使用掩码。 这是一样的事情,但是在大矩阵的情况下,后面的符号更容易查找。
现在让我们看看如何使用基本像素访问方法或使用cv :: filter2D函数来实现这一点。
void Sharpen(const Mat& myImage,Mat& Result)
{
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images 判断图像类型是否是 unsigned char 即uint8
const int nChannels = myImage.channels();//通道数
Result.create(myImage.size(),myImage.type());//按myImage的大小(h x w x c),数据类型(unsigned char)重新创建Mat对象
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= 1*nChannels;i < nChannels*(myImage.cols-1); ++i)
{
*output++ = saturate_cast<uchar>(5*current[i]
-current[i-1*nChannels] - current[i+1*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));
}
//注:opencv中图像像素值被存储成[rows,cols*nchannels]这种形式
对应的python代码:
def sharpen(my_image):
my_image = cv2.cvtColor(my_image, cv2.CV_8U)
height, width, n_channels = my_image.shape
result = np.zeros(my_image.shape, my_image.dtype)
for j in range (1, height-1):
for i in range (1, width-1):
for k in range (0, n_channels):
sum = 5 * my_image[j, i, k] - my_image[j + 1, i, k] - my_image[j - 1, i, k]\
- my_image[j, i + 1, k] - my_image[j, i - 1, k];
if sum > 255:
sum = 255
if sum < 0:
sum = 0
result[j, i, k] = sum
return result
首先我们确保输入的图像数据是unsigned char格式。为此,我们使用cv :: CV_Assert函数,当它里面的表达式为false时会抛出一个错误。
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
我们创建一个与我们的输入具有相同大小和相同类型的输出图像。 正如你可以在存储部分看到的,取决于通道的数量,我们可能有一个或多个子列。
我们将通过指针遍历它们,所以元素的总数取决于这个数字。
const int nChannels = myImage.channels();
Result.create(myImage.size(),myImage.type());
我们将使用普通的C []运算符来访问像素。 因为我们需要同时访问多行,我们将获取每个行的指针(前一行,当前行和下一行)。 我们需要另一个指向我们要保存计算的地方。 然后只需使用[]运算符访问正确的项目。 为了提前移动输出指针,我们只需在每个操作之后增加一个字节即可:
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]);
}
}
在图像的边界上面的符号导致不存在的像素位置(如减一 - 减一)。 在这些方面我们的公式是不确定的。 一个简单的解决方案是不在这些点上应用内核,例如,将边界上的像素设置为零:
//设置边界都为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));
应用这样的过滤器在图像处理中非常普遍,以至于在OpenCV中存在一个将处理掩码(在某些地方也称为内核)的功能。 为此,您首先需要定义一个包含掩码的对象:
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
然后调用指定输入,输出图像和内核的cv :: filter2D函数来使用:
filter2D( src, dst1, src.depth(), kernel );
对应的python:
kernel = np.array([ [0,-1,0],
[-1,5,-1],
[0,-1,0] ],np.float32) # kernel should be floating point type
K = cv2.filter2D(I, -1, kernel) # ddepth = -1, means destination image has depth same as input image.
该函数甚至有第五个可选参数来指定内核的中心,第六个用于在将过滤像素存储在K中之前将可选值添加到过滤像素中,第七个用于确定在操作未定义的区域中做什么 (边界)。
这个函数更短,更简洁,因为有一些优化,通常比手工编码的方法更快。 例如在我的测试中,第二个只花了13毫秒,第一个花费了大约31毫秒。 有一些差异。
你可以从这里下载这个源代码,或者查看OpenCV源代码库的示例目录
samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp.
查看在我们的YouTube频道上运行该程序的实例。