Opencv C++ 五、简单的图像锐化与卷积运算

引子:

图像锐化和卷积是图像处理中常用的技术,它们的主要目的如下:

1. 图像锐化:

图像锐化是一种用于增强图像细节和边缘的技术。它通过强调图像中的高频信息(如边缘和细节)来使图像看起来更清晰和更有质感。锐化技术有助于突出图像中不同区域之间的差异,使图像更容易理解和分析。

主要目的包括:

  • 提高图像的视觉质量。
  • 增强图像中的边缘和细节。
  • 减少模糊和不清晰。

2. 卷积:

卷积是一种用于图像处理和信号处理的数学运算,它在不同的上下文中有不同的目的。在图像处理中,卷积通常用于应用各种滤波器来改变图像的特性。

主要目的包括:

  • 模糊(平滑)图像:通过应用平均滤波器或高斯滤波器,可以减少噪声并减轻图像中的细节,用于去噪和降低图像的粗糙度。
  • 锐化图像:通过应用锐化滤波器(如Laplacian滤波器或增强边缘的滤波器),可以增强图像中的边缘和细节,使其看起来更清晰。
  • 边缘检测:应用特定的边缘检测滤波器(如Sobel、Prewitt或Canny滤波器)来检测图像中的边缘。

总之,图像锐化旨在改善图像的视觉质量和细节,而卷积是一种用于实现各种图像处理任务的数学操作,包括锐化、模糊、边缘检测等。具体的操作和目的会根据具体应用和任务的不同而有所不同。

Part1:人为创造一张含有噪声的图像(如果有现成的,这一步去掉)

在图像中加入白色椒盐噪声:

创建Salt头文件:

在Salt.h中输入:

#pragma once
#include<iostream>
#include<opencv2/opencv.hpp>
#include <random>  //随机数头文件


using namespace cv;
using namespace std;

void Salt(Mat image, int n); //n:加入噪声点数

创建Salt.cpp源文件:

在Salt.cpp中输入:

#include "Salt.h"

void Salt(Mat image, int n)
{
	//随机数生成器
	default_random_engine generater;
	uniform_int_distribution<int>randomRow(0, image.rows - 1);
	uniform_int_distribution<int>randomCol(0, image.cols - 1);

	int i, j;
	for (int k = 0; k < n; k++)
	{
		i = randomCol(generater);
		j = randomRow(generater);
		if (image.channels() == 1)
		{
			image.at<uchar>(j, i) = 255;
		}
		else if (image.channels() == 3)
		{
			image.at<Vec3b>(j, i)[0] = 255;
			image.at<Vec3b>(j, i)[1] = 255;
			image.at<Vec3b>(j, i)[2] = 255;
		}
	}
}

随后在创建一个jiaoyan.cpp的源文件:

在其中输入:

#include<iostream>
#include<opencv2/opencv.hpp>
#include "Salt.h"

using namespace cv;
using namespace std;

int main()
{

	Mat image1 = imread("D://lena.png");  //读取图像;
	if (image1.empty())
	{
		cout << "读取错误" << endl;
		return -1;
	}
	imshow("image1", image1);  //显示原图像;

	Salt(image1, 5000); //加入5000个噪声点
	imshow("image2", image1);  //显示噪声图像;
	imwrite("jiaoyan.png", image1); //保存图像为png格式,文件名称为jiaoyan
	waitKey(0);  //暂停,保持图像显示,等待按键结束
	return 0;
}

其运行结果:

该椒盐图片保存在该项目的根目录:

Part2:对图像进行图像锐化

选择的模版为:

0 -1 0
-1 5 -1
0 -1 0

新建项目,创建ruihua.cpp,在其中输入:

此处使用的为正常图片

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat image1, output_ruihua;   //定义输入图像和输出图像
	image1 = imread("D://lena.png");  //读取图像;
	if (image1.empty())
	{
		cout << "读取错误" << endl;
		return -1;
	}
	output_ruihua = Mat(image1.size(), image1.type());  //定义输出图像大小
	output_ruihua = image1.clone();   //克隆原图像素值

	int rows = image1.rows;    //原图行数
	int stepx = image1.channels();   //原图通道数
	int cols = (image1.cols) * image1.channels();  //矩阵总列数,在BGR彩色图像中,每个像素的BGR通道按顺序排列,因此总列数=像素宽度*通道数

	for (int row = 1; row < (rows - 1); row++)   //对行遍历
	{
		const uchar* previous = image1.ptr<uchar>(row - 1);  //原图上一行指针
		const uchar* current = image1.ptr<uchar>(row);       //原图当前行指针
		const uchar* next = image1.ptr<uchar>(row + 1);      //原图下一行指针
		uchar* output = output_ruihua.ptr<uchar>(row);        //输出图像当前行指针

		for (int col = stepx; col < (cols - stepx); col++)  //对列遍历
		{
			output[col] = saturate_cast<uchar>(5 * current[col] - (previous[col] + current[col - stepx] + current[col + stepx] + next[col]));
			//saturate_cast<uchar>(a),当a在0—255时输出a,当a小于0输出0,当a大于255输出255,保证a的值在0~255之间
		}
	}

	imshow("image1", image1);
	imshow("output_ruihua", output_ruihua);

	waitKey(0);  //暂停,保持图像显示,等待按键结束
	return 0;
}

左图为锐化结果,右边为原图:

这个锐化滤波器使用了当前像素以及其周围像素的值来计算输出像素的值。这是一个经典的锐化滤波器,通常称为Laplacian锐化。它通过计算当前像素值与其周围像素之和的差异来突出图像的边缘。

函数介绍:

output_ruihua = Mat(image1.size(), image1.type());  //定义输出图像大小
	output_ruihua = image1.clone();   //克隆原图像素值

  1. output_ruihua = Mat(image1.size(), image1.type());: 这一行创建了一个新的 Mat 对象 output_ruihua,其大小与 image1 相同,类型也与 image1 相同。这确保了 output_ruihuaimage1 具有相同的图像尺寸和像素类型。

  2. output_ruihua = image1.clone();: 这一行将 image1 的像素值克隆到 output_ruihua 中。这意味着 output_ruihua 现在包含了与 image1 相同的像素值,但它们是不同的内存实例。这是为了确保你可以在 output_ruihua 上进行卷积操作,而不会影响原始的 image1。因为卷积是一个基于像素值的操作,克隆 image1 的像素值到 output_ruihua 可以确保你在不改变原始图像的情况下进行卷积处理。

for (int row = 1; row < (rows - 1); row++)   //对行遍历
	{
		const uchar* previous = image1.ptr<uchar>(row - 1);  //原图上一行指针
		const uchar* current = image1.ptr<uchar>(row);       //原图当前行指针
		const uchar* next = image1.ptr<uchar>(row + 1);      //原图下一行指针
		uchar* output = output_ruihua.ptr<uchar>(row);        //输出图像当前行指针

		for (int col = stepx; col < (cols - stepx); col++)  //对列遍历
		{
			output[col] = saturate_cast<uchar>(5 * current[col] - (previous[col] + current[col - stepx] + current[col + stepx] + next[col]));
			//saturate_cast<uchar>(a),当a在0—255时输出a,当a小于0输出0,当a大于255输出255,保证a的值在0~255之间
		}
	}
  1. for (int row = 1; row < (rows - 1); row++):这是一个 for 循环,用于遍历图像的每一行。它从第2行开始遍历,直到倒数第2行。这是因为卷积操作需要考虑图像边界的像素,而边界像素的上下行(previousnext)可能不存在。

  2. const uchar* previous = image1.ptr<uchar>(row - 1);:这行代码获取了上一行的指针 previousimage1.ptr<uchar>(row - 1) 表示获取图像 image1 中上一行的像素数据的指针。这个指针将用于在卷积操作中访问上一行的像素值。

  3. const uchar* current = image1.ptr<uchar>(row);:这行代码获取了当前行的指针 current,用于访问当前行的像素值。

  4. const uchar* next = image1.ptr<uchar>(row + 1);:这行代码获取了下一行的指针 next,用于访问下一行的像素值。

  5. uchar* output = output_ruihua.ptr<uchar>(row);:这行代码获取了输出图像 output_ruihua 中当前行的指针 output。这个指针将用于在卷积操作中写入卷积结果。

总之,这段代码设置了一个循环,遍历输入图像的每一行,并为每一行获取上一行、当前行和下一行的像素值的指针。这是为了在卷积操作中使用这些指针来处理图像的像素数据。这种方式可以提高卷积操作的效率,因为它不需要多次访问图像数据,而是将数据存储在指针中以便反复使用。

总之,这两行代码为卷积处理创建了一个输出图像 output_ruihua,并确保它与原始图像 image1 具有相同的大小和像素值。这是一个很好的做法,以确保你可以进行图像处理操作而不影响原始图像。

另外,代码中还使用了saturate_cast<uchar>(a)来确保输出值在0到255的范围内,这是非常重要的,因为图像像素值通常在这个范围内。

请注意,这段代码只处理图像的内部像素,所以可能不会处理图像的边缘像素,因为你的循环从 stepx 处开始,以避免访问图像边界。

最后,你可能需要保存输出图像,以便查看锐化后的效果。你可以使用 imwrite 函数将 output_image 保存为图像文件。

Part3:对图像进行卷积处理

本文采用的是filter2D方法:

新建项目,创建juanji.cpp源文件,在其中输入:

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat image1, output_juanji;   //定义输入图像和输出图像
	image1 = imread("D://lena.png");  //读取图像;
	if (image1.empty())
	{
		cout << "读取错误" << endl;
		return -1;
	}

	Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);  //创建滤波器
	filter2D(image1, output_juanji, image1.depth(), kernel);  //卷积

	imshow("image1", image1);
	imshow("output_juanji", output_juanji);

	waitKey(0);  //暂停,保持图像显示,等待按键结束
	return 0;
}

输出结果为:

左图为卷积后,右图为卷积前的原图

Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);  //创建滤波器
	filter2D(image1, output_filter2D, image1.depth(), kernel);  //filter2D卷积

Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);: 这一行创建了一个3x3的滤波器 kernel,其中包含了特定的权重值。这个滤波器的中心值为5,周围的值为-1,用于增强图像中的边缘和细节。filter2D(image1, output_filter2D, image1.depth(), kernel); : 这一行使用 filter2D 函数将滤波器 kernel 应用于输入图像 image1,并将结果存储在 output_filter2D 中。image1.depth() 用于指定输入图像的深度(通常是 CV_8U,表示8位无符号整数),然后将滤波器 kernel 应用于图像。这个操作将使用滤波器 kernel 对输入图像 image1 进行卷积处理,以增强边缘和细节。处理后的图像将存储在 output_filter2D 中。这种滤波器可以用于改进图像的视觉质量,特别是在突出显示边缘和特征方面。

另外,可以再利用另一种方案进行处理:

Sobel滤波

新建sobel.cpp,在其中输入:

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;

using namespace cv; // 引入OpenCV命名空间

int main() {
    // 读取图像
    Mat inputImage = imread("D://lena.png", IMREAD_GRAYSCALE); // 以灰度模式读取图像

    if (inputImage.empty()) {
        cout << "无法加载图像." << endl;
        return -1;
    }

    // 创建输出图像
    Mat outputImage;

    // 应用Sobel滤波
    Mat gradientX, gradientY;
    Sobel(inputImage, gradientX, CV_32F, 1, 0, 3);
    Sobel(inputImage, gradientY, CV_32F, 0, 1, 3);

    // 计算梯度幅值
    magnitude(gradientX, gradientY, outputImage);

    // 将梯度图像归一化为8位图像
    normalize(outputImage, outputImage, 0, 255, NORM_MINMAX, CV_8U);

    // 显示原始图像和处理后的图像
    imshow("原始图像", inputImage);
    imshow("Sobel滤波后的图像", outputImage);

    // 保存处理后的图像
    imwrite("output_image.jpg", outputImage);

    // 等待用户按下任意键后关闭窗口
    waitKey(0);

    return 0;
}

左侧为Sobel前的图片,右侧为Sobel滤波后的图像;

这里存在一个问题,就是窗口的名称变成了乱码,可以通过

  1. 打开源代码文件。

  2. 转到菜单栏的"文件",然后选择"另存为"。

  3. 在"另存为"对话框中,选择"编码"下拉菜单,然后选择"UTF-8 (with signature) - Codepage 65001"。

  4. 保存文件。

猜你喜欢

转载自blog.csdn.net/w2492602718/article/details/134006369