矩阵的掩码操作

在矩阵上进行掩码操作很简单,主要的思想就是根据一个掩码矩阵(也称为核)去重新计算一幅图像中的每一个像素值。这个掩码矩阵里面的值将决定临近的像素对新像素值的影响多大。从数学的观点上来看,我们利用我们给定的值做了一个加权平均。

我们的测试案例:

让我们来考虑这个问题:一幅图像的对比增强方法。基本上我们想要对图像的每个像素应用下面的公式:


第一种方式是使用公式,然而第二种是使用一种比前者结构更紧凑的方法:通过使用掩码。你可以通过把掩码矩阵的中心放到你想要计算的像素上并把像素和覆盖在上面的掩码相加来使用掩码。然而,对于那些比较大的矩阵,第二种方式会更加简单。

现在让我们来看看我们怎样通过使用基本的像素处理方法或使用filter2D函数来做上述操作。

首先来看一下基本的方法:

void Sharpen(const Mat& myImage, Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images

    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));
}

首先我们必须确保我们的输入图像数据必须是uchar类型的。为了达到这个目的我们使用CV_Assert函数:当我们的输入图像不是8uchar类型,则程序报错。

CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images

我们创建一个和输入图像同样大小和同样类型的输出图像。这个可以参照上一节的内容根据通道数我们可能有一个或多个子列。我们将通过指针来遍历输入图像。

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

我们将使用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));               // The top row
Result.row(Result.rows - 1).setTo(Scalar(0)); // The bottom row
Result.col(0).setTo(Scalar(0));               // The left column
Result.col(Result.cols - 1).setTo(Scalar(0)); // The right column

第二种方法是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);

这个函数有第五个可选的参数来确定核的中心,以及第六个参数来确定在图像的边缘进行什么操作(例如直接将图像边缘的像素值设置为0)。使用这个函数的优点是它比较简洁,因为在这个函数的内部实现中OpenCV已经做了相应的优化,所以这个函数通常都比第一种自己写代码的方法要快很多。

 最后的完成测试代码如下:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

static void help()
{
	cout << endl
		<< "This program shows how to filter images with mask: the write it yourself and the"
		<< "filter2d way. " << endl
		<< "Usage:" << endl
		 << " [image_name -- default lena.jpg] [G -- grayscale] " << endl << endl;
}


void Sharpen(const Mat& myImage, Mat& Result);

int main(int argc, char* argv[])
{
	help();
	const char* filename = "F:/Photo/OpenCV_Photo/fruits.jpg";

	Mat I,J, K;

	I = imread(filename, CV_LOAD_IMAGE_COLOR);

	namedWindow("Input", WINDOW_AUTOSIZE);
	namedWindow("Output", WINDOW_AUTOSIZE);
	namedWindow("Output1", WINDOW_AUTOSIZE);

	imshow("Input", I);
	double t = (double)getTickCount();

	Sharpen(I, J);

	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "Hand written function times passed in seconds: " << t << endl;

	imshow("Output", J);
	//waitKey(0);

	Mat kern = (Mat_<char>(3, 3) << 0, -1, 0,
		-1, 5, -1,
		0, -1, 0);
	t = (double)getTickCount();
	filter2D(I, K, I.depth(), kern);
	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "Built-in filter2D time passed in seconds:      " << t << endl;

	imshow("Output1", K);

	waitKey(0);
	return 0;
}
void Sharpen(const Mat& myImage, Mat& Result)
{
	CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images

	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]);
		}//saturate_cast<uchar>溢出保护,对于uchar类型,大于0的像素值设置为0,大于255的像素设置为255
	}
	//将图像边缘的像素值都设置为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));
}

效果图如下:








  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值