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年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,利于后续的图像分opencv4笔记_第1张图片,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小

三角法

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

图像处理之三角法图像二值化_三角阈值法-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 
#include 
#include 

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 
#include 

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 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 
#include 

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 
#include 
#include 

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 
#include 

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 
#include 

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 
#include 

using namespace cv;
using namespace std;

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

	vector 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 
#include 

using namespace cv;
using namespace std;

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

	vector 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 
#include 

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(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 
#include 

using namespace cv;
using namespace std;

int main()
{
	system("color F0");  //更改输出界面颜色
	vector positiveData = { 2.0, 8.0, 10.0 };
	vector 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(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(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 
#include 

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(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 
#include 

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(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 
#include 

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(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(0) };
	float hist2_cdf[256] = { hist2.at(0) };
	for (int i = 1; i < 256; i++)
	{
		hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at(i);
		hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at(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(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 
#include 

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;
}

你可能感兴趣的:(图像处理笔记,笔记,计算机视觉,人工智能)