6、OpenCV掩码矩阵运算Mask

一、学习目标

  • 了解什么是掩码矩阵运算
  • 学会2种方法实现掩码矩阵运算
  • 使用锐化图像的实例

二、掩码矩阵运算

矩阵的掩码操作非常简单。这个想法是我们根据掩码矩阵(也称为内核)重新计算图像中的每个像素值。此掩码保存的值将调整相邻像素(和当前像素)对新像素值的影响程度。从数学的观点来看,我们用我们指定的值得到一个加权平均数作为新像素值,可对比计算机视觉中的卷积运算。

测试案例
假如我们要实现图像得对比度增强。简单讲就是,我们可以对图像的每个像素应用下面的公式:
在这里插入图片描述
第一种表示法是使用公式,I 表示图像的像素矩阵,根据索引指定对应的行和列。而第二种表示法是使用掩码的形式。通过将掩码矩阵的中心(由0 - 0索引标记的大写字母)放在要计算的像素上,并将像素值乘以重叠的矩阵值并相加。掩码的中心值取值较大,相邻像素为-1,通过加权求和过后就扩大了像素值之间的差距,从而达到对比度增强的效果。

三、两种解决方案

1、循环遍历每个像素应用公式
结合上述公式的掩码操作,回想上次教程中我们对图像像素矩阵的遍历,我们很容易想到的方法是在循环中依次对每个像素应用上述公式:

void Sharpen(const Mat& myImage,Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  
    const int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());
    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]);
        }
    }
}

代码讲解:

  • 首先,调用CV_Assert()函数判断图像数据的类型是否为uchar类型,不满足则抛出异常。
  • Result对象的create方法会根据输入图像myImage的尺寸和数据类型初始化Result
  • 遍历像素的循环采用了C风格的指针访问形式。previous表示当前处理行的上一行像素地址,current表示当前行像素地址,next表示下一行像素地址,由此对应掩码,找到中心像素左(current [i-nChannels] )上(previous[i])右(current[i+nChannels])下(next[i])的像素值。注意访问左右元素的时候需要加减一个通道数,这个可以对照Mat矩阵的存储格式理解。
  • 对于根据掩码计算公式计算后得到的像素值,我们需要将其取值限定在有效的范围内(uchar类型对应的取值范围为[0,255]),对此,saturate_cast< uchar> 函数完美解决。
  • 在图像的边界上,上面的符号会导致不存在的像素位置(如(- 1,1))。在这些点上,公式没有定义。一个简单的解决方案是在这些点上不应用掩码运算,直接设置边界上的像素为零:
    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));

2、使用 filter2D 函数
在图像处理中,应用这样的过滤器非常常见,因此OpenCV中有一个函数负责应用掩码(在某些地方也称为内核)。为此,你首先需要定义一个保存掩码的对象:

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

然后调用filter2D()函数指定输入、输出图像和使用的掩码:

    filter2D( src, dst1, src.depth(), kernel );

filter2D 函数的函数原型为:

CV_EXPORTS_W void filter2D( InputArray src, OutputArray dst, int ddepth,
                            InputArray kernel, Point anchor = Point(-1,-1),
                            double delta = 0, int borderType = BORDER_DEFAULT );
  • 参数 src:指定输入图像
  • 参数 dst:指定输出图像,与输入图像的尺寸相同,数据类型相同
  • 参数 ddepth: 目标图像的深度,可以以输入图像为准
  • 参数 kernel:卷积核(或者说是相关核),一个单通道浮点数矩阵;如果你想将不同的内核应用到不同的通道,将图像使用split分离颜色平面并单独处理它们。
  • 参数 anchor:内核的锚点,指示过滤点的相对内核的位置。锚点应该位于内核内;默认值(-1,-1)表示锚在内核中心。
  • 参数 delta: 可选值,和进行掩码运算后的像素相加,然后存储到dst
  • 参数 borderType :边界像素类型

这个函数更短,更简洁,而且OpenCV内部做了一些优化,它通常比手工编码的方法更快。

四、完整代码示例

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

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 lena.jpg] [G -- grayscale] " << endl << endl;
}
void sharpenImage(const Mat& src, Mat& dst);

int main(int argc, char** argv)
{
	help(argv[0]);
	const char* fileName = argc >= 2 ? argv[1] : "lena.jpg";
	Mat src, dst1, dst2;
	if (argc >= 3 && strcmp("G", argv[2]))
		src = imread(samples::findFile(fileName), IMREAD_GRAYSCALE);
	else
		src = imread(samples::findFile(fileName), IMREAD_COLOR);
	if (src.empty())
	{
		cerr << "failed to load image [" << fileName << " ]" << endl;
		system("pause");
		return EXIT_FAILURE;
	}

	namedWindow("Original", WINDOW_AUTOSIZE);
	namedWindow("C Point Mask", WINDOW_AUTOSIZE);
	namedWindow("filter2D", WINDOW_AUTOSIZE);
	imshow("Original", src);
	double t1 = (double)getTickCount();
	sharpenImage(src, dst1);
	double time1 = ((double)getTickCount() - t1) / getTickFrequency();
	cout << "The cost of method C Pointer Mask is: " << time1 << "(ms)" << endl;

	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0,
									 -1, 5, -1,
		                              0, -1, 0);
	double t2 = (double)getTickCount();
	filter2D(src, dst2, src.depth(), kernel);
	double time2 = ((double)getTickCount() - t2) / getTickFrequency();
	cout << "The cost of method filter2D is: " << time2 << "(ms)" << endl;

	imshow("C Point Mask", dst1);
	imshow("filter2D", dst2);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

void sharpenImage(const Mat& src, Mat& dst)
{
	CV_Assert(src.depth() == CV_8U);
	const int channels = src.channels();
	dst.create(src.size(), src.type());
	for (int row = 1; row < src.rows - 1; ++row)
	{
		const uchar* previous = src.ptr<uchar>(row - 1);
		const uchar* current = src.ptr<uchar>(row);
		const uchar* next = src.ptr<uchar>(row + 1);

		uchar* output = dst.ptr<uchar>(row);
		for (int col = channels; col < channels * src.cols; ++col)
		{
			*output++ = saturate_cast<uchar>(5 * current[col] - (current[col - channels] + previous[col] + current[col + channels] + next[col]));
		}
	}
	dst.row(0).setTo(Scalar(0));
	dst.row(dst.rows - 1).setTo(Scalar(0));
	dst.col(0).setTo(Scalar(0));
	dst.col(dst.cols - 1).setTo(Scalar(0));
}

在这里插入图片描述

五、致谢

1、感谢OpenCV官方给出的学习案例,感谢大家的支持
2、感兴趣的小伙伴一起学习讨论。入群飞机票

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值