掌握imgproc组件:opencv-图像变换

news2024/9/21 11:09:33

图像变换

  • 1. 基于OpenCV的边缘检测
    • 1.1 边缘检测的一般步骤
    • 1.2 canny算子
      • 1.2.1 Canny边缘检测步骤:
      • 1.2.2 Canny边缘检测:Canny()函数
      • 1.2.3 Canny边缘检测案例
    • 1.3 sobel算子
      • 1.3.1 sobel算子的计算过程
      • 1.3.2 使用Sobel算子:Sobel()函数
      • 1.3.3 示例程序:Sobel算子的使用
    • 1.4 Laplacian算子
      • 1.4.1 Laplacian算子简介
      • 1.4.2 计算拉普拉斯变换:Laplacian()函数
      • 1.4.3 示例程序: Laplacian 算子的使用
    • 1.5 scharr滤波器
  • 2.霍夫变换
    • 2.1 霍夫变换概述
    • 2.2 霍夫线变换
    • 2.3 霍夫线变换的原理
    • 2.4 标准霍夫变换:HoughLines()函数
    • 2.5 累计概率霍夫变换:HoughLinesP()函数
    • 2.6 霍夫圆变换
      • 2.6.1 霍夫梯度法的原理
      • 2.6.2 霍夫梯度法的缺点
      • 2.6.3 霍夫圆变换:HoughCircles()函数
  • 3. 重映射
    • 3.1 重映射的概念
    • 3.2 实现重映射:remap()函数
  • 4.仿射变换
    • 4.1 认识仿射变换
    • 4.2 仿射变换的求法
    • 4.3 进行仿射变换:warpAffine()函数
    • 4.4 计算二维旋转变换矩阵:getRotationMatrix2D()函数
    • 4.3 案例
  • 5. 直方图均衡化
    • 5.1 直方图均衡化的概念和特点
    • 5.2 实现直方图均衡化:equalizeHist()函数

1. 基于OpenCV的边缘检测

1.1 边缘检测的一般步骤

第一步:滤波
边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波,即采用离散化的高斯函数产生一组归一化的高斯核,然后基于高斯核函数对图像灰度矩阵的每一点进行加权求和。
第二步:增强
增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。在具体编程实现时,可通过计算题都幅值来确定。
第三步:检测
经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些点并不是要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用的方法是通过阈值化方法来检测。

1.2 canny算子

最优化边缘检测的三个主要评价标准。

  • 低错误率:表示出尽可能多的实际边缘,同时尽可能地减少噪声产生的误报。
  • 高定位率:标识出的边缘要与图像中的实际边缘尽可能接近。
  • 最小响应:图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。

1.2.1 Canny边缘检测步骤:

  1. 消除噪声
    一般情况下,使用高斯平滑滤波器卷积降噪。以下显示了一个size=5的高斯内核示例:
    在这里插入图片描述

  2. 计算梯度幅值和方向
    此处,按照Sobel滤波器的步骤来操作。

    1. 运用一对卷积阵列(分别作用于x和y方向)
      在这里插入图片描述
    2. 使用下列公式计算梯度幅值和方向
      在这里插入图片描述
      而梯度方向一般取这4个可能的角度之一——0、45、90、135度。
  3. 非极大值抑制
    这将删除那些不被认为是边缘的一部分的像素。因此,将只保留细线(候选边缘)。

  4. 滞后阈值
    这是最后一步,Canny使用了滞后阈值,滞后阈值需要两个阈值(高阈值和低阈值):

    • 若某一像素位置的幅值超过高阈值,该像素被保留为边缘像素。
    • 若某一像素位置的幅值小于地阈值,该像素被排除。
    • 若某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。
      Canny建议上下限的比例在2:1和3:1之间。

1.2.2 Canny边缘检测:Canny()函数

在这里插入图片描述

  • 第一个参数:输入图像
  • 第二个参数:输出的边缘图
  • 第三个参数:第一个滞后性的阈值
  • 第四个参数:第二个滞后性的阈值
  • 第五个参数:int类型的apertureSize,表示应用Sobel算子的孔径大小,其有默认值3
  • 第六个参数:一个标志,表示是否应该使用更精确的L2准则来计算图像梯度大小(L2gradient=true),或者默认的L1准则(L2gradient=false)。
    需要注意的是,这个函数1阈值和阈值2两者中较小的值用于边缘连接,而较大值用来控制强边缘的初始段,推荐的高低阔值比在 2:1 到3:1之间。

1.2.3 Canny边缘检测案例

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

int main() {
	Mat src = imread("../../image/1.tif");
	Mat src1 = src.clone();

	imshow("Canny边缘检测", src);

	/*
		1.转换成灰度图
		2.降噪
		3.canny
		4.将得到的边缘作为掩码拷贝原图到效果图上,得到彩色的边缘图
	*/

	Mat dst, edge, gray;

	//1.创建与src同类型和大小的矩阵dst
	dst.create(src1.size(), src1.type());
	
	//2.将原图转化为灰度图
	cvtColor(src1, gray, COLOR_RGB2GRAY);

	//3.使用3*3内核来降噪
	blur(gray, edge, Size(3, 3));

	//4.运算Canny算子
	Canny(edge, edge, 3, 9, 3);

	//5.将dst内的所有元素设置为0
	dst = Scalar::all(0);

	src1.copyTo(dst, edge);
	
	imshow("效果图", dst);
	waitKey();
	return 0;
}

在这里插入图片描述

1.3 sobel算子

Sobel 算子是一个主要用于边缘检测的离散微分算子。它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任何一点使用此算子,都将会产生对应的梯度矢量或是其法矢量。
在这里插入图片描述
你可以很容易地注意到,在一个边缘,像素强度的变化是很明显的。一个表达变化的好方法是使用导数。梯度的高变化表示图像中的一个重大变化。

1.3.1 sobel算子的计算过程

我们假设被作用的图像为 I ,然后进行如下操作

  1. 分别在x和y两个方向求导
    • 水平变化:将 I 与一个奇数大小的内核Gx进行卷积。比如,当内核大小为3时,Gx的计算结果为
      在这里插入图片描述
    • 垂直变化:将 I 与一个奇数大小的内核Gy进行卷积。比如,当内核大小为3是,Gy的计算结果为
      -
  2. 在图像的每一点,结合以上两个结果求出近似梯度:
    在这里插入图片描述
    有时也可用下面更简单的公式代替
    在这里插入图片描述

1.3.2 使用Sobel算子:Sobel()函数

在这里插入图片描述

  • 第一个参数:输入图像

  • 第二个参数:目标图像

  • 第三个参数:int类型ddepth,输出图像的深度,支持如下src.depth()和ddepth的组合
    在这里插入图片描述

  • 第四个参数:是否计算x方向上的导数标志

  • 第五个参数:是否计算y方向上的导数标志

  • 第六个参数:Sobel核的大小;它必须是1、3、5或7。

  • 第七个参数:double类型的scale,计算导数时可选的缩放因子,默认为1,表示默认情况下是没有应用缩放的。

  • 第八个参数:可选的delta值,在将结果存储到dst之前,将其添加到结果中。
    一般情况下,都是用ksize*ksize内核来计算导数的。然而有一个特殊的情况,当ksize为1时,往往会使用3*1或1*3的内核。且这种情况下,并没有进行高斯平滑操作。

  • 当内核大小为3时,Sobel内核可能产生表较明显的误差(毕竟,Sobel算子只是求取了导数的近似值而已)。为解决这一问题,OpenCV提供了Scharr函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果更加精确,其内核是这样的在这里插入图片描述

  • 因为Sobel算子结合了高斯平滑和分化,因此结果会具有更多的抗噪性。大多数情况下,使用( xorder = 1, yorder = 0, ksize = 3) 计算图像X方向的导数, ( xorder = 0, yorder = 1, ksize = 3)计算Y方向的导数。

1.3.3 示例程序:Sobel算子的使用

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

int main() {

	//创建grad_x和grad_y矩阵
	Mat grad_x, grad_y;
	Mat abs_grad_x, abs_grad_y, dst;

	Mat src = imread("../../image/1.tif");


	namedWindow("原图", 0);
	namedWindow("X方向的Sobel", 0);
	namedWindow("Y方向的Sobel", 0);
	namedWindow("整体方向Sobel", 0);
	imshow("原图", src);

	//求x方向的梯度
	Sobel(src, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
	/*
		convertScaleAbs (
				InputArray src, 
				OutputArray dst, 
				double alpha = 1.0, //alpha 为乘数因子
				double beta = 0.0 //beta为偏移量 
				);
				它的原理/用法为:
				(1)若像素值-255 < src * alpha + beta < 255,取绝对值;
				(2)若像素值 src * alpha + beta >= 255 || src * alpha + beta <= -255,取255,不可溢出。
	*/
	
	convertScaleAbs(grad_x, abs_grad_x);
	imshow("X方向的Sobel", abs_grad_x);

	//计算Y方向的梯度
	Sobel(src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
	convertScaleAbs(grad_y, abs_grad_y);
	imshow("Y方向的Sobel", abs_grad_y);

	//近似梯度
	addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5,0,dst);
	imshow("整体方向Sobel", dst);
	waitKey();
	return 0;
}

在这里插入图片描述

1.4 Laplacian算子

1.4.1 Laplacian算子简介

在这里插入图片描述

1.4.2 计算拉普拉斯变换:Laplacian()函数

在这里插入图片描述

  • 第一个参数:输入图像
  • 第二个图像:输出的边缘图
  • 第三个参数:int类型的ddept,目标图像的深度
  • 第四个参数:int类型的ksize,用于计算二阶导数的滤波器孔径尺寸,大小必须为正奇数
  • 第五个参数:double类型的scale,计算拉普拉斯值的时候可选的比例因子,有默认值1
  • 第六个参数:double类型的delta,表示在结果存入目标图之前可选地delta值

Laplacian() 函数其实主要是利用 Sobel算子的运算。它通过加上 Sobel算子运算出的图像x方向和y方向上的导数 来得到载入图像的拉普拉斯变换结果。
在这里插入图片描述
当ksize=1时,拉普拉斯的计算方法是用以下3×3的孔径对图像进行过滤:
在这里插入图片描述

1.4.3 示例程序: Laplacian 算子的使用

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

int main() {
	Mat src, src_gray, dst, abs_dst;

	src = imread("../../image/1.tif");
	imshow("原图", src);

	//使用高斯滤波消除噪声
	GaussianBlur(src, src, Size(3, 3), 0, 0);

	cvtColor(src, src_gray, COLOR_RGB2GRAY);

	Laplacian(src_gray, dst, CV_16S, 3, 1, 0);

	convertScaleAbs(dst, abs_dst);
	
	imshow("Laplace变换", abs_dst);
	waitKey();
	return 0;
}

在这里插入图片描述

1.5 scharr滤波器

我们一般直接称scharr为滤波器,而不是算子 。上文已经到,它在 OpenCV中主要是配合 Sobel 算子的运算而存在的。

在这里插入图片描述
它的参与与Sobel类似,只不过滤波器尺寸有固定尺寸和固定值。

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

int main() {
	Mat grad_x, grad_y;
	Mat src, abs_grad_x, abs_grad_y,dst;

	
	src = imread("../../image/lena_color_256.tif");
	imshow("原图", src);

	Scharr(src, grad_x, CV_16S, 1, 0, 1, 1);
	convertScaleAbs(grad_x, abs_grad_x);
	imshow("Scharr在x方向", abs_grad_x);

	Scharr(src, grad_x, CV_16S, 0, 1, 1, 1);
	convertScaleAbs(grad_x, abs_grad_y);
	imshow("Scharr在y方向", abs_grad_y);

	addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
	imshow("梯度近似", dst);

	waitKey();
	return 0;
}

在这里插入图片描述

2.霍夫变换

在图像处理和 计算机视觉领域中,如何从当前的图像种提取所需要的特征信息是图像识别的关键所在。在许多应用场什中需要快速准确地检测出直线或者圆。
其中一种非常有效的解决问题的方法是霍夫变换,其为图像处理中从图像中识别几何形状的基本方法之一 ,应川很广泛,也有很多改进算法。最基本的霍夫变换是从黑白图像中检测直线 (线段)。

2.1 霍夫变换概述

霍夫变化是图像处理中的一种特征提取技术,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的几何作为霍夫变换结果。最初的霍夫变换是设计用来检测直线和曲线的。起初的方法要求知道物体边界线的解析方程,但不需要有关区域位置的先验知识。这种方法的一个突出优点是分割结果的Robustness,即对数据的不完全或噪声不是非常敏感。然后,要获得描述边界的解析表达常常是不可能的。后于1972 年由 Rich rd Duda & Peter Hart 推广使用,经典霍夫变换用来检测图像中的直线,后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆。霍夫变换运用两个坐标空间之间的变换将一个空间中具有相同性状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化成统计峰值的问题。

2.2 霍夫线变换

霍夫线变换是一种用来寻找直线的方法,在使用霍夫线性变换之前,首先要对图像进行边缘检测的处理,即霍夫线变化的直接输入只能是边缘二值图像。
OpenCV支持三种不同的霍夫线变换,他们分别是:标准霍夫变换、多尺度霍夫变化、累计概率霍夫变换。
其中多尺度霍夫变换为经典霍夫变换在多尺度下的一种变种。而累计概率霍夫变换算法是标准霍夫变换算法的一个改进,他在一定的范围内进行霍夫变换,计算单独线段的方法以及范围,从而减少计算量,缩短计算时间。之所以成为“概率”的,是因为并不讲累加器平面内的所有可能的点累加,而只是累加其中一部分,该想法是如果峰值足够高,只是一小部分时间去寻找它就够了。按照猜想,可以实质性的减少计算时间。
在OpenCV中,可以用HoughLines函数来调用标准霍夫变换和多尺度霍夫变换。
而HoughLinesP函数用于调用累计概率霍夫变换。累计概率霍夫变换执行效率很高,所以相比于HoughLines函数,我们更倾向于使用HoughLinesP函数。

2.3 霍夫线变换的原理

直线在图像二维空间可由两个变量表示,有以下两种情况。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 标准霍夫变换:HoughLines()函数

此函数可以找出采用标准霍夫变换的二值图像线条。
在这里插入图片描述

  • 第一个参数: 输入图像
  • 第二个参数:输出线条的向量。每条线由一个2或3元素的向量(ρ,θ)或(ρ,θ,votes)表示,其中ρ是与坐标原点(0,0)(图像的左上角)的距离,θ是线的旋转角度,单位是弧度(0∼垂直线,π/2∼水平线),votes是累积器的值。
  • 第三个参数:以像素为单位的距离精度。另一种表述方法是直接搜索时的进步尺寸的单位半径。
  • 第四个参数:double类型的theta,以弧度为单位的角度精度。另一种表述方式是直线搜索时的进步尺寸的单位角度。
  • 第五个参数:累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
  • 第六个参数:double类型的srn,有默认值0。对于对尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确地累加器进步尺寸为rho/srn。
  • 第七个参数:double类型的stn,有默认值0,对于对尺度霍夫变换,srn表示第四个参数进步尺寸的单位叫角度theta的除数距离。且如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。
  • 第八个参数:对于标准和多尺度Hough变换,检查线条的最小角度。必须落在0和max_theta之间。
  • 第九个参数:对于标准和多尺度Hough变换,是一个角度的上界。
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
	Mat dst, cdst, tmp;
	Mat src = imread("1.jpg");

	//边缘检测
	Canny(src, dst, 50, 200, 3);
	imshow("边缘检测后", dst);
	//将边缘复制到将在BGR中显示结果的图像上
	cvtColor(dst, cdst, COLOR_GRAY2BGR);


	//标准Hough线变换
	vector<Vec2f> lines; //将保存检测的结果
	HoughLines(dst, lines, 1, CV_PI / 180, 150, 0, 0); //运行实际的检测

	//画线
	for (size_t i = 0; i < lines.size(); i++) {
		float rho = lines[i][0], theta = lines[i][1];
		Point pt1, pt2;
		double a = cos(theta), b = sin(theta);
		double x0 = a * rho, y0 = b*rho;
		pt1.x = cvRound(x0 + 1000 * (-b));
		pt1.y = cvRound(y0 + 1000 * (a));
		pt2.x = cvRound(x0 - 1000 * (-b));
		pt2.y = cvRound(y0 - 1000 * (a));
		line(cdst, pt1, pt2, Scalar(0, 0, 255), 1, LINE_AA);
	}

	imshow("原图", src);
	imshow("效果图", cdst);

	waitKey();
	return 0;

}

在这里插入图片描述

2.5 累计概率霍夫变换:HoughLinesP()函数

在这里插入图片描述

  • 第一个参数:输入图像
  • 第二个参数:输出线的向量。每条线由一个4元素的向量(x1,y1,x2,y2)表示,其中(x1,y1)和(x2,y2)是每个检测到的线段的结束点。
  • 第三个参数:以像素为单位的距离精度
  • 第四个参数:以弧度为单位的角度精度
  • 第五个参数:累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
  • 第六个参数:double类型的minLineLength,有默认值0,表示最低段的长度,比这个设定参数端的线段就不能被显现出来。
  • 第七个参数:double类型的maxLineGap,有默认值0,允许将同一行点与点之间连接起来的最大距离。
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
	Mat src = imread("1.jpg");
	Mat dst, cdstP;

	Canny(src, dst, 50, 200, 3);
	imshow("轮廓图", dst);
	cvtColor(dst, cdstP, COLOR_GRAY2BGR);

	vector<Vec4i> linesP;
	HoughLinesP(dst, linesP, 1, CV_PI / 180, 50, 50, 10);
	for (size_t i = 0; i < linesP.size(); i++) {
		Vec4i l = linesP[i];
		line(cdstP, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 1, LINE_AA);
	}
	imshow("原图", src);
	imshow("效果图", cdstP);

	waitKey();
	return 0;
}

在这里插入图片描述

2.6 霍夫圆变换

霍夫圆变换的基本原理和上面讲的霍夫线性变化大体上是很类似,只是点对应的二维极径极角空间被三维的点心x、y和半径r空间取代。说“大体上类似”的原因是,如果完全相同的方法的话,累加平面会被三维的累加容器所代替——在这三维中,一维是x,一维是y,另外一维是圆的半径r。这就意味着需要大量的内存而且执行效率会很低,速度会很慢。
对直线来说,一条直线能由参数极径极角(r,θ)表示,而对圆来说需要三个参数表示:(xcenter,ycenter,r)
其中(xcenter,ycenter)定义了中心位置(绿点),r是半径,这使得我们可以完全定义一个圆,如下图所示:
在这里插入图片描述
在OpenCV中,我们常常通过一个叫做“霍夫梯度法”的方法来解决圆变换的问题。

2.6.1 霍夫梯度法的原理

  1. 首先对图像应用边缘检测,比如用canny边缘检测
  2. 然后,对边缘图像中的每一个非零点,考虑其局部梯度,即用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度
  3. 利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。
  4. 同时,标记边缘图像中每一个非0像素的位置
  5. 然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照类价值将序排序,以便于最支持像素的中心首先出现。
  6. 接下来地每一个中心,考虑所有的非零像素。
  7. 这些像素按照其与中心距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径
  8. 如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的的距离,那么它就会被保留下来。

这个实现可以使算法执行起来更高效 ,或许更加重要的是,能够帮助解决三维累加器中会产生许多噪声并且使得结果不稳定的稀疏分布问题。

2.6.2 霍夫梯度法的缺点

  1. 在霍夫梯度法中,我们使用Sobel 导数来计算局部梯度,那么随之而来的假设是,它可以视作等同于一条局部切线,这并不是一个数值稳定的做法。在大多数情况下,这样做会得到正确的结果,但或许会在输出 产生一些噪声。
  2. 在边缘图像中的整个非0像素集被看做每个中心的候选部分。因此,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间。此外,因为每一个中心只选择一个圆,如果有同心圆,就只能选择其中的一个。
  3. 因为中心是按照其关联的累加器值的升序排列的,并且如果新的中心过于接近之前已经接受的中心的话,霍夫梯度法的倾向是保留最大的一个圆。可以说这是一种比较极端的做法,因为在这里默认Sobel导数会产生噪声,入市对于无穷分别率的平滑图像而言的话,这才是必须的。

2.6.3 霍夫圆变换:HoughCircles()函数

HoughCircle函数可以利用霍夫变换算法检测出灰度图中的圆。它相比之前的eHoughLines和HoughLinesP,比较明显的一个区别是不需要源图是二值的,而HoughLines和HoughLinesP都需要源图为二值图像。
在这里插入图片描述

  • 第一个参数:源图像,需为8位的灰度单通道图像
  • 第二个参数:OutputArray 类型 circles 经过调用 HoughCircles 函数后此数存储了检测到的圆的输出矢量,每个矢量由包含了3个元素的浮点矢量(x,y,radius)表示。
  • 第三个参数:检测方法,可用的方法是HOUGH_GRADIENT和HOUGH_GRADIENT_ALT
  • 第四个参数:累加器分辨率与图像分辨率的反比。例如,如果 dp=1 ,累积器的分辨率与输入图像相同。如果 dp=2 ,累加器的宽度和高度都是源图的一半。对于HOUGH_GRADIENT_ALT,推荐的值是dp=1.5,除非需要检测一些非常小的圆圈。
  • 第五个参数:double类型的minDist,为霍夫变换检测到的圆的圆心之间的最小距离,即让算法能明显区分的两个不同圆之间的最小距离。这个参数如果太小的话,多个相邻的圆可能会被错误地检测成了一个重合的圆。反之,这个参数设置太大,某些圆就不能被检测出来。
  • 第六个参数:double类型的param1,有默认值100。它是第三个参数metho参数设置的检测方法的对应的参数。对霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半。
  • 第七个参数: 第二个方法特有的参数。在HOUGH_GRADIENT的情况下,它是检测阶段的圆心累积阈值。它越小,可能会检测到更多的错误圆圈。对应于较大的累加器值的圆将首先被返回。在HOUGH_GRADIENT_ALT算法的情况下,这是圆的 "完美性 "测量。它越接近1,算法选择的圆的形状就越好。在大多数情况下,0.9应该是不错的。如果你想获得更好的小圆圈检测,你可以把它降低到0.85、0.8甚至更低。但也要尽量限制搜索范围[minRadius, maxRadius],以避免许多假圆。
  • 第八个参数:圆的最小半径
  • 第九个参数:圆的最大半径
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
	Mat srcImage = imread("1.jpg");
	Mat tmpImage, dstImage;

	imshow("原图", srcImage);
	cvtColor(srcImage, tmpImage, COLOR_BGR2GRAY);
	GaussianBlur(tmpImage, tmpImage, Size(9, 9), 2, 2);

	vector<Vec3f> circles;
	HoughCircles(tmpImage, circles, HOUGH_GRADIENT, 1.5, 10, 200, 100, 0, 0);
	
	//在图中绘制出圆
	for (size_t i = 0; i < circles.size(); i++) {
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int radius = cvRound(circles[i][2]);

		//绘制圆心
		circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
		//绘制圆轮廓
		circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);

	}
	imshow("效果图", srcImage);
	waitKey();
	return 0;

}

在这里插入图片描述

3. 重映射

3.1 重映射的概念

重映射,就是把一幅图像中某位置的像素放置到另一图片指定位置的过程。为了完成映射过程,需要获得一些插值为非整数像素的坐标,因为源图像与目标图像的像素坐标不是一 一对应的。一般情况下,我们通过重映射来表达每个像素的位置(x,y)。

g ( x , y ) = f ( h ( x , y ) ) g(x,y)=f(h(x,y)) g(x,y)=f(h(x,y))
其中g()是被目标图像,f()是源图像,h(x,y)是在(x,y)上操作的映射函数。

若有一幅图 I,对其按照下面的条件作重映射:

h ( x , y ) = ( I . c o l s − x , y ) h(x,y)=(I.cols-x,y) h(x,y)=(I.colsx,y)
图像会按照x轴方向发生翻转。

3.2 实现重映射:remap()函数

在这里插入图片描述

  • 第一个参数:源图像,需要为单通道8位或者浮点型图像。
  • 第二个参数:输出图像
  • 第三个参数:InputArray类型的map1,它有两种可能的表示对象
    • 表示点(x,y)的第一个映射
    • 表示CV_16SC2、CV_32FC1或CV_32FC2类型的x值
  • 第四个参数:InputArray类型的map2,也有两种可能的表示对象
    • 若map1表示点(x,y)时。这个参数不代表任何值。
    • 表示CV_16UC1, CV_32FC2类型的Y值
  • 第五个参数:int类型的interpolation,表示插值方式。本函数不支持INTER_AREA和INTER_LINEAR_EXACT方法。
  • 第六个参数:像素外推法(见BorderTypes)。当borderMode=BORDER_TRANSPARENT时,意味着目标图像中对应于源图像中的 "离群点 "的像素不会被该函数修改。
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
	Mat srcImage, dstImage;
	Mat map_x, map_y;

	srcImage = imread("../../image/lena_gray_256.tif");
	imshow("原图", srcImage);
	//创建和原始图一样的效果图
	dstImage.create(srcImage.size(), srcImage.type());
	map_x.create(srcImage.size(), CV_32FC1);
	map_y.create(srcImage.size(), CV_32FC1);

	//双层循环,遍历每一个像素点,改变map_x和map_y的值
	for (int i = 0; i < srcImage.rows; i++) {
		for (int j = 0; j < srcImage.cols; j++) {
			/*
			* at函数
				对于单通道图像"picture1",picture1.at<uchar>(i,j)就表示在第i行第j列的像素值。
				对于多通道图像如RGB图像"picture2",可以用picture2.at<Vec3b>(i,j)[c]来表示某个通道中在(i,j)位置的像素值。

			*/
			map_x.at<float>(i, j) = static_cast<float>(j);
			map_y.at<float>(i, j) = static_cast<float>(srcImage.rows - i);
		}
	
	}

	remap(srcImage, dstImage, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT,Scalar(0,0,0));
	imshow("程序窗口", dstImage);
	waitKey();

}

在这里插入图片描述

4.仿射变换

4.1 认识仿射变换

仿射变换(Affine Transformation或Affine Map),又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变化为另一个向量空间的过程
它保持了二维图像的平直性(直线经过变换之后依然是直线)和平行性(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线向上点的位置顺序不变)。

一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)接着再加上一个向量(平移)的形式。
常见的变换形式:

  • 旋转:rotation(线性变换)
  • 平移:translation(向量加)
  • 缩放:scale(线性变换)
    仿射变换代表的是两幅图之间的一种仿射关系
    我们通常使用2*3的矩阵来表示仿射变换。
    在这里插入图片描述

4.2 仿射变换的求法

在这里插入图片描述
OpenCV仿射变换相关的函数一般涉及到warpAffine和getRotationMatrix2D这两个函数。

  • 使用OpenCV函数warpAffine来实现一些简单的重映射
  • 使用OpenCV函数getRotationMatrix2D来获得旋转矩阵

4.3 进行仿射变换:warpAffine()函数

对一个图像进行仿生变换。
函数warpAffine使用指定的矩阵对源图像进行变换:

d s t ( x , y ) = s r c ( M 11 x + M 12 y + M 13 , M 21 x + M 22 y + M 23 ) dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23) dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)

在这里插入图片描述

  • 第一个参数:输入图像
  • 第二个参数:输出图像
  • 第三个参数:InputArray类型的M,2*3的变换矩阵
  • 第四个参数:表示输出图像的尺寸
  • 第五个参数:插值 方法的标识符

4.4 计算二维旋转变换矩阵:getRotationMatrix2D()函数

在这里插入图片描述

  • 第一个参数:源图像的旋转中心
  • 第二个参数:旋转角度。角度为正值表示向逆时针旋转
  • 第三个参数:缩放系数

该函数计算出以下矩阵:
在这里插入图片描述

4.3 案例

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

#define WINDOW_NAME1 "原始窗口"
#define WINDOW_NAME2 "Warp后的图像"
#define WINDOW_NAME3 "Warp后和Rotate后的图像"
int main() {

	//定义两组点,代表两个三角形
	Point2f srcTriangle[3];
	Point2f dstTriangle[3];


	Mat rotMat(2, 3, CV_32FC1);
	Mat warpMat(2, 3, CV_32FC1);

	Mat srcImage, dstImage_warp, dstImage_warp_rotate;
	
	srcImage = imread("../../image/1.tif");
	if (!srcImage.data) {
		cout << "读取图片错误" << endl;
		return false;
	}

	//设置目标图像的大小和类型与源图像一样
	dstImage_warp = Mat::zeros(srcImage.size(), srcImage.type());

	//设置源图像和目标图像上的三组点以计算仿射变换
	srcTriangle[0] = Point2f(0, 0);
	srcTriangle[1] = Point2f(static_cast<float>(srcImage.cols - 1), 0);
	srcTriangle[2] = Point2f(0, static_cast<float>(srcImage.rows - 1));

	dstTriangle[0] = Point2f(static_cast<float>(srcImage.cols * 0.0),
		static_cast<float>(srcImage.rows * 0.33));
	dstTriangle[1] = Point2f(static_cast<float>(srcImage.cols * 0.65),
		static_cast<float>(srcImage.rows * 0.35));
	dstTriangle[2] = Point2f(static_cast<float>(srcImage.cols * 0.15),
		static_cast<float>(srcImage.rows * 0.6));


	//求得仿射变换
	warpMat = getAffineTransform(srcTriangle, dstTriangle);

	//对源图像应用刚刚求得的仿射变换
	warpAffine(srcImage, dstImage_warp, warpMat, dstImage_warp.size());

	//对图像进行缩放后再旋转
	Point center = Point(dstImage_warp.cols / 2, dstImage_warp.rows / 2);
	double angle = -30.0;
	double scale = 0.8;

	//通过上面旋转细节信息求得旋转矩阵
	rotMat = getRotationMatrix2D(center, angle, scale);
	warpAffine(dstImage_warp, dstImage_warp_rotate, rotMat, dstImage_warp.size());

	//显示结果
	imshow(WINDOW_NAME1, srcImage);
	imshow(WINDOW_NAME2, dstImage_warp);
	imshow(WINDOW_NAME3, dstImage_warp_rotate);
	
	waitKey();
	return 0;
}

在这里插入图片描述

5. 直方图均衡化

5.1 直方图均衡化的概念和特点

什么是图像直方图?
它是一个图像的强度分布的图形表示。
它量化了每个强度值的像素数量。
在这里插入图片描述
什么是直方图均衡化?
这是一种改善图像对比度的方法,目的是拉长强度范围(也可参见维基百科的相应条目)。
为了更清楚地说明问题,从上面的图像中,你可以看到像素似乎集中在可用强度范围的中间。直方图均衡化所做的就是把这个范围拉长。请看下面的图:绿色的圆圈表示密度不足的地方。应用均衡化后,我们得到的直方图就像中间的图。得到的图像如右图所示。
在这里插入图片描述
它是如何工作的?
均衡意味着将一个分布(给定的直方图)映射到另一个分布(一个更广泛、更均匀的强度值分布),这样强度值就会分布在整个范围内。
为了达到均衡的效果,重映射应该是累积分布函数。对于直方图H(i),其累积分布H′(i)为:
在这里插入图片描述
为了将其作为重映射函数,我们必须将H′(i)归一化,使其最大值为255(或图像强度的最大值)。从上面的例子来看,累积函数是:
在这里插入图片描述
最后,我们使用一个简单的重映射程序来获得均衡化图像的强度值:
在这里插入图片描述

5.2 实现直方图均衡化:equalizeHist()函数

在这里插入图片描述

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

int main() {
	Mat srcImage, dstImage;
	srcImage = imread("../../image/1.tif");

	cvtColor(srcImage, srcImage, COLOR_BGR2GRAY);
	imshow("原始图", srcImage);

	equalizeHist(srcImage, dstImage);
	imshow("效果图", dstImage);
	waitKey();
	return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/698010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模拟高并发下RabbitMQ的削峰作用

在并发量很高的时候&#xff0c;服务端处理不过来客户端发的请求&#xff0c;这个时候可以使用消息队列&#xff0c;实现削峰。原理就是请求先打到队列上&#xff0c;服务端从队列里取出消息进行处理&#xff0c;处理不过来的消息就堆积在消息队列里等待。 可以模拟一下这个过…

生态+公链:中创面向未来的区块链建设!

未来的区块链市场&#xff0c;一定属于能够将区块链技术与应用完美结合在一起的产品。从互联网的发展历程来看&#xff0c;最后的竞争往往会集中到生态与兼容性。 如何将区块链的落地和应用更加有机地结合在一起&#xff0c;从而让区块链的功能和作用得到最大程度的发挥&#…

机器学习8:特征组合-Feature Crosses

特征组合也称特征交叉&#xff08;Feature Crosses&#xff09;&#xff0c;即不同类型或者不同维度特征之间的交叉组合&#xff0c;其主要目的是提高对复杂关系的拟合能力。在特征工程中&#xff0c;通常会把一阶离散特征两两组合&#xff0c;构成高阶组合特征。可以进行组合的…

css:去除input和textarea默认边框样式并美化

input input默认样式和focus样式 参考element-ui的css&#xff0c;可以实现如下效果 实现代码 <style>/* 去除默认样式 */input {border: none;outline: none;padding: 0;margin: 0;-webkit-appearance: none;-moz-appearance: none;appearance: none;background-im…

ElasticSearch 8.0+ 版本Windows系统启动

下载地址&#xff1a;https://www.elastic.co/cn/downloads/past-releases/winlogbeat-8-8-1 解压\elasticsearch\elasticsearch-8.5.1 进入bin目录&#xff0c;启动elasticsearch.bat 问题1&#xff1a; warning: ignoring JAVA_HOMED:\jdk1.8.0_271; using bundled JDK J…

使用凌鲨连接SSH服务器

SSH&#xff08;Secure Shell&#xff09;是一种加密的网络协议&#xff0c;用于安全地连接远程服务器。它提供了一种安全的通信方式&#xff0c;使得用户可以在不受干扰的情况下远程访问服务器。SSH协议的加密技术可以保护用户的登录信息和数据传输过程中的安全性。 SSH对于服…

伦敦银同业拆借利率查询

伦敦银同业拆借利率&#xff08;London InterBank Offered rate&#xff09;简称Libor&#xff0c;它是伦敦银业之间在货币市场的无担保借贷利率&#xff0c;主要报价有五种币别&#xff1a;美元、欧元、英镑、日圆、瑞士法郎&#xff0c;分别有隔夜、一周、一个月、两个月、三…

密码学—Vigenere破解Python程序

文章目录 概要预备知识点学习整体流程技术名词解释技术细节小结代码 概要 破解Vigenere需要Kasiski测试法与重合指数法的理论基础 具体知识点细节看下面这两篇文章 预备知识点学习 下面两个是结合起来使用猜测密钥长度的&#xff0c;只有确认了密钥长度之后才可以进行破解。 …

Jupyter Notebook左侧大纲目录设置

在 Jupyter Notebook 中&#xff0c;可以通过安装jupyter_contrib_nbextensions插件来实现在页面左边显示大纲的功能。 1. 安装插件 pip install jupyter_contrib_nbextensions 1.1 如何安装 windows cmd小黑裙窗口&#xff1b; 1.查看目前安装了哪些库 conda list 2. 使用…

【Oracle】springboot连接Oracle写入blob类型图片数据

目录 一、表结构二、mapper 接口和sql三、实体类四、controller五、插入成功后的效果 springboot连接Oracle写入blob类型图片数据 一、表结构 -- 创建表: student_info 属主: scott (默认当前用户) create table scott.student_info (sno number(10) constraint pk_si…

Vue3 完整项目搭建 Vue3+Pinia+Vant3/ElementPlus+typerscript

❤ Vue3 项目 1、Vue3+Pinia+Vant3/ElementPlus+typerscript环境搭建 1、安装 Vue-cli 3.0 脚手架工具 npm install -g @vue/cli2、安装vite环境 npm init @vitejs/app报错 使用: yarn create @vitejs/app依然报错 转而使用推荐的: npm c

Redisson分布式锁原理

1、Redisson简介 一个基于Redis实现的分布式工具&#xff0c;有基本分布式对象和高级又抽象的分布式服务&#xff0c;为每个试图再造分布式轮子的程序员带来了大部分分布式问题的解决办法。 2、使用方法 引入依赖 <dependency><groupId>org.springframework.bo…

基于Python所写的Word助手设计

点击以下链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87959100?spm1001.2014.3001.5503 《Word助手》程序使用说明 在PyCharm中运行《Word助手》即可进入如图1所示的系统主界面。在该界面中&#xff0c;通过顶部的工具栏可以选择所要进行的…

阿里云顺利通过云原生中间件成熟度评估

前言&#xff1a; 2023 年 6 月 6 日&#xff0c;由中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;承办的“ICT中国2023 高层论坛-云原生产业发展论坛”在北京召开&#xff0c;会上正式发布了一系列云原生领域评估结果。阿里云计算有限公司&#xff08;以…

图解红黑树

gitee仓库&#xff1a;https://gitee.com/WangZihao64/data-structure-and-algorithm/tree/master/RBTree 目录 概念红黑树的性质红黑树的调整规则 概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Bl…

Redis设计与实现笔记之字典

1.字典的实现 Redis中字典使用的哈希表结构 typedef struct dictht {// 哈希表数组dictEntry **table;// 哈希表大小unsigned long size;// 哈希表大小掩码&#xff0c;用于计算索引值// 总是等于 size - 1unsigned long sizemask;// 该哈希表已有节点的数量unsigned long use…

3D web可视化工具HOOPS Communicator与Autodesk的对比分析

越来越多的开发人员转向基于Web的2D和3D可视化和交互服务。这些使您只需使用网络浏览器即可快速向同事、客户或其他任何人展示设计。该领域的工具提供了大量功能&#xff0c;这些功能可能适合也可能不适合您的特定开发需求。 HOOPS Communicator的原始开发人员之一分享了对该市…

chatgpt赋能python:Python输出NaN的原因及解决方法

Python输出NaN的原因及解决方法 NaN&#xff08;Not a Number&#xff09;是一种特殊的数值类型&#xff0c;表示不是一个数字。在Python中&#xff0c;当某种计算结果无法表示为有限数字时&#xff0c;就会输出NaN。本文将介绍Python中输出NaN的原因&#xff0c;并提供一些解…

python: more Layer Architecture and its Implementation in Python

sql server: --学生表 DROP TABLE DuStudentList GO create table DuStudentList (StudentId INT IDENTITY(1,1) PRIMARY KEY,StudentName nvarchar(50),StudentNO varchar(50), --学号StudentBirthday datetime --学生生日 ) go mod…

Qt关闭主窗口后,退出所有异步线程

目录 1.要知道主窗口什么时候关闭2.关闭异步线程 1.要知道主窗口什么时候关闭 在widget.h新增下面的函数 private slots:void closeEvent(QCloseEvent *event);在widget.cpp新增 void Widget::closeEvent(QCloseEvent *event) {qDebug() << "关闭主窗口了&#x…