掩膜(mask)

摘要:
例如,1&1=1;1&0=0; 例如,用3*3掩模操作3*3图像,得到的图像是实现图像对比度调整矩阵的掩模操作非常简单。每个像素的像素值根据掩码重新计算,掩码也称为内核。

1.掩膜(mask)的定义

用选定的图像,图形或物体,对处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程。用于覆盖的特定图像或物体称为掩模或模板。光学图像处理中,掩模可以足胶片,滤光片等。

掩模是由0和1组成的一个二进制图像。当在某一功能中应用掩模时,1值区域被处理,被屏蔽的0值区域不被包括在计算中。通过指定的数据值,数据范围,有限或无限值,感兴趣区和注释文件来定义图像掩模,也可以应用上述选项的任意组合作为输入来建立掩模。

掩膜是一种图像滤镜的模板,实用掩膜经常处理的是遥感图像。当提取道路或者河流,或者房屋时,通过一个N * N的矩阵来对图像进行像素过滤,然后将我们需要的地物或者标志突出显示出来。这个矩阵就是一种掩膜。

在OpenCV的中,掩模操作是相对简单的。大致的意思是,通过一个掩模矩阵,重新计算图像中的每一个像素值。掩模矩阵控制了旧图像当前位置以及周围位置像素对新图像当前位置像素值的影响力度。用数学术语讲,即我们自定义一个权重表。

2.掩膜的用法

2.1 提取感兴趣区:用预先制作的感兴趣区掩膜与待处理图像相乘,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0; 
2.2 屏蔽作用:用掩膜对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计; 
2.3 结构特征提取:用相似性变量或图像匹配方法检测和提取图像中与掩膜相似的结构特征; 
2.4 特殊形状图像的制作。

3.掩膜运算的一个小实例

以图和掩膜的与运算为例: 
原图中的每个像素和掩膜中的每个对应像素进行与运算。比如1 & 1 = 1;1 & 0 = 0; 
比如一个3 * 3的图像与3 * 3的掩膜进行运算,得到的结果图像就是: 

掩膜(mask)第1张

掩膜操作实现图像对比度调整

矩阵的掩膜操作十分简单,根据掩膜来重新计算每个像素的像素值,掩膜(mask)也被称为内核。
通过掩膜操作实现图像对比度提高。
I(i,j) = 5*I(i,j) - [I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]

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

红色是中心像素,从上到下,从左到右对每个像素做同样的处理操作,得到最终结果就是对比度提高之后的输出图像垫对象。

实例代码:

#include <opencv2opencv.hpp>
#include <iostream>

#include <math.h>

using namespace std;
using namespace cv;


int main()
{
	Mat src, dst;
	src = imread("E:\VS2015Opencv\vs2015\project\picture\cat.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++)
		{
			//current[col-offsetx]是当前的像素点的左边那个像素点的位置,因为一个像素点有三个通道
			//current[col+offsetx]是当前的像素点的右边那个像素点的位置,因为一个像素点有三个通道
			//previous[col]表示当前像素点对应的上一行的那个像素点
			//next[col]表示当前像素点对应的下一行的那个像素点
			output[col] = 5 * current[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;
}

  

掩膜(mask)第2张

我们可以看见掩膜操作后的图像对比度明显提高了,但是美中不足的是出现了一些不好的小斑点。这是因为这项像素点的值的范围不在0~255之间了。
解决方法:
使用函数saturate_cast(像素值)
这个函数的作用就是确保RGB的值在0~255之间。

像素范围处理saturate_cast <typename _Tp>()

  • saturate_cast <UCHAR>( - 100),返回0
  • saturate_cast <UCHAR>(288),返回255
  • saturate_cast <UCHAR>(100),返回100

这个函数的功能是确保RGB值范围在0〜255之间。

添加上:

#include <opencv2opencv.hpp>
#include <iostream>

#include <math.h>

using namespace std;
using namespace cv;


int main()
{
    Mat src, dst;
    src = imread("E:\VS2015Opencv\vs2015\project\picture\cat.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]表示当前像素点对应的下一行的那个像素点
            //output[col] = 5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]);
        }

    }

    double timeconsume = ((double)getTickCount() - timeStart) / getTickFrequency();
    cout << "运行上面程序共耗时: " << timeconsume << endl;

    //输出 掩膜操作后的图像
    namedWindow("contrast image", CV_WINDOW_AUTOSIZE);
    imshow("contrast image", dst);

    waitKey(0);
    return 0;
}

掩膜(mask)第3张

 函数调用filter2D功能

   定义掩膜:Mat kernel = (Mat_(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
                    filter2D( src, dst, src.depth(), kernel ); 其中src与dst是Mat类型变量、src.depth表示位图深度,有32、24、8等。

#include <opencv2opencv.hpp>
#include <iostream>

#include <math.h>

using namespace std;
using namespace cv;


int main()
{
	Mat src, dst;
	src = imread("E:\VS2015Opencv\vs2015\project\picture\cat.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]表示当前像素点对应的下一行的那个像素点
			//output[col] = 5 * current[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;
}

  

掩膜(mask)第3张
与前面没有区别;

 小结

1.图像中,各种位运算,比如与、或、非运算与普通的位运算类似。 
2.如果用一句话总结,掩膜就是两幅图像之间进行的各种位运算操作。

 可以看看下面代码:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

int main()
{
    Mat image, mask;
    Rect r1(100, 100, 250, 300);
    Mat img1, img2, img3, img4;
    image = imread("E:\VS2015Opencv\vs2015\project\picture\cat.jpg");
    mask = Mat::zeros(image.size(), CV_8UC1);
    mask(r1).setTo(255);
    img1 = image(r1);
    image.copyTo(img2, mask);

    image.copyTo(img3);
    img3.setTo(0, mask);


    imshow("Image sequence", image);
    imshow("img1", img1);
    imshow("img2", img2);
    imshow("img3", img3);
    imshow("mask", mask);

    waitKey();
    return 0;
}

注意程序中的这两句关于Mask的操作。

    mask = Mat::zeros(image.size(), CV_8UC1);
    mask(r1).setTo(255); //r1是已经设置好的兴趣区域

解释一下上面两句的操作。

  • 第一步建立与原图一样大小的mask图像,并将所有像素初始化为0,因此全图成了一张全黑色图。
  • 第二步将mask图中的r1区域的所有像素值设置为255,也就是整个r1区域变成了白色。

这样就能得到Mask图像了。

掩膜(mask)第5张

image.copyTo(img2, mask);

 这句是原始图image拷贝到目的图img2上

其实拷贝的动作完整版本是这样的:

原图(image)与掩膜(mask)进行与运算后得到了结果图(img2)。

说白了,mask就是位图啊,来选择哪个像素允许拷贝,哪个像素不允许拷贝。如果mask像素的值是非0的,我就拷贝它,否则不拷贝。

因为我们上面得到的mask中,感兴趣的区域是白色的,表明感兴趣区域的像素都是非0,而非感兴趣区域都是黑色,表明那些区域的像素都是0。一旦原图与mask图进行与运算后,得到的结果图只留下原始图感兴趣区域的图像了。也正如下图所示。

掩膜(mask)第6张

如果想要直接抠出目标区域,直接这样写就OK了:

img1 = image(r1);

掩膜(mask)第7张

image.copyTo(img3);
img3.setTo(0, mask);

上面两句代码所做的事情是首先将原始图image拷贝一份给img3,然后img3将mask白色区域设置为0(黑色),好比如果mask中像素非0的,我就把我图像对应的那个点的像素值设置为0,否则啥也不做。伪代码是   if mask(i,j)>0 then img3(i,j)=0。

也就是说mask为1的位置设置为0,如下图

掩膜(mask)第8张

免责声明:文章转载自《掩膜(mask)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【转】 JS实现HTML标签转义及反转义27.Qt Quick QML-State、Transition下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[html]浏览器标签小图标LOGO简单设置

方式一:如果是一个项目一个LOGO 的话,直接接把图片像素设置成16x16像素,然后改名favicon.ico放在项目根部目录就可以,自动识别的! 方式二:简单设置! 首先找一个图片把像素设置成16x16像素, 然后把图片名字改成favicon.ICO, 最后在你的html页面上标签<head></head>种加上这么一句 (好像浏...

深度图像的获取原理

RGB-D(深度图像)   深度图像 = 普通的RGB三通道彩色图像 + Depth Map   在3D计算机图形中,Depth Map(深度图)是包含与视点的场景对象的表面的距离有关的信息的图像或图像通道。其中,Depth Map 类似于灰度图像,只是它的每个像素值是传感器距离物体的实际距离。通常RGB图像和Depth图像是配准的,因而像素点之间具有一...

矩阵理论 第三讲 线性变换及其矩阵

第三讲 线性变换及其矩阵 一、线性变换及其运算 定义:设V是数域K上的线性空间,T是V到自身的一个映射,使得对于V中的任意元素x均存在唯一的yV与之对应,则称T为V的一个变换或算子,记为 Tx=y 称y为x在变换T下的象,x为y的原象。 若变化T还满足 T(kx+ly)=k(Tx)+l(Ty)x,yV, k,lK 称T为线性变换。 [例1...

Git知识总览(三) 分支的创建、删除、切换、合并以及冲突解决

  前两篇博客集中的聊了git的一些常用命令,具体请参见《Git知识总览(一) 从 git clone 和 git status 谈起》、《Git知识总览(二) git常用命令概览》。本篇博客主要涉及了在git版本管理中的分支的创建、切换以及合并。并且罗列了在merge分支使发生冲突时的解决方式。同时还介绍了如何删除本地分支以及远程分支。本篇博客除了参考...

Qt 智能指针学习

从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *label =...

【学习总结】《大话数据结构》- 第6章-树

【学习总结】《大话数据结构》- 总 第6章树-代码链接 启示: 树 目录 6.1 开场白 6.2 树的定义 6.3 树的抽象数据类型 6.4 树的存储结构 6.5 二叉树的定义 6.6 二叉树的性质 6.7 二叉树的存储结构 6.8 遍历二叉树 6.9 二叉树的建立 6.10 线索二叉树 6.11 树、森林与二叉树的转换 6.12 赫夫曼树及其应用 6...