opencv4笔记

图像二值化

全局法Threshold

void cvThreshold( const CvArr* src, CvArr* dst, double threshold,
                  double max_value, int threshold_type );

src:原始数组 (单通道 , 8-bit of 32-bit 浮点数).

dst:输出数组,必须与 src 的类型一致,或者为 8-bit.

threshold:阈值

max_value:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值.

threshold_type:阈值类型
 

大津法

大津法OSTU阈值类型——适用于双峰直方图

OTSU算法也称最大类间差法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,利于后续的图像分,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小

三角法

 该方法是使用直方图数据,最适用于单个波峰,基于纯几何方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值

图像处理之三角法图像二值化_三角阈值法-CSDN博客

局部法adaptiveThreshold

void cv::adaptiveThreshold(InputArray src,
                           OutputArray dst,
                           double maxValue,
                           int adaptiveMethod,
                           int thresholdType,
                           int blockSize,
                           double C)

 InputArray src:源图像。
OutputArray dst:输出图像,与源图像的尺寸和数据类型一致。
maxValue:上面截图中的数学表达式已经很直观地说明了这个值的意义,这里就不再赘述了。
adaptiveMethod:在一个邻域内计算阈值所采用的算法 

aptiveThreshold()的阈值计算单位是像素的邻域块,而邻域块取多大,就由这个值作决定。
C:在对参数adaptiveMethod的说明中,已经说明了这个参数的作用,从中可以看出,这个参数实际上是一个偏移值调整量。

适用于光照不均衡以及更加复杂的情况下。二值化标志只能是THRESH_BINARY,THRESH_BINARY_INV

均值算法ADAPTIVE_THRESH_MEAN_C

平均值法就是对目标像素点的周围取一定size(为奇数)的块区域,将该区域的像素点灰度值的平均值再减去参数C的值得到的值作为阈值

高斯法(ADAPTIVE_THRESH_GAUSSIAN_C)

使用高斯的方法,则每个像素周围像素的权值则根据其到中心点的距离通过高斯方程得到,然后阈值就会等于各像素点权值乘以灰度值的积的累加再减去C,权值的和为1

关于Block Size和C的取值,一般Block Size取3~17比较合适,C也不宜太大,可取3~9,具体的值需要自己测试调节。

【C++ OpenCV】阈值二值化、阈值反二值化、截断、阈值取零、阈值反取零、自适应阈值使用方法以及时机_固定阈值二值化-CSDN博客

图像处理——图像的二值化操作及阈值化操作(固定阈值法(全局阈值法——大津法OTSU和三角法TRIANGLE)和自适应阈值法(局部阈值法——均值和高斯法)) | 码农家园 (codenong.com)icon-default.png?t=N7T8https://www.codenong.com/cs107102117/

 两者比较:

固定阈值的二值化效果一般比较差,尤其是在处理亮度差别很大的图像,它是对整个图像进行阈值操作,图像比较平滑,细节较少。自适应阈值法则是围绕目标像素点的一小块区域进行阈值化操作,效果会更好,图像的细纹都保留了下来,即图像细节得到了保存。

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat img_B, img_B_V, gray_B, gray_B_V, gray_T, gray_T_V, gray_TRUNC;

	//彩色图像二值化
	threshold(img, img_B, 125, 255, THRESH_BINARY);
	threshold(img, img_B_V, 125, 255, THRESH_BINARY_INV);
	imshow("img_B", img_B);
	imshow("img_B_V", img_B_V);

	//灰度图BINARY二值化
	threshold(gray, gray_B, 125, 255, THRESH_BINARY);
	threshold(gray, gray_B_V, 125, 255, THRESH_BINARY_INV);
	imshow("gray_B", gray_B);
	imshow("gray_B_V", gray_B_V);

	//灰度图像TOZERO变换
	threshold(gray, gray_T, 125, 255, THRESH_TOZERO);
	threshold(gray, gray_T_V, 125, 255, THRESH_TOZERO_INV);
	imshow("gray_T", gray_T);
	imshow("gray_T_V", gray_T_V);

	//灰度图像TRUNC变换
	threshold(gray, gray_TRUNC, 125, 255, THRESH_TRUNC);
	imshow("gray_TRUNC", gray_TRUNC);

	//灰度图像大津法和三角形法二值化
	Mat img_Thr = imread("threshold.png", IMREAD_GRAYSCALE);
	Mat img_Thr_O, img_Thr_T;
	threshold(img_Thr, img_Thr_O, 100, 255, THRESH_BINARY | THRESH_OTSU);
	threshold(img_Thr, img_Thr_T, 125, 255, THRESH_BINARY | THRESH_TRIANGLE);
	imshow("img_Thr", img_Thr);
	imshow("img_Thr_O", img_Thr_O);
	imshow("img_Thr_T", img_Thr_T);

	//灰度图像自适应二值化
	Mat adaptive_mean, adaptive_gauss;
	adaptiveThreshold(img_Thr, adaptive_mean, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 55, 0);
	adaptiveThreshold(img_Thr, adaptive_gauss, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 55, 0);

	imshow("adaptive_mean", adaptive_mean);
	imshow("adaptive_gauss", adaptive_gauss);
	waitKey(0);
	return 0;
}

LUT查表,多阈值

在OpenCV中,LUT代表查找表(Lookup Table),它是一种用于像素值映射的技术。查找表是一个数组,其中每个元素对应于输入像素值的一个映射值。使用LUT可以有效地对图像进行像素值的转换,常用于颜色空间转换或者对特定像素值进行操作。

LUT通常在需要将图像像素值映射到其他值域时使用,例如将灰度图像转换为伪彩色图像。通过定义一个映射表,可以将原始图像中的每个像素值映射到新的颜色或灰度值,从而实现不同的效果。

因此,LUT主要用于对图像像素值进行映射,从而实现颜色空间转换等操作,而二值化阈值化则用于将灰度图像转换为二值图像。两者的主要区别在于处理的目标和操作方式。

和阈值threshold有什么区别?

阈值threshold只能将图片全局按照一个阈值进行映射,LUT可以按照自己设置的阈值范围进行映射,兼容了threshold的二值化,但是可以多阈值划分。不仅仅可以应用于0-255像素值。
【C++ OpenCV】LUT查找表原理、实操、使用时机_dagengen12138的博客-CSDN博客

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

using namespace std;
using namespace cv;

int main()
{
	//LUT查找表第一层
	uchar lutFirst[256];
	for (int i = 0; i<256; i++)
	{
		if (i <= 100)
			lutFirst[i] = 0;
		if (i > 100 && i <= 200)
			lutFirst[i] = 100;
		if (i > 200)
			lutFirst[i] = 255;
	}
	Mat lutOne(1, 256, CV_8UC1, lutFirst);

	//LUT查找表第二层
	uchar lutSecond[256];
	for (int i = 0; i<256; i++)
	{
		if (i <= 100)
			lutSecond[i] = 0;
		if (i > 100 && i <= 150)
			lutSecond[i] = 100;
		if (i > 150 && i <= 200)
			lutSecond[i] = 150;
		if (i > 200)
			lutSecond[i] = 255;
	}
	Mat lutTwo(1, 256, CV_8UC1, lutSecond);

	//LUT查找表第三层
	uchar lutThird[256];
	for (int i = 0; i<256; i++)
	{
		if (i <= 100)
			lutThird[i] = 100;
		if (i > 100 && i <= 200)
			lutThird[i] = 200;
		if (i > 200)
			lutThird[i] = 255;
	}
	Mat lutThree(1, 256, CV_8UC1, lutThird);

	//拥有三通道的LUT查找表矩阵
	vector<Mat> mergeMats;
	mergeMats.push_back(lutOne);
	mergeMats.push_back(lutTwo);
	mergeMats.push_back(lutThree);
	Mat LutTree;
	merge(mergeMats, LutTree);

	//计算图像的查找表
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	Mat gray, out0, out1, out2;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	LUT(gray, lutOne, out0);
	LUT(img, lutOne, out1);
	LUT(img, LutTree, out2);
	imshow("out0", out0);
	imshow("out1", out1);
	imshow("out2", out2);
	waitKey(0);
	return 0;
}

resize中的插值法

先验知识: 

(1)空间分辨率:图像空间中可分辨的最小细节。一般用单位长度上采样的像素数目或单位长度上的线对数目表示。(空间分辨率的度量必须针对空间单位来规定才有意义)
(2)灰度分辨率:图像灰度级中可分辨的最小变化。一般用灰度级或比特数表示
(3)空间分辨率(图像的采样)与图像质量的关系:空间分辨率越高,图像质量越好;空间分辨率越低,图像质量越差,会出现虚假轮廓

内插

INTER_NEAREST

最近邻插值法。在图像缩放过程中,最近邻插值法将目标图像的每个像素值设为源图像中与该像素最近的点的值。这种方法在处理过程中产生块状效果和混叠效果的可能性较低。

以下是INTER_NEAREST插值方法的优点:

  1. 计算速度快:最近邻插值法是所有插值方法中最简单的,因此计算速度快。
  2. 适合对速度要求较高的场景:由于其较快的计算速度,INTER_NEAREST插值法适合于对速度要求较高的场景,例如实时图像处理。

然而,INTER_NEAREST插值法也存在以下缺点:

  1. 图像细节的失真:由于该方法简单地取最近的像素值,可能会造成图像细节的失真。
  2. 产生块状效果:在处理过程中,可能会产生块状效果,特别是在放大图像时。

使用场景:INTER_NEAREST插值法常用于需要快速处理且对图像细节要求不高的场景,例如图像压缩、图像预处理等。

INTER_LINEAR

OpenCV中双线性插值(Bilinear interpolation)的标志。双线性插值是一种线性插值方法,它使用两个相邻的像素点来计算目标像素点的值。

在图像缩放过程中,双线性插值法通过考虑源图像中与目标像素相邻的四个像素的值,来计算目标像素的值。这种方法在处理过程中产生块状效果和混叠效果的可能性较低,并且可以保持较好的平滑性。

INTER_LINEAR插值方法的速度较快,适用于对速度要求较高的场景。然而,它也存在一些缺点,例如在处理过程中可能会产生一些振铃效应和图像细节的失真。

使用场景:INTER_LINEAR插值法常用于图像缩放、图像重采样等应用中,特别是在一些需要保持图像细节和边缘清晰度的场景中。

INTER_CUBIC

计算量最大,算法也是最为复杂的。在几何运算中,双线性内插法的平滑作用可能会使图像的细节产生退化,在进行放大处理时,这种影响更为明显。在其他应用中,双线性插值的斜率不连续性会产生不希望的结果。立方卷积插值不仅考虑到周围四个直接相邻像素点灰度值的影响,还考虑到它们灰度值变化率的影响。因此克服了前两种方法的不足之处,能够产生比双线性插值更为平滑的边缘,计算精度很高,处理后的图像像质损失最少,效果是最佳的。 

INTER_CUBIC是OpenCV中双三次插值(bicubic interpolation)的标志。双三次插值是一种更高级的插值方法,它使用4×4的像素块来计算目标像素点的值。这种方法在处理过程中会考虑更多的像素信息,从而产生更平滑的图像效果。

在图像缩放过程中,双三次插值法通过考虑源图像中与目标像素相邻的16个像素的值,来计算目标像素的值。这种方法在处理过程中产生块状效果和混叠效果的可能性较低,并且可以保持较好的平滑性和边缘清晰度。

INTER_CUBIC插值方法的优点是可以产生较为平滑的图像效果,并且在处理过程中不容易产生振铃效应。然而,它也存在一些缺点,例如计算量较大,处理速度较慢,并且在处理过程中可能会出现一些图像细节的失真。

使用场景:INTER_CUBIC插值法常用于图像缩放、图像重采样等应用中,特别是在一些需要保持图像细节和边缘清晰度且对处理速度要求不高的场景中。

数字图像处理学习笔记——数学基础 - 知乎 (zhihu.com)

opencv中插值算法详解_lanczos插值-CSDN博客

三种插值算法优缺点分析 - 哔哩哔哩 (bilibili.com)

 INTER_AREA

INTER_AREA是OpenCV中一种图像缩放时使用的插值方法。这种方法通过在目标图像中每个像素的值由源图像中对应区域像素的平均值来确定,从而对图像进行平均采样以减小图像的尺寸。当图像缩小时,INTER_AREA能够有效地减少图像的尺寸并保持较好的平滑性。

以下是INTER_AREA插值方法的优点:

  1. 能够有效减小图像的尺寸,同时保持较好的平滑性。
  2. 适用于一些对图像细节要求不高但需要快速处理的场景。

然而,INTER_AREA插值方法也存在以下缺点:

  1. 在处理过程中可能会产生块状效果。
  2. 在处理过程中可能会产生混叠效果。

使用场景:INTER_AREA插值方法常用于图像压缩

 INTER_LANCZOS4

INTER_LANCZOS4是OpenCV中另一种图像缩放时使用的插值方法。它使用Lanczos矩阵的特征向量计算出图像的插值,具有较好的插值效果和较快的计算速度。当图像缩小时,INTER_LANCZOS4能够有效地减少图像的尺寸并保持较好的边缘清晰度。

以下是INTER_LANCZOS4插值方法的优点:

  1. 插值效果较好,能够保持较好的边缘清晰度。
  2. 计算速度较快,适用于一些对处理速度要求较高的场景。

然而,INTER_LANCZOS4插值方法也存在以下缺点:

  1. 在处理过程中可能会产生一些噪声。
  2. 对于一些对图像细节要求较高的应用场景,可能需要进一步处理以减少噪声。

使用场景:INTER_LANCZOS4插值方法常用于图像缩放、图像重采样等应用中,特别是在一些需要快速处理且对图像细节要求不高的场景中。

 INTER_LINEAR_EXACT

位精确双线插值法是OpenCV中一种图像缩放时使用的插值方法。它使用双线性插值方法计算出目标图像中每个像素的值,以实现精确的双线性插值。当图像缩小时,INTER_LINEAR_EXACT能够有效地减少图像的尺寸并保持较好的平滑性。

以下是INTER_LINEAR_EXACT插值方法的优点:

  1. 能够实现精确的双线性插值,保持较好的平滑性和边缘清晰度。
  2. 在处理过程中不容易产生块状效果和混叠效果。

然而,INTER_LINEAR_EXACT插值方法也存在以下缺点:

  1. 计算量较大,处理速度相对较慢。
  2. 在处理过程中可能会产生一些振铃效应。

使用场景:INTER_LINEAR_EXACT插值方法常用于图像缩放、图像重采样等应用中,特别是在一些需要精确插值且对图像细节要求较高的场景中。

 INTER_MAX

用掩码进行插值是OpenCV中一种图像缩放时使用的插值方法。它使用最大值法插值计算出目标图像中每个像素的值,即每个像素的值等于源图像中对应区域像素的最大值。当图像缩小时,INTER_MAX能够有效地减少图像的尺寸并保持较好的边缘清晰度。

以下是INTER_MAX插值方法的优点:

  1. 能够有效地减少图像的尺寸并保持较好的边缘清晰度。
  2. 在处理过程中不容易产生块状效果和混叠效果。

然而,INTER_MAX插值方法也存在以下缺点:

  1. 计算量较大,处理速度相对较慢。
  2. 在处理过程中可能会产生一些振铃效应(图像处理中,对一幅图像进行滤波处理,若选用的频域滤波器具有陡峭的变化)。

使用场景:INTER_MAX插值方法常用于图像缩放、图像重采样等应用中,特别是在一些需要保持图像边缘清晰且对图像细节要求较高的场景中。

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

using namespace std;
using namespace cv;

int main()
{
	Mat gray = imread("lena.png", IMREAD_GRAYSCALE);
	if (gray.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	Mat smallImg, bigImg0, bigImg1, bigImg2;
	resize(gray, smallImg, Size(15, 15), 0, 0, INTER_AREA);  //先将图像缩小
	resize(smallImg, bigImg0, Size(30, 30), 0, 0, INTER_NEAREST);  //最近邻差值
	resize(smallImg, bigImg1, Size(30, 30), 0, 0, INTER_LINEAR);  //双线性差值
	resize(smallImg, bigImg2, Size(30, 30), 0, 0, INTER_CUBIC);  //双三次差值
	namedWindow("smallImg", WINDOW_NORMAL);  //图像尺寸太小,一定要设置可以调节窗口大小标志
	imshow("smallImg", smallImg);
	namedWindow("bigImg0", WINDOW_NORMAL);
	imshow("bigImg0", bigImg0);
	namedWindow("bigImg1", WINDOW_NORMAL);
	imshow("bigImg1", bigImg1);
	namedWindow("bigImg2", WINDOW_NORMAL);
	imshow("bigImg2", bigImg2);
	waitKey(0);
	return 0;
}

仿射变换

仿射变换是什么?

仿射变换是计算机图形学中的基本概念,它是指一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。在二维空间中,仿射变换可以用一个矩阵表示,而在三维空间中,则需要一个更复杂的矩阵来表示。

仿射变换的优点包括:

  1. 保持图像的平行性:变换后互相平行的直线仍是互相平行,三角形映射后也仍是三角形。
  2. 可以实现平移、旋转、缩放等几何变换,并且变换效果可以随着变换参数的不同而变化,从而实现图像的扭曲和变形。

然而,仿射变换也存在一些缺点:

  1. 变换范围有限:仿射变换是一种线性变换,只能对图像进行平移、旋转、缩放和倾斜等简单的几何变换。对于复杂的变换,如透视变换,需要使用其他的变换方法。
  2. 变换效果不稳定:由于仿射变换只是对图像进行简单的线性变换,对于一些复杂的图像,变换效果可能不够理想。在这种情况下,需要使用其他的非线性变换方法。

为什么需要仿射变化?

反射变换在计算机图形学和图像处理中有着广泛的应用,主要有以下几个原因:

  1. 实现几何变换:反射变换可以实现对二维或三维空间中的点进行平移、旋转、缩放等几何变换。这些变换在图形渲染、图像配准、目标跟踪等任务中非常重要。
  2. 保持图像的形状和大小:反射变换是一种线性变换,它可以保持图像的形状和大小不变。这意味着,通过反射变换,我们可以将一个图像映射到另一个图像上,而不会改变图像的相对大小和形状。
  3. 增强图像的对比度和清晰度:反射变换可以将图像中的某些部分向上或向下移动,从而增强图像的对比度和清晰度。这种技术在图像处理中很有用,例如在医学成像、遥感图像处理等领域。
  4. 实现图像的扭曲和变形:反射变换可以实现对图像进行各种扭曲和变形,例如扭曲、拉伸、压缩等。这些变换可以用于创建特殊的效果,例如动画和游戏中的特效。
  5. 计算机图形学中的基本操作:反射变换是计算机图形学中的基本操作之一,它可以用于将三维模型渲染到二维平面上,实现投影变换和视锥体裁剪等操作。

 怎么做仿射变换?

1、对于旋转的变换矩阵:

Mat cv::getRotationMatrix2D(Point2f center,
                            double angle,
                            double scale
                            )


参数解释:
center:源图像的旋转中心;
angle:旋转角度,正值表示逆时针;
scale:各向同性比例因子;

  利用3个像素坐标获取变换矩阵

Mat cv::getAffineTransform(const Point2f src[],
                           const Point2f dst[]
                           )

src[]:源图像中的3个像素的坐标
dst[]:目标图像中的3个像素的坐标 

2、warpAffine()

void cv::warpAffine( InputArray src, OutputArray dst,
                     InputArray M0, Size dsize,
                     int flags, int borderType, const Scalar& borderValue )

src: 输入图片
dst: 输出图片,尺寸为dsize,类型与输入一致
M0: 2×3的变换矩阵
dsize: 输出图片的尺寸
flags: 插值算法,当flag为WARP_INVERSE_MAP时,M为逆变换矩阵
borderType: Pixel extrapolation method
borderValue: Value used in case of a constant border

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	Mat rotation0, rotation1, img_warp0, img_warp1;
	double angle = 30;  //设置图像旋转的角度
	Size dst_size(img.rows, img.cols);  //设置输出图像的尺寸
	Point2f center(img.rows / 2.0, img.cols / 2.0);  //设置图像的旋转中心
	rotation0 = getRotationMatrix2D(center, angle, 1);  //计算放射变换矩阵
	warpAffine(img, img_warp0, rotation0, dst_size);  //进行仿射变换
	imshow("img_warp0", img_warp0);
	//根据定义的三个点进行仿射变换
	Point2f src_points[3];
	Point2f dst_points[3];
	src_points[0] = Point2f(0, 0);  //原始图像中的三个点
	src_points[1] = Point2f(0, (float)(img.cols - 1));
	src_points[2] = Point2f((float)(img.rows - 1), (float)(img.cols - 1));
	dst_points[0] = Point2f((float)(img.rows)*0.11, (float)(img.cols)*0.20);  //放射变换后图像中的三个点
	dst_points[1] = Point2f((float)(img.rows)*0.15, (float)(img.cols)*0.70);
	dst_points[2] = Point2f((float)(img.rows)*0.81, (float)(img.cols)*0.85);
	rotation1 = getAffineTransform(src_points, dst_points);  //根据对应点求取仿射变换矩阵
	warpAffine(img, img_warp1, rotation1, dst_size);  //进行仿射变换
	imshow("img_warp1", img_warp1);
	waitKey(0);
	return 0;
}

透视变换

透视变换是什么?

透视变换(Perspective Transformation)是将图片投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。它是一种更复杂的变换,可以模拟人眼视角的透视效果,将平行的线条在变换后变得不再平行。

透视变换通常用于计算机图形学中的三维渲染,将三维模型投影到二维平面上,实现投影变换和视锥体裁剪等操作。它也常用于图像处理中,实现图像的扭曲和变形,增强图像的对比度和清晰度等操作。

透视变换的通用变换公式可以拆分成四个部分,包括线性变换和用于平移的部分。线性变换可以包括缩放、剪切和旋转等操作,而用于平移的部分则可以实现图像的平行移动。

透视变换的应用非常广泛,例如在计算机图形学中的三维游戏开发、虚拟现实、电影特效制作等领域,以及图像处理中的图像校正、图像增强、目标跟踪等任务中都有广泛的应用。

为什么需要透视变换? 

透视变换在计算机图形学和图像处理中是必要的,因为人们常常需要将三维场景或物体以二维图像的形式呈现出来,同时保持场景或物体的原始形状和大小。透视变换可以解决这个问题,它可以将三维场景或物体投影到二维平面上,同时保持场景或物体的相对大小和形状不变。

在计算机图形学中,透视变换被广泛应用于三维游戏开发、虚拟现实、电影特效制作等领域。在这些应用中,需要将三维模型渲染到二维平面上,以实现真实的视觉效果。透视变换可以模拟人眼的视角效果,使二维图像呈现出三维场景或物体的立体感。

在图像处理中,透视变换也被广泛应用于图像校正、图像增强、目标跟踪等任务中。例如,在医学成像中,透视变换可以用于将不同角度的图像对齐,以便进行更准确的分析和处理。在遥感图像处理中,透视变换可以用于将不同角度的卫星图像对齐,以进行地形分析和地貌制图等任务。

 怎么做透视变换?

变换矩阵:

Mat cv::getPerspectiveTransform(const Point2f src[], const Point2f dst[], int solveMethod = DECOMP_LU)
  • src[]:原图像中的四个像素坐标。
  • dst[]:目标图像中的四个像素坐标。
  • solveMethod:选择计算透视变换矩阵的方法,默认为DECOMP_LU。

函数的返回值是一个3×3的变换矩阵

变换函数:
 

void cv::warpPerspective(InputArray src, 
                        OutputArray dst, 
                        InputArray M, 
                        Size dsize, 
                        int flags=INTER_LINEAR, 
                        int borderMode=BORDER_CONSTANT, 
                        const Scalar& borderValue=Scalar())

参数意义如下:

  • src:输入图像。
  • dst:透视变换后输出图像,与src数据类型相同,但是尺寸与dsize相同。
  • M:3×3的透视变换矩阵。
  • dsize:输出图像的尺寸。
  • flags:插值方法标志。默认为INTER_LINEAR
  • borderMode:像素边界外推方法的标志。默认为BORDER_CONSTANT
  • borderValue:与像素边界外推方法对应的常数。默认为Scalar()
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("noobcvqr.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	Point2f src_points[4];
	Point2f dst_points[4];
	//通过Image Watch查看的二维码四个角点坐标
	src_points[0] = Point2f(94.0, 374.0);
	src_points[1] = Point2f(507.0, 380.0);
	src_points[2] = Point2f(1.0, 623.0);
	src_points[3] = Point2f(627.0, 627.0);
	//期望透视变换后二维码四个角点的坐标
	dst_points[0] = Point2f(0.0, 0.0);
	dst_points[1] = Point2f(627.0, 0.0);
	dst_points[2] = Point2f(0.0, 627.0);
	dst_points[3] = Point2f(627.0, 627.0);
	Mat rotation, img_warp;
	rotation = getPerspectiveTransform(src_points, dst_points);  //计算透视变换矩阵
	warpPerspective(img, img_warp, rotation, img.size());  //透视变换投影
	imshow("img", img);
	imshow("img_warp", img_warp);
	waitKey(0);
	return 0;
}

极坐标变换

什么是极坐标变换?

极坐标变换是将平面直角坐标系中的点用极坐标表示的变换。具体来说,极坐标变换公式为:x = r * cosθ,y = r * sinθ。其中,x、y为平面直角坐标系中某个点的坐标,r为此点到坐标原点的极径长度,θ为此点到坐标轴正方向的极角大小。

 为什么要进行极坐标变化,或者反极坐标变化

  1. 极坐标变换可以帮助我们更好地理解图像中的圆形、环状结构以及周期性特征。例如,极坐标变换可以用于矫正图像中的圆形物体或者包含在圆环中的物体。通过将图像坐标变换为极坐标(d, A),即相对于变换中心的距离d和向量角度A,可以帮助我们更好地理解和分析图像的结构和特征。
  2. 在某些数学问题中,极坐标变换可以简化问题的求解。例如,通过将曲线的方程从笛卡尔坐标系转换为极坐标系,可以减少方程中的变量数量,从而使问题更容易求解。对于某些复杂的曲线,如圆锥曲线和螺旋曲线,其方程在极坐标系中往往更加简单。极坐标变换还可以用于求解一些特殊的积分问题,例如计算极坐标系下的面积分、线积分和体积分等。

怎么样进行极坐标变换?

warpPolar函数是OpenCV库中的一个函数,用于将图像或矩阵从直角坐标系(笛卡尔坐标系)转换到极坐标系。该函数在OpenCV4版本中新增。

函数原型:


void cv::warpPolar( InputArray src, 
                    OutputArray dst, 
                    Size dsize, 
                    Point2f center, 
                    double maxRadius, 
                    int flags);

参数意义:

  • src:原图像,对通道数无要求,可以是灰度图像或者彩色图像。
  • dst:输出图像,它和原图像具有相同的数据类型和通道数。
  • dsize:目标图像大小。
  • center:极坐标变换时原点坐标。
  • maxRadius:最大半径。
  • flags:操作标志位,用于指定映射的模式,系统默认的模式是极坐标映射模式。WARP_POLAR_LOG: 表示使用对数极坐标变换。对数极坐标变换可以将输入图像中远离中心的位置映射到更大的极径上,从而更好地处理远离中心的区域。

这个函数的功能是将一幅图像映射到极坐标或者半极坐标空间中,其中半极坐标映射用于模仿人类的中心视觉,在视觉聚焦的地方可以看的很清晰,非聚焦的地方会比较模糊,通过弱化聚焦以外的区域,来简化图像,方便对图像进行处理。

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

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("dial.png");
	if (!img.data)
	{
		cout << "请检查图像文件名称是否输入正确" << endl;
		return -1;
	}

	Mat img1, img2;
	Point2f center = Point2f(img.cols / 2, img.rows/2);  //极坐标在图像中的原点
	//正极坐标变换
	warpPolar(img, img1, Size(300,600), center, center.x, INTER_LINEAR + WARP_POLAR_LINEAR);
	//逆极坐标变换
	warpPolar(img1, img2, Size(img.rows,img.cols), center, center.x, INTER_LINEAR + WARP_POLAR_LINEAR + WARP_INVERSE_MAP);

	imshow("原表盘图", img);
	imshow("表盘极坐标变换结果", img1); 
	imshow("逆变换结果", img2);
	waitKey(0);
	return 0;
}

图像金字塔

高斯金字塔

什么是高斯金字塔?

高斯金字塔是通过高斯模糊滤波和下采样不断地将图像的尺寸缩小,生成包含多个分辨率的一组图像。它本质上为信号的多尺度表示法,即将同一信号或图片多次进行高斯模糊,并且向下取样,生成不同尺度下的多组信号或图片以进行后续的处理。

为什么要高斯金字塔?

高斯金字塔的主要目的是在二维图像的基础上,榨取出图像中自然存在的另一个维度:尺度。高斯核是唯一的线性核,使用高斯核对图像模糊不会引入其他噪声。因此,构建高斯金字塔能够较好地对图像进行多尺度描述。此外,高斯金字塔结构简洁又通俗易懂,可以在其结构上进行改造延伸并与其他优秀的算法相结合,从而应用到新的领域中。

怎么样构建高斯金字塔?
void cv::pyrDown(InputArray src, OutputArray dst, int kernel_size)
  • src:输入图像,必须为8位或32位浮点型。
  • dst:输出图像,类型与输入图像相同。
  • kernel_size:下采样核的大小,必须是奇数。该参数控制下采样过程中高斯核的大小。核越大,模糊程度越高,图像细节丢失越多。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	vector<Mat> Gauss;  //高斯金字塔
	int level = 3;  //高斯金字塔下采样次数
	Gauss.push_back(img);  //将原图作为高斯金字塔的第0层
						   //构建高斯金字塔
	for (int i = 0; i < level; i++)
	{
		Mat gauss;
		pyrDown(Gauss[i], gauss);  //下采样
		Gauss.push_back(gauss);
	}
	
	//查看两个金字塔中的图像
	for (int i = 0; i < Gauss.size(); i++)
	{
		string name = to_string(i);
		imshow("G" + name, Gauss[i]);
	}
	waitKey(0);
	return 0;
}

拉普拉斯金字塔

 什么是拉普拉斯金字塔?

拉普拉斯金字塔图片icon-default.png?t=N7T8https://img-blog.csdnimg.cn/958dc63453ff42c3855ee8c26f84a918.png

拉普拉斯金字塔是图像金字塔的一种,它保存了下采样图像和原图像之间的差分图像。这样就可以通过低分辨图像和残差恢复出高分辨图像。

为什么要拉普拉斯金字塔 ?

在图像金字塔中,通过高斯金字塔进行下采样,会丢失一些图像的细节信息,这些丢失的信息可以通过拉普拉斯金字塔进行恢复。

怎么样构建拉普拉斯金字塔?
void cv::pyrUp(InputArray src, OutputArray dst, int kernel_size)
  • src:输入图像,必须为8位或32位浮点型。
  • dst:输出图像,类型与输入图像相同。
  • kernel_size:上采样核的大小,必须是奇数。该参数控制上采样过程中插值核的大小。核越大,插值程度越高,图像细节补充越多。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("lena.png");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	vector<Mat> Gauss, Lap;  //高斯金字塔和拉普拉斯金字塔
	int level = 3;  //高斯金字塔下采样次数
	Gauss.push_back(img);  //将原图作为高斯金字塔的第0层
						   //构建高斯金字塔
	for (int i = 0; i < level; i++)
	{
		Mat gauss;
		pyrDown(Gauss[i], gauss);  //下采样
		Gauss.push_back(gauss);
	}
	//构建拉普拉斯金字塔
	for (int i = Gauss.size() - 1; i > 0; i--)
	{
		Mat lap, upGauss;
		if (i == Gauss.size() - 1)  //如果是高斯金字塔中的最上面一层图像
		{
			Mat down;
			pyrDown(Gauss[i], down);  
			pyrUp(down, upGauss);
			lap = Gauss[i] - upGauss;
			Lap.push_back(lap);
		}
		pyrUp(Gauss[i], upGauss);
		lap = Gauss[i - 1] - upGauss;
		Lap.push_back(lap);
	}
	//查看两个金字塔中的图像
	for (int i = 0; i < Gauss.size(); i++)
	{
		string name = to_string(i);
		imshow("L" + name, Lap[i]);
	}
	waitKey(0);
	return 0;
}

opencv基础45-图像金字塔01-高斯金字塔cv2.pyrDown()_小海聊智造的博客-CSDN博客

opencv基础46-图像金字塔02-拉普拉斯金字塔_opencv拉普拉斯金字塔-CSDN博客

 图像直方图

什么是图像直方图?

图像直方图是帮助我们了解图像的亮度特征,例如像素值的范围、像素值的分布等的一种统计工具,用于分析数字图像的亮度分布。具体来说,图像直方图是用来表示数字像素中亮度分布的情况的。横坐标代表像素的灰度级(0-255),纵坐标代表具有该灰度级像素的个数。通过使用图像直方图,我们可以标注图像中像素的亮度。

为什么要图像直方图?

使用图像直方图的原因有多个:

  1. 计算代价较小:图像直方图的计算代价相对较小,这使得它在处理大量图像数据时更为高效。
  2. 具有图像平移、旋转、缩放不变性等众多优点:图像直方图具有平移、旋转和缩放不变性,这意味着无论图像如何变换,直方图的信息都不会改变。这使得直方图成为图像检索和分类等任务中的重要工具。
  3. 应用广泛:图像直方图在图像处理的各个领域都有广泛的应用,例如灰度图像的阈值分割、基于颜色的图像检索以及图像分类等。
  4. 提供图像的统计信息:图像直方图提供了一种统计方法来分析图像的亮度分布和像素值的频率分布,这有助于我们更好地理解图像的内容和特征。
  5. 支持比较不同图像:通过比较不同图像的直方图,我们可以了解它们在亮度分布上的差异,这有助于对图像进行分类、识别和分析。
  6. 帮助进行图像均衡化和增强:通过使用直方图,我们可以对图像进行均衡化和增强,以提高图像的对比度和亮度,改善图像的质量。
  7. 用于图像分割和特征提取:直方图可以帮助我们进行图像分割和特征提取,从而更好地理解和分析图像中的对象和特征。

怎样构建直方图?

void cv::calcHist(const Mat* images, 
                    int nimages, 
                    const int* channels, 
                    InputArray mask,
                    OutputArray hist, 
                    int dims,
                    int* histSize, 
                    const float** ranges, 
                    bool uniform=true, 
                    bool accumulate=false)
  • images:输入的图像数组。
  • nimages:输入图像数组中的图像数量。
  • channels:包含要计算直方图的通道的数组。对于彩色图像,可以传入 01 或 2 来分别计算 B、G 或 R 通道的直方图。
  • mask:可选的掩码,用于确定计算直方图的区域。如果为 noArray(),则计算整个图像的直方图。
  • hist:输出的直方图。如果 hist 是 NULL,函数会自动创建直方图。
  • histSize:直方图数组的大小。通常设置为 256 或 1024
  • ranges:每个通道的取值范围。对于灰度图像,通常设置为 0 和 256。对于彩色图像,通常设置为 0163248(分别对应 B、G、R 通道)。
  • uniform:如果为 true,则使用均匀分布来计算直方图。如果为 false,则使用累积分布来计算直方图。
  • accumulate:如果为 true,则函数会将每个通道的直方图累加到先前计算的直方图上。如果为 false,则每次调用都会重新计算直方图。

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

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	//设置提取直方图的相关变量
	Mat hist;  //用于存放直方图计算结果
	const int channels[1] = { 0 };  //通道索引
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };  //像素灰度值范围
	const int bins[1] = { 256 };  //直方图的维度,其实就是像素灰度值的最大值
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);  //计算图像直方图
																//准备绘制直方图
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 15)),
			Scalar(255, 255, 255), -1);
	}
	namedWindow("histImage", WINDOW_AUTOSIZE);
	imshow("histImage", histImage);
	imshow("gray", gray);
	waitKey(0);
	return 0;
}

进一步操作

直方图归一化
什么是直方图归一化?

直方图归一化是一种图像处理技术,旨在将一幅灰度概率分布已知的图像,生成一幅分布均匀的新图像。具体来说,它把直方图上每个属性的计数除以所有属性的计数之和,得到归一化直方图。归一化直方图的所有属性计数之和为1,即每个属性对应计数都是0到1之间的一个数(百分比)。

为什么要直方图归一化?

更好的查看某个灰度值在所有像素中所占比例

怎样进行直方图归一化?
void cv::normalize(InputArray src,
 OutputArray dst, 
double alpha=127, 
double beta=0, 
int norm_type=NORM_MINMAX, 
InputArray mask=noArray(), 
int dtype=-1)
  • src:输入图像,可以是单通道或多通道的图像。
  • dst:输出图像,即归一化后的图像。
  • alpha:可选参数,指定缩放因子,范围通常为 [0,255]。如果设置为 NORM_MINMAX,则将每个通道的最小值和最大值分别缩放到0和255。如果设置为其他值,则使用该值作为缩放因子。
  • beta:可选参数,用于指定要添加到归一化后的图像中的偏移量。如果设置为 NORM_MINMAX,则将每个通道的最小值减去,然后加上128(对于8位图像)。如果设置为其他值,则使用该值作为偏移量。
  • norm_type:指定归一化的类型。可选的值包括 NORM_MINMAX(将每个通道的最小值和最大值分别缩放到0和255)、NORM_L1(L1范数归一化)、NORM_L2(L2范数归一化)等。
  • mask:可选参数,用于指定掩码。如果设置了该参数,则只对掩码覆盖的区域进行归一化。
  • dtype:可选参数,指定输出数组的深度。如果未指定该参数,则输出数组的深度将与输入数组相同。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改输出界面颜色
	vector<double> positiveData = { 2.0, 8.0, 10.0 };
	vector<double> normalized_L1, normalized_L2, normalized_Inf,
     normalized_L2SQR;
	//测试不同归一化方法
	normalize(positiveData, normalized_L1, 1.0, 0.0,  NORM_L1);  //绝对值求和归一化
	cout << "normalized_L1=[" << normalized_L1[0] << ", "
		<< normalized_L1[1] << ", " 
    << normalized_L1[2] << "]" << endl;
	normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2);  //模长归一化
	cout << "normalized_L2=[" << normalized_L2[0] << ", "
		<< normalized_L2[1] << ", " << normalized_L2[2] 
    << "]" << endl;
	normalize(positiveData, normalized_Inf, 1.0, 0.0,NORM_INF);  //最大值归一化
	cout << "normalized_Inf=[" << normalized_Inf[0] << ", "
		<< normalized_Inf[1] << ", " << normalized_Inf[2] 
    << "]" << endl;
	normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX);  //偏移归一化
	cout << "normalized_MINMAX=[" << normalized_L2SQR[0] << ", "
		<< normalized_L2SQR[1] << ", " << normalized_L2SQR[2] 
    << "]" << endl;
	//将图像直方图归一化
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray, hist;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage_L1 = Mat::zeros(hist_h, hist_w, CV_8UC3);
	Mat histImage_Inf = Mat::zeros(hist_h, hist_w, CV_8UC3);
	Mat hist_L1, hist_Inf;
	normalize(hist, hist_L1, 1, 0, NORM_L1, -1, Mat());
	for (int i = 1; i <= hist_L1.rows; i++)
	{
		rectangle(histImage_L1, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, 
            hist_h - cvRound(30 * hist_h*hist_L1.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());
	for (int i = 1; i <= hist_Inf.rows; i++)
	{
		rectangle(histImage_Inf, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, 
            hist_h - cvRound(hist_h*hist_Inf.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow("histImage_L1", histImage_L1);
	imshow("histImage_Inf", histImage_Inf);
	waitKey(0);
	return 0;
}
直方图比较
什么是直方图比较? 

直方图比较是一种图像处理技术,用于计算和比较两幅图像的直方图数据,以得到它们之间的相似程度。这种比较是通过比较直方图的形状和分布来进行的。

为什么要直方图比较?

直方图比较是一种非常有用的图像处理技术,主要用于比较两幅或多幅图像的直方图数据,以获取它们之间的相似性。其应用场景广泛,包括图像检索、分类、识别等任务。

进行直方图比较的原因有以下几点:

  1. 量化图像的相似性:通过比较直方图,可以量化图像的相似性,从而用于检索、分类和识别等任务。
  2. 反映图像的质量特征:直方图比较可以反映图像的质量特征,包括图像的亮度、对比度和分布情况等。这有助于对图像进行质量评估和改进。
  3. 用于图像配准:在图像配准中,直方图比较可以用于确定两幅图像之间的相似程度,从而帮助确定图像的变换参数。
  4. 辅助图像分割:直方图比较可以辅助图像分割,通过对不同区域或对象的直方图进行比较,有助于更好地分割图像。
  5. 用于目标检测:在一些目标检测算法中,直方图比较可以用于确定目标的存在和位置。
怎样进行直方图比较?
double cv::compareHist(
const Histogram& hist1,
 const Histogram& hist2, 
int compare_type)

函数参数:

  • hist1:表示第一个直方图的 Histogram 对象。
  • hist2:表示第二个直方图的 Histogram 对象。
  • compare_type:表示比较方法的类型

返回值:

  • 返回一个浮点数,表示两个直方图之间的相似度。具体意义取决于比较方法的选择。

注意事项:

  • 输入的直方图必须具有相同的尺寸和类型,否则函数将返回一个无效的比较结果。
  • 比较的结果范围取决于所选择的比较方法,需要根据具体的应用场景选择合适的比较方法。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, 
            hist_h - cvRound(hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函数
int main()
{
	system("color F0");  //更改输出界面颜色
	Mat img = imread("apple.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray, hist, gray2, hist2, gray3, hist3;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	resize(gray, gray2, Size(), 0.5, 0.5);
	gray3 = imread("lena.png", IMREAD_GRAYSCALE);
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	calcHist(&gray2, 1, channels, Mat(), hist2, 1, bins, ranges);
	calcHist(&gray3, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist, NORM_INF, "hist");
	drawHist(hist2, NORM_INF, "hist2");
	drawHist(hist3, NORM_INF, "hist3");
	//原图直方图与原图直方图的相关系数
	double hist_hist = compareHist(hist, hist, HISTCMP_CORREL);
	cout << "apple_apple=" << hist_hist << endl;
	//原图直方图与缩小原图直方图的相关系数
	double hist_hist2 = compareHist(hist, hist2, HISTCMP_CORREL);
	cout << "apple_apple256=" << hist_hist2 << endl;
	//两张不同图像直方图相关系数
	double hist_hist3 = compareHist(hist, hist3, HISTCMP_CORREL);
	cout << "apple_lena=" << hist_hist3 << endl;
	waitKey(0);
	return 0;
}
直方图均衡化
什么是直方图均衡化? 

直方图均衡化是一种图像处理技术,旨在将一幅灰度概率分布已知的图像,通过非线性拉伸,重新分配像素值,使一定灰度范围内的像素数量大致相同,从而增强图像的对比度和清晰度。

为什么要直方图均衡化?

它把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布,使图像的亮度分布更加均匀,提高了图像的对比度和清晰度

怎样进行直方图均衡化?
void cv::equalizeHist(InputArray src, OutputArray dst)
  • InputArray src:这是输入图像,应该是8位单通道的图像。
  • OutputArray dst:这是目标图像,与原图像具有同样的大小与类型。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函数
int main()
{
	Mat img = imread("gearwheel.jpg");
	if (img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray, hist, hist2;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat equalImg;
	equalizeHist(gray, equalImg);  //将图像直方图均衡化
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
	calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
	drawHist(hist, NORM_INF, "hist");
	drawHist(hist2, NORM_INF, "hist2");
	imshow("原图", gray);
	imshow("均衡化后的图像", equalImg);
	waitKey(0);
	return 0;
}

直方图匹配
什么是直方图匹配?

直方图匹配(Histogram Matching)是指对一副图像进行变换,使其直方图与另一幅图像的直方图或特定函数形式的直方图进行匹配的过程。

这个过程通常包括两个步骤:首先,对原始图像的直方图进行归一化处理,使其成为概率密度函数;然后,通过一定的变换,将这个概率密度函数调整为与目标图像的直方图相匹配。

为什么要直方图匹配?

进行直方图匹配的原因主要有以下几点:

  1. 改善图像的对比度和清晰度:直方图均衡化可以增强图像的对比度和清晰度,使得图像的细节更加清晰可见。这对于一些需要高对比度和清晰度的应用场景,如医学图像处理、产品质量检测、遥感图像分析等,非常有用。
  2. 增强图像的动态范围:直方图匹配能够将图像的直方图调整为规定形状的直方图,从而增强图像的动态范围。这对于一些需要处理不同光照条件下的图像或者多波段影像的应用来说,是非常有用的。
  3. 适应不同的光照条件:由于不同的光照条件可能导致图像的直方图发生变化,从而影响图像的质量和视觉效果。通过直方图匹配,可以调整图像的直方图,使其适应不同的光照条件,提高图像的可视性和可用性。
  4. 满足特定需求:在一些特定的应用场景中,可能需要对图像的直方图进行规定形状的调整,以满足特定的需求。比如,在一些医学图像处理中,可能需要将图像的直方图调整为正态分布的形式,以便更好地进行疾病诊断和分析。
 怎么样进行直方图匹配?
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 1, 0, type, -1, Mat());
	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(20 * hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函数
int main()
{
	Mat img1 = imread("histMatch.png");
	Mat img2 = imread("equalLena.png");
	if (img1.empty() || img2.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat hist1, hist2;
	//计算两张图像直方图
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
	calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
	//归一化两张图像的直方图
	drawHist(hist1, NORM_L1, "hist1");
	drawHist(hist2, NORM_L1, "hist2");
	//计算两张图像直方图的累积概率
	float hist1_cdf[256] = { hist1.at<float>(0) };
	float hist2_cdf[256] = { hist2.at<float>(0) };
	for (int i = 1; i < 256; i++)
	{
		hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
		hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);

	}
	//构建累积概率误差矩阵
	float diff_cdf[256][256];
	for (int i = 0; i < 256; i++)
	{
		for (int j = 0; j < 256; j++)
		{
			diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
		}
	}

	//生成LUT映射表
	Mat lut(1, 256, CV_8U);
	for (int i = 0; i < 256; i++)
	{
		// 查找源灰度级为i的映射灰度
		// 和i的累积概率差值最小的规定化灰度
		float min = diff_cdf[i][0];
		int index = 0;
		//寻找累积概率误差矩阵中每一行中的最小值
		for (int j = 1; j < 256; j++)
		{
			if (min > diff_cdf[i][j])
			{
				min = diff_cdf[i][j];
				index = j;
			}
		}
		lut.at<uchar>(i) = (uchar)index;
	}
	Mat result, hist3;
	LUT(img1, lut, result);
	imshow("待匹配图像", img1);
	imshow("匹配的模板图像", img2);
	imshow("直方图匹配结果", result);
	calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist3, NORM_L1, "hist3");  //绘制匹配后的图像直方图
	waitKey(0);
	return 0;
}

直方图反向投影?
什么事直方图反向投影?

直方图反向投影是一种计算机视觉技术,用于对象检测和图像分割。它的原理是首先建立一个描述希望检测的对象的颜色分布的“目标颜色模型”的直方图。然后,将这个目标颜色模型与输入图像进行比较,为输入图像的每个像素分配一个分数,以表示该像素属于目标对象的可能性。得分较高的像素被认为更可能属于目标对象。

为什么要直方图反向投影?

直方图反向投影常用于对目标的跟踪和定位。它的目的是在图像中寻找与目标对象颜色分布相匹配的区域,从而实现目标识别等任务。直方图反向投影具有简单、直观和易于实现的特点,因此在计算机视觉领域得到广泛应用。

怎样进行直方图反向投影?
void cv::calcBackProject ( 
const Mat* images,
 int nimages, 
const int* channels, 
InputArray hist, 
OutputArray backProject, 
const float** ranges, 
double scale = 1, 
bool uniform = true )
  • images:输入图像,图像深度必须为CV_8U、CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数。
  • nimages:输入图像的数量。
  • channels:用于计算反向投影的通道列表,通道数必须与直方图维度相匹配。
  • hist:预先计算的直方图。
  • backProject:计算得到的反投影。
  • ranges:可选参数,用于指定每个通道的取值范围。
  • scale:可选参数,缩放因子。
  • uniform:可选参数,如果为true,则表示直方图是均匀分布的。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	normalize(hist, hist, 255, 0, type, -1, Mat());
	namedWindow(name, WINDOW_NORMAL);
	imshow(name, hist);
}
//主函数
int main()
{
	Mat img = imread("apple.jpg");
	Mat sub_img = imread("sub_apple.jpg");
	Mat img_HSV, sub_HSV, hist, hist2;
	if (img.empty() || sub_img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}

	imshow("img", img);
	imshow("sub_img", sub_img);
	//转成HSV空间,提取S、V两个通道
	cvtColor(img, img_HSV, COLOR_BGR2HSV);
	cvtColor(sub_img, sub_HSV, COLOR_BGR2HSV);
	int h_bins = 32; int s_bins = 32;
	int histSize[] = { h_bins, s_bins };
	//H通道值的范围由0到179
	float h_ranges[] = { 0, 180 };
	//S通道值的范围由0到255
	float s_ranges[] = { 0, 256 };
	const float* ranges[] = { h_ranges, s_ranges };  //每个通道的范围
	int channels[] = { 0, 1 };  //统计的通道索引
								//绘制H-S二维直方图
	calcHist(&sub_HSV, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
	drawHist(hist, NORM_INF, "hist");  //直方图归一化并绘制直方图
	Mat backproj;
	calcBackProject(&img_HSV, 1, channels, hist, backproj, ranges, 1.0);  //直方图反向投影
	imshow("反向投影后结果", backproj);
	waitKey(0);
	return 0;
}

图像卷积

什么是图像卷积?

图像卷积是图像处理中的一种常用技术,主要用于去噪、滤波、边缘提取等。具体来说,图像卷积就是将一个卷积核(又称为滤波器或模板)在图像上滑动,并将卷积核上的数值与图像上对应位置的像素值相乘,然后将所有的乘积相加,得到新的像素值。这个过程会不断重复,直到卷积核滑动完所有的图像。

上图来源:图像卷积操作_图像卷积运算-CSDN博客

为什么要图像卷积?

图像卷积在图像处理中有多种应用,主要包括以下几个方面:

  1. 特征提取:卷积操作可以提取图像中的局部特征,如边缘、纹理等。通过对图像进行卷积,可以得到一组新的特征图,这些特征图能够更好地描述图像的内容。
  2. 降维:在处理高分辨率图像时,由于图像包含大量的像素信息,计算和存储都会变得非常困难。通过卷积操作,可以将高维的图像数据降维到低维空间,从而简化计算和存储。
  3. 去噪:卷积操作可以有效地去除图像中的噪声。当卷积核与噪声像素相匹配时,噪声像素的值会被卷积核中的值替换,从而去除噪声。
  4. 图像增强:通过选择合适的卷积核,可以对图像进行增强。例如,锐化滤波器可以通过增加边缘像素的强度来增强图像的边缘信息。

总的来说,图像卷积是一种重要的信号处理方法,能够模拟生物视觉系统,提取图像特征信息,提升算法性能和数据压缩等。在图像处理、自然语言处理、语音识别等领域都有广泛的应用。 

怎样实现图像卷积?

cv::filter2D是OpenCV库中的一个函数,用于对图像进行线性滤波。该函数使用指定的卷积核对输入图像进行卷积,以计算输出图像中每个像素的值。该函数不会将卷积模版进行旋转,如果卷积模版不是中心对称,需要将卷积模版旋转180°。

函数的原型如下:


	void cv::filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT)

 参数说明:

  • src:输入图像。
  • dst:输出图像,与src具有相同大小和通道数。
  • ddepth:输出图像的深度,可以是任意有效的深度。
  • kernel:卷积核,用于对图像进行滤波。它是一个单通道浮点矩阵。
  • anchor:锚点,指定卷积核的中心位置。默认值为Point(-1,-1),表示锚点位于卷积核的左上角。
  • delta:可选值添加到过滤后的像素,然后将其存储在dst中。
  • borderType:边界填充类型,用于处理图像边界。

使用cv::filter2D函数可以对图像进行平滑、锐化、边缘检测等操作,具体效果取决于所选择的卷积核和参数设置。

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

using namespace cv;
using namespace std;

int main()
{
	//待卷积矩阵
	uchar points[25] = { 1,2,3,4,5,
		6,7,8,9,10,
		11,12,13,14,15,
		16,17,18,19,20,
		21,22,23,24,25 };
	Mat img(5, 5, CV_8UC1, points);
	//卷积模板
	Mat kernel = (Mat_<float>(3, 3) << 1, 2, 1,
		2, 0, 2,
		1, 2, 1);
	Mat kernel_norm = kernel / 12;  //卷积模板归一化
									//未归一化卷积结果和归一化卷积结果
	Mat result, result_norm;
	filter2D(img, result, CV_32F, kernel, Point(-1, -1), 2, BORDER_CONSTANT);
	filter2D(img, result_norm, CV_32F, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
	cout << "result:" << endl << result << endl;
	cout << "result_norm:" << endl << result_norm << endl;
	//图像卷积
	Mat lena = imread("lena.png");
	if (lena.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat lena_fillter;
	filter2D(lena, lena_fillter, -1, kernel_norm, Point(-1, -1), 2, BORDER_CONSTANT);
	imshow("lena_fillter", lena_fillter);
	imshow("lena", lena);
	waitKey(0);
	return 0;
}

噪声

椒盐噪声

椒盐噪声也称为脉冲噪声,是图像中经常见到的一种噪声,它是一种随机出现的白点或者黑点,可能是亮的区域有黑色像素或是在暗的区域有白色像素(或是两者皆有)。椒盐噪声的成因可能是影像讯号受到突如其来的强烈干扰而产生、类比数位转换器或位元传输错误等。例如失效的感应器导致像素值为最小值,饱和的感应器导致像素值为最大值。

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

using namespace cv;
using namespace std;

//盐噪声函数
void saltAndPepper(cv::Mat image, int n)
{
	for (int k = 0; k<n / 2; k++)
	{
		//随机确定图像中位置
		int i, j;
		i = std::rand() % image.cols;  //取余数运算,保证在图像的列数内 
		j = std::rand() % image.rows;  //取余数运算,保证在图像的行数内 
		int write_black = std::rand() % 2;  //判定为白色噪声还是黑色噪声的变量
		if (write_black == 0)  //添加白色噪声
		{
			if (image.type() == CV_8UC1)  //处理灰度图像
			{
				image.at<uchar>(j, i) = 255;  //白色噪声
			}
			else if (image.type() == CV_8UC3)  //处理彩色图像
			{
				image.at<cv::Vec3b>(j, i)[0] = 255; //cv::Vec3b为opencv定义的一个3个值的向量类型  
				image.at<cv::Vec3b>(j, i)[1] = 255; //[]指定通道,B:0,G:1,R:2  
				image.at<cv::Vec3b>(j, i)[2] = 255;
			}
		}
		else  //添加黑色噪声
		{
			if (image.type() == CV_8UC1)
			{
				image.at<uchar>(j, i) = 0;
			}
			else if (image.type() == CV_8UC3)
			{
				image.at<cv::Vec3b>(j, i)[0] = 0; //cv::Vec3b为opencv定义的一个3个值的向量类型  
				image.at<cv::Vec3b>(j, i)[1] = 0; //[]指定通道,B:0,G:1,R:2  
				image.at<cv::Vec3b>(j, i)[2] = 0;
			}
		}

	}
}

int main()
{
	Mat lena = imread("lena.png");
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (lena.empty() || equalLena.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	imshow("lena原图", lena);
	imshow("equalLena原图", equalLena);
	saltAndPepper(lena, 10000);  //彩色图像添加椒盐噪声
	saltAndPepper(equalLena, 10000);  //灰度图像添加椒盐噪声
	imshow("lena添加噪声", lena);
	imshow("equalLena添加噪声", equalLena);
	waitKey(0);
	return 0;
}

高斯噪声

cv::RNG 是 OpenCV 中的一个类,用于生成随机数。这个类提供了多种方法来生成不同类型的随机数。

以下是 cv::RNG 类的一些常用方法:

  1. 随机种子初始化

    • cv::RNG::setSeed(unsigned int seed):设置随机数生成器的种子。相同的种子将产生相同的随机数序列。
  2. 生成随机整数

    • cv::RNG::uniform(int a, int b):生成一个在 [a, b] 范围内的随机整数。
    • cv::RNG::fill(Mat mat, int distType, const Scalar& s, const Rect& area):在给定矩阵的指定区域中填充随机值。distType 参数决定了分布类型。
  3. 生成随机浮点数

    • cv::RNG::gaussian(double sigma, double mean = 0):生成一个均值为 mean、标准差为 sigma 的高斯分布的随机浮点数。
  4. 其他方法

    • cv::RNG::randInt():生成一个随机整数。
    • cv::RNG::randReal():生成一个随机浮点数。
    • cv::RNG::operator():当对象作为函数调用时,返回一个随机数。

高斯噪声是指它的概率密度函数服从高斯分布(即正态分布)的一类噪声。常见的高斯噪声包括起伏噪声、宇宙噪声、热噪声和散粒噪声等等。除常用抑制噪声的方法外,对高斯噪声的抑制方法常常采用数理统计方法。

在数字图像中的高斯噪声的主要来源出现在采集期间。 由于不良照明和/或高温引起的传感器噪声。

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

using namespace cv;
using namespace std;

int main()
{
	Mat lena = imread("lena.png");
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	if (lena.empty() || equalLena.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	//生成与原图像同尺寸、数据类型和通道数的矩阵
	Mat lena_noise = Mat::zeros(lena.rows, lena.cols, lena.type());
	Mat equalLena_noise = Mat::zeros(lena.rows, lena.cols, equalLena.type());
	imshow("lena原图", lena);
	imshow("equalLena原图", equalLena);
	RNG rng;  //创建一个RNG类
	rng.fill(lena_noise, RNG::NORMAL, 10, 20);  //生成三通道的高斯分布随机数
	rng.fill(equalLena_noise, RNG::NORMAL, 15, 30);  //生成三通道的高斯分布随机数
	imshow("三通道高斯噪声", lena_noise);
	imshow("单通道高斯噪声", equalLena_noise);
	lena = lena + lena_noise;  //在彩色图像中添加高斯噪声
	equalLena = equalLena + equalLena_noise;  //在灰度图像中添加高斯噪声
											  //显示添加高斯噪声后的图像
	imshow("lena添加噪声", lena);
	imshow("equalLena添加噪声", equalLena);
	waitKey(0);
	return 0;
}

线性滤波

什么是线性滤波?

线性滤波是一种图像处理技术。线性滤波器包括方框滤波、均值滤波、高斯滤波等。

线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度g(x,y),即g(x,y)=1/m∑f(x,y)m为该模板中包含当前像素在内的像素总个数。

为什么需要线性滤波?

主要在尽量保持图像细节特征的前提下,对图像的噪点进行抑制,消除图像中的噪声成分。

滤波可分为低通滤波和高通滤波。低通滤波主要进行模糊处理,而高通滤波主要进行锐化处理。例如,高斯滤波就是一种线性平滑滤波,可以消除高斯噪声。

怎么样实现线性滤波?

 均值滤波

均值滤波的计算步骤如下:

  1. 确定一个模板,通常是一个小的矩形区域,用于定义像素的邻域。
  2. 将模板在图像上滑动,并将模板中心位置的像素值替换为其邻域像素值的平均值。
  3. 重复步骤2,直到模板覆盖整个图像。
void cv::blur(cv::InputArray src, cv::OutputArray dst,
         cv::Size ksize, cv::Point anchor = cv::Point(-1,-1), 
         int borderType = cv::BORDER_DEFAULT);

参数说明:

  • src:输入图像,可以是多通道的。
  • dst:输出图像,其大小和类型必须与源图像相同。
  • ksize:核的大小,必须是正奇数。
  • anchor:锚点,指定了核的中心位置。默认值(-1,-1)表示锚点位于核的中心。
  • borderType:用于处理图像边界的模式。 
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
	Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
	if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat result_3, result_9;  //存放不含噪声滤波结果,后面数字代表滤波器尺寸
	Mat result_3gauss, result_9gauss;  //存放含有高斯噪声滤波结果,后面数字代表滤波器尺寸
	Mat result_3salt, result_9salt;  //存放含有椒盐噪声滤波结果,后面数字代表滤波器尺寸
	//调用均值滤波函数blur()进行滤波
	blur(equalLena, result_3, Size(3, 3));
	blur(equalLena, result_9, Size(9, 9));
	blur(equalLena_gauss, result_3gauss, Size(3, 3));
	blur(equalLena_gauss, result_9gauss, Size(9, 9));
	blur(equalLena_salt, result_3salt, Size(3, 3));
	blur(equalLena_salt, result_9salt, Size(9, 9));
	//显示不含噪声图像
	imshow("equalLena ", equalLena);
	imshow("result_3", result_3);
	imshow("result_9", result_9);
	//显示含有高斯噪声图像
	imshow("equalLena_gauss", equalLena_gauss);
	imshow("result_3gauss", result_3gauss);
	imshow("result_9gauss", result_9gauss);
	//显示含有椒盐噪声图像
	imshow("equalLena_salt", equalLena_salt);
	imshow("result_3salt", result_3salt);
	imshow("result_9salt", result_9salt);
	waitKey(0);
	return 0;
}
  方框滤波

   方框滤波和均值滤波核基本上是一致的,主要的区别是要不要归一化处理,如果使用归一化处理,方框滤波就是均值滤波。

在方框滤波中,将滤波器中所有的像素值求和后的平均值作为滤波后结果。与均值滤波不同的是,方框滤波可以选择不进行归一化,即将所有像素值的和作为滤波结果,而不是所有像素值的平均值。

void cv::boxFilter(cv::InputArray src, 
        cv::OutputArray dst, 
        int ddepth,
        cv::Size ksize, 
        cv::Point anchor = cv::Point(-1,-1), 
        bool normalize = true, 
        int borderType = cv::BORDER_DEFAULT);

参数说明:

  • src:输入图像。
  • dst:输出图像,其大小和类型必须与源图像相同。
  • ddepth:输出图像的深度,如果为负数,则使用源图像的深度。
  • ksize:核的大小。
  • anchor:锚点,指定了核的中心位置。默认值(-1,-1)表示锚点位于核的中心。
  • normalize:指定是否按核的区域进行规范化。如果为true,则按核的面积进行规范化;如果为false,则不进行规范化。
  • borderType:边界模式,用于处理图像边界。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);  //用于方框滤波的图像
	if (equalLena.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	//验证方框滤波算法的数据矩阵
	float points[25] = { 1,2,3,4,5,
		6,7,8,9,10,
		11,12,13,14,15,
		16,17,18,19,20,
		21,22,23,24,25 };
	Mat data(5, 5, CV_32FC1, points);
	//将CV_8U类型转换成CV_32F类型
	Mat equalLena_32F;
	equalLena.convertTo(equalLena_32F, CV_32F, 1.0 / 255);
	Mat resultNorm, result, dataSqrNorm, dataSqr, equalLena_32FSqr;
	//方框滤波boxFilter()和sqrBoxFilter()
	boxFilter(equalLena, resultNorm, -1, Size(3, 3), Point(-1, -1), true);  //进行归一化
	boxFilter(equalLena, result, -1, Size(3, 3), Point(-1, -1), false);  //不进行归一化
	sqrBoxFilter(data, dataSqrNorm, -1, Size(3, 3), Point(-1, -1),
		true, BORDER_CONSTANT);  //进行归一化
	sqrBoxFilter(data, dataSqr, -1, Size(3, 3), Point(-1, -1),
		false, BORDER_CONSTANT);  //不进行归一化
	sqrBoxFilter(equalLena_32F, equalLena_32FSqr, -1, Size(3, 3), Point(-1, -1),
		true, BORDER_CONSTANT);
	//显示处理结果
	imshow("resultNorm", resultNorm);
	imshow("result", result);
	imshow("equalLena_32FSqr", equalLena_32FSqr);
	waitKey(0);
	return 0;
}
高斯滤波

高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到

高斯滤波的计算步骤如下:

  1. 确定高斯核的大小,通常为奇数,如3x3、5x5等。
  2. 生成高斯核,高斯核的每个元素值可以通过高斯函数计算得到。高斯函数的值取决于像素的坐标和标准差。
  3. 将高斯核与图像进行卷积,即将高斯核的每个元素与图像中对应位置的像素值相乘,然后将所有乘积相加得到最终结果就是当前中心点的高斯滤波结果
  4. 重复步骤3,直到处理完整个图像。
void cv::GaussianBlur(
    InputArray src, 
    OutputArray dst, 
    Size ksize, 
    double sigmaX, 
    double sigmaY=0, 
    int borderType=BORDER_DEFAULT
)
  • src:输入图像,可以是单通道或多通道。
  • dst:输出图像,其大小和类型与输入图像相同。
  • ksize:高斯核的大小,必须是奇数。
  • sigmaX:X方向的标准差。如果为 0,则函数会根据核大小计算标准差。
  • sigmaY:Y方向的标准差。如果为 0,则与 sigmaX 相同。
  • borderType:像素外推的类型。
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat equalLena = imread("equalLena.png", IMREAD_ANYDEPTH);
	Mat equalLena_gauss = imread("equalLena_gauss.png", IMREAD_ANYDEPTH);
	Mat equalLena_salt = imread("equalLena_salt.png", IMREAD_ANYDEPTH);
	if (equalLena.empty() || equalLena_gauss.empty() || equalLena_salt.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat result_5, result_9;  //存放不含噪声滤波结果,后面数字代表滤波器尺寸
	Mat result_5gauss, result_9gauss;  //存放含有高斯噪声滤波结果,后面数字代表滤波器尺寸
	Mat result_5salt, result_9salt;  存放含有椒盐噪声滤波结果,后面数字代表滤波器尺寸
	 //调用均值滤波函数blur()进行滤波
	GaussianBlur(equalLena, result_5, Size(5, 5), 10, 20);
	GaussianBlur(equalLena, result_9, Size(9, 9), 10, 20);
	GaussianBlur(equalLena_gauss, result_5gauss, Size(5, 5), 10, 20);
	GaussianBlur(equalLena_gauss, result_9gauss, Size(9, 9), 10, 20);
	GaussianBlur(equalLena_salt, result_5salt, Size(5, 5), 10, 20);
	GaussianBlur(equalLena_salt, result_9salt, Size(9, 9), 10, 20);
	//显示不含噪声图像
	imshow("equalLena ", equalLena);
	imshow("result_5", result_5);
	imshow("result_9", result_9);
	//显示含有高斯噪声图像
	imshow("equalLena_gauss", equalLena_gauss);
	imshow("result_5gauss", result_5gauss);
	imshow("result_9gauss", result_9gauss);
	//显示含有椒盐噪声图像
	imshow("equalLena_salt", equalLena_salt);
	imshow("result_5salt", result_5salt);
	imshow("result_9salt", result_9salt);
	waitKey(0);
	return 0;
}
可分离滤波
void cv::sepFilter2D(
    InputArray src, 
    OutputArray dst, 
    int ddepth, 
    InputArray kernelX, 
    InputArray kernelY, 
    Point anchor=Point(-1,-1), 
    double delta=0, 
    int borderType=BORDER_DEFAULT)
  • src:输入图像,需要进行滤波处理的原始图像。
  • dst:输出图像,滤波处理后的结果图像。
  • ddepth:输出图像的深度,通常设置为与输入图像相同的深度。
  • kernelX:X方向的滤波器,用于在水平方向上对图像进行滤波。
  • kernelY:Y方向的滤波器,用于在垂直方向上对图像进行滤波。
  • anchor:锚点,指定滤波器的中心位置。默认值为 Point(-1,-1),表示锚点位于滤波器的中心。
  • delta:添加到输出图像的常量值。默认值为0,表示不添加任何常量值。
  • borderType:像素外推的类型,用于处理边界像素。默认值为 BORDER_DEFAULT

非线性滤波

中值滤波

什么是中值滤波?

中值滤波是一种非线性平滑技术,将每个像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。

为什么需要中值滤波?

这种方法基于排序统计理论,能有效抑制噪声。

中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近真实值,从而消除孤立的噪声点。

具体操作过程中,会使用某种结构的二维滑动模板,将板内像素按照像素值的大小进行排序,生成单调上升(或下降)的二维数据序列。

中值滤波对于滤除脉冲干扰及图像扫描噪声最为有效,还可以克服线性滤波器(如领域简单平滑滤波)带来的图像细节模糊。

如何实现中值滤波?
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat gray = imread("equalLena_salt.png", IMREAD_ANYCOLOR);
	Mat img = imread("lena_salt.png", IMREAD_ANYCOLOR);
	if (gray.empty() || img.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat imgResult3, grayResult3, imgResult9, grayResult9;
	//分别对含有椒盐噪声的彩色和灰度图像进行滤波,滤波模板为3×3
	medianBlur(img, imgResult3, 3);
	medianBlur(gray, grayResult3, 3);
	//加大滤波模板,图像滤波结果会变模糊
	medianBlur(img, imgResult9, 9);
	medianBlur(gray, grayResult9, 9);
	//显示滤波处理结果
	imshow("img", img);
	imshow("gray", gray);
	imshow("imgResult3", imgResult3);
	imshow("grayResult3", grayResult3);
	imshow("imgResult9", imgResult9);
	imshow("grayResult9", grayResult9);
	waitKey(0);
	return 0;
}

双边滤波

什么是双边滤波?

双边滤波是一种非线性的滤波方法,结合了图像的空间邻近度和像素值相似度进行折衷处理,同时考虑空域信息和灰度相似性,以达到保边去噪的目的。双边滤波器能够做边缘保存,一般过去用的维纳滤波或者高斯滤波去降噪,会较明显地模糊边缘,对于高频细节的保护效果并不明显。

双边滤波器比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会对边缘上的像素值影响太多,这样就保证了边缘附近像素值的保存。

但是,由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净地滤掉,只能对低频信息进行较好地滤波。在双边滤波中,输出像素的值依赖于邻域像素值的加权值组合。正是由于这些加权值的作用,使得边缘,即相距近但差异大的像素点的特性得以保留。

为什么需要双边滤波?

需要双边滤波的原因在于其能够同时达到降噪平滑并保持边缘的效果。传统的均值滤波、高斯滤波等只考虑空域,认为临近像素应该拥有相近的特性,这种假设在图像中的边缘处则不满足,如果仍用这种方式滤波则必定会导致边缘被模糊掉。为了达到保边效果,双边滤波通过结合空域和值域,在保证滤波效果的同时保持了边缘。

此外,双边滤波具有简单、非迭代、局部的特点。由于其是基于空间分布的高斯滤波函数,因此在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样可以保证边缘附近像素值的保存。

 如何实现双边滤波?
#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	//读取两张含有人脸的图像
	Mat img1 = imread("img1.png", IMREAD_ANYCOLOR);
	Mat img2 = imread("img2.png", IMREAD_ANYCOLOR);
	if (img1.empty() || img2.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat result1, result2, result3, result4;
	//验证不同滤波器直径的滤波效果
	bilateralFilter(img1, result1, 9, 50, 25 / 2);
	bilateralFilter(img1, result2, 25, 50, 25 / 2);
	//验证不同标准差值的滤波效果
	bilateralFilter(img2, result3, 9, 9, 9);
	bilateralFilter(img2, result4, 9, 200, 200);
	//显示原图
	imshow("img1", img1);
	imshow("img2", img2);
	//不同直径滤波结果
	imshow("result1", result1);
	imshow("result2", result2);
	//不同标准差值滤波结果
	imshow("result3 ", result3);
	imshow("result4", result4);
	waitKey(0);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值