OpenCV学习笔记09_掩码操作(卷积操作)

24 篇文章 0 订阅
19 篇文章 0 订阅

掩码操作
根据掩码矩阵(也称作核)重新计算图像中每个像素的值。掩码矩阵中的值表示近邻像素值(包括该像素自身的值)对新像素值有多大影响。从数学观点看,我们用自己设置的权值,对像素邻域内的值做了个加权平均。

图像的掩码操作是指通过掩码核算子重新计算图像中各个像素的值,掩码核算子刻画领域像素点对新像素值得影响程度,同时根据掩码算子中权重因子对像素点进行加权平均。图像掩码操作常用于图像平滑、边缘检测、特征分析等区域。
具体思路为8邻域的像素点与本身像素点作加权运算(kernel size:3x3)

实现方式(三通道图像):
1、at方法实现:
需要对三通道的数据进行单独处理,每个通道进行一次运算:
例子:

dst.at<cv::Vec3f>(i, j)[0]= cv::saturate_cast<uchar>
(-src.at<cv::Vec3b>(i - 1, j)[0]
- src.at<cv::Vec3b>(i + 1, j)[0]
- src.at<cv::Vec3b>(i, j-1)[0] 
- src.at<cv::Vec3b>(i, j+1)[0] 
+ 5*src.at<cv::Vec3b>(i, j )[0]);

使用at的泛型分别处理3个通道,Vec3f可以看作是vector<float, 3>,
相当于是3个float类型的向量。

saturate_cast 函数的作用是防止值溢出

2、模板类Mat_:
模板类型是针对数据取出繁杂问题实现简化功能:
例子:

//直接声明一个模板Mat_类
cv::Mat_<cv::Vec3f> src_ = src.clone();
//声明另一个模板类进行接收数据
dst_Mat_(i, j)[0] = cv::saturate_cast<float>
(5 * src_(i, j)[0] 
- src_(i - 1, j)[0] 
- src_(i + 1, j)[0] 
- src_(i, j - 1)[0] 
- src_(i, j + 1)[0]);

优点:不用每次使用at的泛型取值,简化代码;

3、Ptr指针实现:
首先我们知道在C++中一行中的数据是连续存储的,所以一行是channels*cols个数据;
所以我们只需要获取上一行,当前行与下一行的指针就能对当前行的数组进行操作(指针指向的是第一个数组元素,也叫表头);
然后因为模板是横向卷积,所以针对横向数据进行操作就行;
例子:

//(i从1开始循环)
		//上一行
		uchar* previous = src.ptr<uchar>(i - 1);
		//当前行
		uchar* current = src.ptr<uchar>(i);
		//下一行
		uchar* next = src.ptr<uchar>(i + 1);
//接收数据
	uchar* out = dst_1.ptr<uchar>(i);
//(j从3开始循环,到channels*(cols-1)结束,因为这里例子通道数为3,所以前一列为0,1,2;当前列从三开始)
out[j] = cv::saturate_cast<uchar>
//上一行当前列的每个通道
(- 1 * previous[j] 
//当前像素点的列方向下一个像素点每个通道
- 1 * current[j+3]
//当前像素点的每个通道 
+ 5 * current[j]
//当前像素点的列方向上一个一个像素点每个通道 
- 1 * current[j-3]
//下一行当前列的每个通道 
- 1 * next[j]);

4、Filter2D()函数实现:

OpenCV中提供了卷积函数:

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

参数解释:
1、InputArray src: 输入图像

2、OutputArray dst: 输出图像,和输入图像具有相同的尺寸和通道数量

3、int ddepth: 目标图像深度,如果没写将生成与原图像深度相同的图像。

支持深度如下(-1为原图像深度):

src.depth() = CV_8U, 			ddepth = -1/CV_16S/CV_32F/CV_64F
src.depth() = CV_16U/CV_16S, 	ddepth = -1/CV_32F/CV_64F
src.depth() = CV_32F, 			ddepth = -1/CV_32F/CV_64F
src.depth() = CV_64F, 			ddepth = -1/CV_64F

4、InputArray kernel: 卷积核(或者是相关核),一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,可以先使用split()函数将图像通道事先分开。

5、Point anchor: 内核的基准点(anchor),其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。

6、double delta: 在储存目标图像前可选的添加到像素的值,默认值为0

7、int borderType: 像素向外逼近的方法,默认值是BORDER_DEFAULT,即对全部边界进行计算。

卷积核kernel:

cv::Mat  kernel = (cv::Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);

例子:

cv::Mat src_filter;
cv::Mat kernel = (cv::Mat_<float> (3, 3)<< 0, -1, 0, -1, 5, -1, 0, -1, 0);
cv::filter2D(src, src_filter,-1, kernel);

代码:

#include <opencv.hpp>

void main()
{
	cv::Mat src = cv::imread("C:/Users/Administrator/Desktop/test.jpg");

	cv::Mat dst = src.clone()/*(src.rows,src.cols,CV_32FC3)*/;
// 	内核为 
// 		0, 1, 0, 
// 		1, -4, 1, 
// 		0, 1,  0


	

//三种方法进行卷积运算
	//1、at取值Vec3b的类型分别对每个通道处理数据(注意类型)

	dst.convertTo(dst, CV_32F);
	for (int i = 1 ;i < src.rows -1; i++)
	{
		for (int j = 1; j < src.cols -1; j++)
		{
			//需要加上saturate_cast防止数据溢出
			dst.at<cv::Vec3f>(i, j)[0]
				= cv::saturate_cast<uchar> (-src.at<cv::Vec3b>(i - 1, j)[0]- src.at<cv::Vec3b>(i + 1, j)[0]
				- src.at<cv::Vec3b>(i, j-1)[0] - src.at<cv::Vec3b>(i, j+1)[0] +5*src.at<cv::Vec3b>(i, j )[0]);
			dst.at<cv::Vec3f>(i, j)[1]
				= cv::saturate_cast<uchar> (-src.at<cv::Vec3b>(i - 1, j)[1] - src.at<cv::Vec3b>(i + 1, j)[1]
				- src.at<cv::Vec3b>(i, j - 1)[1] - src.at<cv::Vec3b>(i, j + 1)[1] + 5*src.at<cv::Vec3b>(i, j)[1]);
			dst.at<cv::Vec3f>(i, j)[2]
				= cv::saturate_cast<uchar> (-src.at<cv::Vec3b>(i - 1, j)[2] - src.at<cv::Vec3b>(i + 1, j)[2]
				- src.at<cv::Vec3b>(i, j - 1)[2] - src.at<cv::Vec3b>(i, j + 1)[2] + 5*src.at<cv::Vec3b>(i, j)[2]);
		}

	}

	cv::Mat out;
	normalize(dst, out, 0.0, 255.0, cv::NORM_MINMAX,CV_8UC3);


	//2、使用指针操作数据进行卷积
	cv::Mat dst_1 = src.clone();
	//先处理行
	for (int i = 1; i < src.rows - 1; i++)
	{
		//指向uchar类型的指针,
		//上一行
		uchar* previous = src.ptr<uchar>(i - 1);
		//当前行
		uchar* current = src.ptr<uchar>(i);
		//下一行
		uchar* next = src.ptr<uchar>(i + 1);
		uchar* out = dst_1.ptr<uchar>(i);
		//每行有cols列,每列分为三列(三个通道)
		for (int j = src.channels() ; j < src.channels()*(src.cols - 1); j++)
		{
			//每一行的数据地址是连续的行有通道数乘以列数个数据
			out[j] = cv::saturate_cast<uchar>(- 1 * previous[j] - 1 * current[j+3] + 5*current[j] - 1*current[j-3] - 1 * next[j]);



		}
	}

// 	dst_1.cols;
// 	dst_1.rows;
	//处理边缘
// 	dst_1.row(0).setTo(cv::Scalar(0)); 
// 	dst_1.row(src.cols - 1).setTo(cv::Scalar( 0));
// 	dst_1.col(0).setTo(cv::Scalar( 0));
// 	dst_1.col(src.rows - 1).setTo(cv::Scalar(0));

	//3、使用模板类进行卷积
	//声明一个模板Mat_类
	cv::Mat_<cv::Vec3f> src_ = src.clone();
	src_.convertTo(src_, CV_32F, 1.0 / 255);
	cv::Mat_<cv::Vec3f> dst_Mat_ = src_.clone();

	for (int i = 1;i<src.rows-1;i++)
	{
		for (int j =1;j<src.cols - 1;j++)
		{
			dst_Mat_(i, j)[0] = cv::saturate_cast<float>(5 * src_(i, j)[0] - src_(i - 1, j)[0] - src_(i + 1, j)[0] - src_(i, j - 1)[0] - src_(i, j + 1)[0]);
			dst_Mat_(i, j)[1] = cv::saturate_cast<float>(5 * src_(i, j)[1] - src_(i - 1, j)[1] - src_(i + 1, j)[1] - src_(i, j - 1)[1] - src_(i, j + 1)[1]);
			dst_Mat_(i, j)[2] = cv::saturate_cast<float>(5 * src_(i, j)[2] - src_(i - 1, j)[2] - src_(i + 1, j)[2] - src_(i, j - 1)[2] - src_(i, j + 1)[2]);
		}

	}

	//4、filte2D函数
	cv::Mat src_filter;
	cv::Mat kernel = (cv::Mat_<float> (3, 3)<< 0, -1, 0, -1, 5, -1, 0, -1, 0);
	cv::filter2D(src, src_filter,-1, kernel);

/*	normalize(dst_Mat_, dst_Mat_, 0.0, 1.0, cv::NORM_MINMAX, CV_32F);*/
//问题1:dst需要初始化
//问题2:row(范围),row使用setto函数修改整行值
	cv::imshow("src_filter", src_filter);
	cv::imshow("dst_Mat_", dst_Mat_);
	cv::imshow("src", src);
	cv::imshow("filter_mask_1", dst_1);
	cv::imshow("filter_mask", out);
	cv::waitKey(0);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiao张的da世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值