掩码操作
根据掩码矩阵(也称作核)重新计算图像中每个像素的值。掩码矩阵中的值表示近邻像素值(包括该像素自身的值)对新像素值有多大影响。从数学观点看,我们用自己设置的权值,对像素邻域内的值做了个加权平均。
图像的掩码操作是指通过掩码核算子重新计算图像中各个像素的值,掩码核算子刻画领域像素点对新像素值得影响程度,同时根据掩码算子中权重因子对像素点进行加权平均。图像掩码操作常用于图像平滑、边缘检测、特征分析等区域。
具体思路为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
相当于是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
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);
}