矩阵的掩码操作

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

我们的测试案例:

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


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

现在让我们来看看我们怎样通过使用基本的像素处理方法或使用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));
}

效果图如下:








### 掩码矩阵的概念 掩码矩阵是一种用于特定位置屏蔽或修改数据的工具,广泛应用于图像处理、自然语言处理等领域。其核心功能在于通过指定某些位置为特殊值(如负无穷大),从而忽略这些位置的数据对最终结果的影响[^2]。 在图像处理领域,掩码矩阵通常被用来定义局部区域的操作权重。例如,在锐化图像的过程中,可以通过设计一个合适的掩码矩阵来增强边缘特征[^3]。而在自然语言处理中,掩码则常用于序列模型中的注意力机制,通过对填充部分(PAD)施加无限小的权值,使得它们不会干扰有效信息的学习过程[^4]。 --- ### 实现方法 #### 方法一:手动构建掩码矩阵并应用 可以基于具体需求自定义掩码矩阵,并将其作用于输入数据。以下是使用 Python 和 NumPy 的简单示例: ```python import numpy as np # 定义原始矩阵 input_matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # 构建掩码矩阵 mask_matrix = np.array([ [0, -1, 0], [-1, 5, -1], [0, -1, 0] ]) # 应用掩码矩阵 result = np.zeros_like(input_matrix).astype(float) for i in range(1, input_matrix.shape[0]-1): for j in range(1, input_matrix.shape[1]-1): patch = input_matrix[i-1:i+2, j-1:j+2] result[i, j] = np.sum(patch * mask_matrix) print(result) ``` 上述代码展示了如何利用卷积核形式的手动掩码矩阵完成基本的空间滤波操作。 #### 方法二:借助 OpenCV 提供的功能 对于更复杂的场景,可以直接调用 OpenCV 中预置好的函数 `cv2.filter2D` 来简化流程。下面是一个简单的例子: ```python import cv2 import numpy as np # 创建测试图片 image = np.array([[100, 200, 100], [200, 100, 200], [100, 200, 100]], dtype=np.uint8) # 设定掩码矩阵 kernel = np.array([ [0, -1, 0], [-1, 5, -1], [0, -1, 0] ], dtype=np.float32) # 使用 filter2D 函数执行掩码操作 output_image = cv2.filter2D(image, -1, kernel) print(output_image) ``` 此段代码实现了与前一段类似的逻辑,但更加高效且易于扩展到多通道彩色图像的情况。 #### 方法三:针对 NLP 场景下的掩码生成 当涉及自然语言处理时,尤其是 Transformer 结构中的 Masked Attention 部分,则需特别注意 PAD 值以及未来时间步预测等问题。以下展示了一个基础版本的实现思路: ```python import torch def create_padding_mask(seq): """ 输入 seq 是形状为 (batch_size, seq_len) 的张量, 返回对应大小的 padding mask。 """ pad_token_id = 0 # 假设 PAD token ID 为 0 mask = (seq != pad_token_id).unsqueeze(1).unsqueeze(2) return mask.type(torch.int32) # 测试样例 sequence_input = torch.tensor([[7, 6, 0, 0], [1, 2, 3, 0]]) padding_mask = create_padding_mask(sequence_input) print(padding_mask) ``` 这里创建的是一个布尔类型的掩码矩阵,其中有效的单词位置标记为 True 而 PAD 则标记为 False。随后可进一步调整数值范围至适合网络训练的形式,比如转换成浮点数并将无效位赋值为 `-inf`。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值