精通人脸识别05:OpenCV--矩阵的掩膜操作
1.什么是掩膜
掩膜其实就是一个矩阵,然后根据这个矩阵重新计算图片中像素的值。
矩阵的掩膜操作——根据掩膜重新计算每个像素的像素值,掩膜mask 也称做kernel
首先我们从物理的角度来看看mask到底是什么过程。
在半导体制造中,许多芯片工艺步骤采用光刻技术,用于这些步骤的图形“底片”称为掩膜(也称作“掩模”),其作用是:在硅片上选定的区域中对一个不透明的图形模板遮盖,继而下面的腐蚀或扩散将只影响选定的区域以外的区域。
图像掩膜与其类似,用选定的图像、图形或物体,对处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程。
2.掩膜的用法
2.1 提取感兴趣区:用预先制作的感兴趣区掩膜与待处理图像相乘,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0;
2.2 屏蔽作用:用掩膜对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计;
2.3 结构特征提取:用相似性变量或图像匹配方法检测和提取图像中与掩膜相似的结构特征;
2.4 特殊形状图像的制作。
3.掩膜运算的一个小实例
以图和掩膜的与运算为例:
原图中的每个像素和掩膜中的每个对应像素进行与运算。比如1 & 1 = 1;1 & 0 = 0;
比如一个3 * 3的图像与3 * 3的掩膜进行运算,得到的结果图像就是:
1.图像中,各种位运算,比如与、或、非运算与普通的位运算类似。
2.如果用一句话总结,掩膜就是两幅图像之间进行的各种位运算操作。
#include <opencv2\opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("D:/demo01.jpg");
if (!src.data)
{
cout << "could not load image..." << endl;
return -1;
}
namedWindow("source image", CV_WINDOW_AUTOSIZE);
imshow("source image", src);
//1).empty() 判断文件读取是否正确
//2).rows 获取图像行数(高度)
//3).cols 获取图像列数(长度)
//4).channels() 获取图像通道数
//5).depth() 获取图像位深度
//【1】记录程序开始点timeStart
double timeStart = (double)getTickCount();//计算时间语句
//进行矩阵的掩膜操作
int cols = (src.cols - 1)*src.channels();//837 //获取图像的列数,一定不要忘记图像的通道数
int offsetx = src.channels();//图像的通道数 3
int rows = src.rows;//220
dst = Mat::zeros(src.size(),src.type());//返回指定的大小和类型的数组 创建一个跟src一样大小 类型的图像矩阵
for (int row = 1; row < (rows - 1); row++)
{
//Mat.ptr<uchar>(int i=0) 获取像素矩阵的指针,索引i表示第几行,从0开始计行数。
//获得当前行指针const uchar* current= myImage.ptr<uchar>(row );
//获取当前像素点P(row, col)的像素值 p(row, col) =current[col]
//Mat.ptr<uchar>(row):获取第row行的图像像素指针。图像的行数从0开始计数
//获取点P(row, col)的像素值:P(row.col) = Mat.ptr<uchar>(row)[col]
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 = offsetx; col < cols; col++)
{
//像素范围处理saturate_cast<uchar>
output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]));
//current[col-offsetx]是当前的像素点的左边那个像素点的位置,因为一个像素点有三个通道
//current[col+offsetx]是当前的像素点的右边那个像素点的位置,因为一个像素点有三个通道
//previous[col]表示当前像素点对应的上一行的那个像素点
//next[col]表示当前像素点对应的下一行的那个像素点
}
}
//OpenCV提高了函数filter2D来实现掩膜操作:
//Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//定义掩膜
//调用filter2D
//filter2D(src, dst, src.depth(), kernel);
double timeconsume = ((double)getTickCount() - timeStart) / getTickFrequency();
cout << "运行上面程序共耗时: " << timeconsume << endl;
//输出 掩膜操作后的图像
namedWindow("contrast image",CV_WINDOW_AUTOSIZE);
imshow("contrast image",dst);
waitKey(0);
return 0;
}
掩膜操作作用——实现图像对比度调整
红色是中心像素I(x,y),从上到下,从左到右对每个像素做同样的处理操作,掩膜操作公式如下:
int main() {
Mat src, dst;
src = imread("C:\\Users\\liuhuimin\\Desktop\\pp.jpg");//imread的图像是RGB
if (!src.data) {
printf("cloud not load image");
return -1;
}
imshow("原图",src);
dst = Mat::zeros(src.size(), src.type());//初始化输出图像矩阵
int cols = (src.cols-1)*src.channels();//获取图像的列数,注意实际图像是三通道
int rows = src.rows; //获取图像的行数
int offsetx = src.channels();
for (int i = 1; i < (rows - 1); i++) {
uchar* previous = src.ptr<uchar>(i - 1); //上
uchar* current = src.ptr<uchar>(i); //获取当前指针
uchar* next = src.ptr<uchar>(i + 1);//下
uchar* output = dst.ptr<uchar>(i);
for (int j = offsetx; j < cols; j++) {
output[j] = 5 * current[j] - (previous[j] + next[j] + current[j - offsetx] + current[j + offsetx]);
}
}
imshow("掩膜后", dst);
waitKey(0);
return 0;
}
出现了一些不好的小斑点。这是因为这项像素点的值的范围不在0~255之间了。
解决办法如下:output[j] = saturate_cast<uchar>(5 * current[j] - (previous[j] + next[j] + current[j - offsetx] + current[j + offsetx]));
调用API来实现:
int main() {
Mat src, dst;
src = imread("C:\\Users\\liuhuimin\\Desktop\\pp.jpg");//imread的图像是RGB
if (!src.data) {
printf("cloud not load image");
return -1;
}
imshow("原图",src);
dst = Mat::zeros(src.size(), src.type());//初始化输出图像矩阵
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(src, dst, src.depth(), kernel);
imshow("掩膜后", dst);
waitKey(0);
return 0;
}