一、图像处理
1.图像操作
1.1读写像素
- 读一个GRAY像素点的像素值(CV_8UC1)
Scalar intensity = img.at<uchar>(y, x); Scalar intensity = img.at<uchar>(Point(x, y));
- 读一个RGB像素点的像素值
Vec3f intensity = img.at<Vec3f>(y, x); float blue = intensity.val[0]; float green = intensity.val[1]; float red = intensity.val[2];
1.2Vec3b与Vec3F
- Vec3b对应三通道的顺序是blue、green、red的uchar类型数据。
- Vec3f对应三通道的float类型数据 ;把CV_8UC1转换到CV32F1实现如下: src.convertTo(dst, CV_32F);
2.图像混合
理论:dst(I) = saturate ( src1(I) * alpha + src2(I) * beta + gama)
函数:
void CV::addWeighted(InputArray src1, double alpha,InputArray src2,double beta, double gamma,OutputArray dst,int dtype=-1)
参数1:输入图像Mat – src1
参数2:输入图像src1的alpha值
参数3:输入图像Mat – src2
参数4:输入图像src2的alpha值
参数5:gamma值
参数6:输出混合图像
注意点:两张图像的大小和类型必须一致才可以
3.调整图像亮度和对比度
图像变换可以看作如下:
- 像素变换 – 点操作
- 邻域操作 – 区域
调整图像亮度和对比度属于像素变换-点操作
,alpha 越大对比度越高,beta越大亮度越高
相关函数:
saturate_cast<uchar>(value) 确保值大小范围为0~255之间Mat.at<Vec3b>(y,x)[index]=value 给每个像素点每个通道赋值
4.模糊图像:
-
均值模糊 blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1,-1)); 第 一个参数为源图像,第 二个参数为目标图像,第 三个参数为Size类型,即内核的大小,一般写作Size(w,h);第 四个参数为Point类型,表示锚点,默认值为Point(-1,-1),如果这个坐标为负值,就表示取该核的中心为锚点;后面其余参数一般均取默认值
-
高斯模糊: GaussianBlur(Mat src, Mat dst, Size(x, y), sigmax, sigmay); 其中Size(x, y), x, y 必须是正数而且是奇数;参数sigmax表示高斯核函数在X方向的标准偏差,sigmay表示在Y方向的标准偏差,决定了高斯函数的宽度。
4.3中值滤波
基本思想:用像素点邻域灰度值的中值来代替该像素点的灰度值
void medianBlur(InputArray src,OutputArray dst,int ksize);
ksize表示孔径的线性尺寸,即滤波器窗口的大小,ksize必须是大于1的奇数
4.4双边滤波
基本思想:结合图像的空间林进度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性
void bilateralFilter(Mat src,Mat,dst,int d,double sigmaColor,double sigmaSpace,
int borderType=BORDER_DEFAULT)
d表示滤波过程中每个像素邻域的直径;
sigmaColor 颜色空间滤波器的值,值越大,就表明像素邻域内有越宽广的颜色被混合到一起,决定了多少差值之内的像素会被计算
sigmaSpace 坐标空间中滤波器的sigma的值,坐标空间的标注方差;数值越大,意味着越远的像素会相互影响,滤波器的影响范围更大; 如果d的值大于0则声明无效,否则根据它来计算d值 中值模糊的ksize大小必须是大于1而且必须是奇数。
5.形态学滤波
5.1膨胀和腐蚀
膨胀和腐蚀是对白色部分(高亮部分)而言的;膨胀就是图像中的高亮部分进行膨胀;腐蚀就是高亮部分被腐蚀。可以使用任意形状
膨胀:求局部最大值的操作;
void dilate(Mat src,Mat dst,Mat kernel,Point anchor=Point(-1,-1) int iterations=1,intborderType=BORDER_CONSTANT,Const Scalar& borderValue=mpDB());
腐蚀:求局部最小值的操作;
void erode(Mat src,Mat dst,Mat kernel,Point anchor=Point(-1,-1) int iterations=1,intborderType=BORDER_CONSTANT,Const Scalar& borderValue=mpDB());
一般使用函数getStructingElement 配合kernel参数的使用
Mat getStructingElement (int shape,Size ksize,Point anchor);
shape为形状:MORPH_RECT(矩形)、MORPH_CROSS(交叉形)、MORPH_ELLIPSE(椭圆形)
5.2开闭运算、形态学梯度、顶帽、黑帽
- 开运算:先腐蚀后膨胀;
用途:去除图像中的噪声和小的干扰细节,同时可以用于分离物体、拓展区域和填补空洞等应用。开运算可以平滑和细化二值图像的边界,同时保持物体的整体形状和大小。
- 闭运算:先膨胀后腐蚀的过程;
用途:排除小型黑洞
- 形态学梯度:膨胀图与腐蚀图之差
用途:保留物体的边缘轮廓
- 顶帽:原图与开运算的结果图之差
用途:用于分离比邻近点亮一些的斑块
- 黑帽:闭运算的结果图与原图之差
用途:突出比原轮廓周围的区域更暗的区域;用以分离比邻近点暗一些的斑块,轮廓非常完美
核心的API函数:morphologyEX
void morphologyEX(Mat src,Mat dst,int op,Mat kernel,Point anchor=Point(-1,-1) int iterations=1,intborderType=BORDER_CONSTANT,Const Scalar& borderValue=mpDB());
op表示形态学运算的类型:
标识符 含义 标识符 含义 MORPH_OPEN 开运算 MORPH_CLOSE 闭运算 MORPH_GRADIENT 形态学梯度 MORPH_TOPHAT 顶帽 MORPH_BLACKHAT 黑帽 MORPH_ERODE 腐蚀 MORPH_DILATE 膨胀
6.高斯金字塔
6.1高斯金字塔
高斯金字塔是从底向上,逐层降采样得到。
降采样之后图像大小是原图像MxN的M/2 x N/2 ,就是对原图像删除偶数行与列,即得到降采样之后上一层的图片。
高斯金子塔的生成过程分为两步:1.对当前层进行高斯模糊 2.删除当前层的偶数行与列
上采样:
pyrUp(Mat src, Mat dst, Size(src.cols*2, src.rows*2))
生成的图像是原图在宽与高各放大两倍
降采样:
pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2))
生成的图像是原图在宽与高各缩小1/2
6.2高斯不同
把同一张图像在不同的参数下做高斯模糊之后的结果相减,得到的输出图像。称为高斯不同
使用:高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常用到。
相关函数:
void cv::subtract(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray(), int dtype = -1)
mask
是可选的掩码数组,指定哪些元素需要进行计算。默认的掩码值为noArray(),表示没有指定掩码。这意味着在计算两个输入数组之间的差异时,会考虑所有的元素,不会进行任何限制或过滤。为了使得到的特征响应具有可比性,我们需要对其进行归一化处理。
void normalize( InputArray src, // 输入数组(图像或矩阵) OutputArray dst, // 输出数组(归一化后的图像或矩阵) double alpha = 0, // 归一化范围的最小值 double beta = 1, // 归一化范围的最大值 int norm_type = NORM_L2, // 归一化类型 int dtype = -1, // 输出数组的数据类型,-1 表示与输入数组相同 InputArray mask = noArray() // 可选的掩膜,用于指定归一化的区域 );
Mat tmp,dst,dogimg; GaussianBlur(src, tmp, Size(3, 3), 0, 0); imshow("一次高斯模糊", tmp); GaussianBlur(tmp, dst, Size(3, 3), 0, 0); imshow("二次高斯模糊", dst); subtract(tmp, dst, dogimg); normalize(dogimg, dogimg, 255, 0, NORM_MINMAX); imshow("高斯不同", dogimg);
7.基本阈值操作
用于将图像中的像素值分为两个类别:一个类别是低于或等于给定阈值的像素,另一个类别是高于阈值的像素。
THRESH_BINARY
:二进制阈值,低于阈值的像素值设为0,高于阈值的像素值设为指定的最大值(通常是255)。枚举值:0THRESH_BINARY_INV
:反二进制阈值,低于阈值的像素值设为指定的最大值,高于阈值的像素值设为0。枚举值:1
THRESH_TRUNC
:截断阈值,高于阈值的像素值设为阈值,低于阈值的像素值保持不变。枚举值:2
THRESH_TOZERO
:阈值到零,低于阈值的像素值设为0,高于阈值的像素值保持不变。枚举值:3
THRESH_TOZERO_INV
:反阈值到零,低于阈值的像素值保持不变,高于阈值的像素值设为0。枚举值:4
THRESH_MASK
:掩膜阈值,与cv2.THRESH_BINARY
类似,但是生成的是二进制掩膜,即将低于阈值的像素设为0,高于阈值的像素设为指定的最大值。枚举值:7
THRESH_OTSU
:Otsu 自动阈值,使用 Otsu’s 方法自动确定最佳阈值。对于双峰图像特别有效。枚举值:8
THRESH_TRIANGLE
:使用 Triangle 方法自动确定最佳阈值。对于具有单一峰值的图像特别有效。枚举值:16threshold(gray, dst, threshold_value, threshold_max, type_value);
gray:输入的源图像,单通道灰度图像。
threshold_value,设定的阈值,用于比较像素的灰度值。
threshold_max:分配给超过阈值的像素的最大像素值,常用的是255。
type_value:阈值类型,可选参数,用于指定不同的阈值处理方法。
8.自定义线性滤波
8.1卷积的概念
卷积:卷积是图像处理中一个操作,是kernel在图像的每个像素上的操作。
Kernel:本质上一个固定大小的矩阵数组,也被称为算子、卷积核、掩码等,其中心点称为锚点。
把kernel放到像素数组之上,求锚点周围覆盖的像素乘积之和(包括锚点),用来替换锚点覆盖下像素点值称为卷积处理。
常见算子:
Robert:Sobel:
拉普拉斯算子:
8.2卷积边界问题
图像卷积的时候边界像素,不能被卷积操作,原因在于边界像素没有完全跟kernel重叠,所以当3x3滤波时候有1个像素的边缘没有被处理,5x5滤波的时候有2个像素的边缘没有被处理。
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在 四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之 后再去掉这些边缘。openCV中默认的处理方法是: BORDER_DEFAULT,此外 常用的还有如下几种: BORDER_CONSTANT – 用指定像素值填充边缘
BORDER_REPLICATE – 用已知的边缘像素值填充边缘像素
BORDER_WRAP – 用另外一边的像素来补偿填充
copyMakeBorder(Mat src,Mat dst,int top,int bottom, int left, int right, int borderType,Scalar value);
相关API:
top、bottom、left、right表示上下左右边界的大小,一般上下左右都取相同值;
borderType表示边缘类型;
8.3卷积应用-图像边缘提取
边缘:图像中不同区域之间的边界或过渡区域;常用的边缘检测算法包括Sobel、Canny、Laplacian等。
如何捕捉/提取边缘?对图像求它的一阶导数delta = f(x) – f(x-1), delta越大,说明像素在X方向变化越大,边缘信号越强。
Sobel算子
离散微分算子,用来计算图像灰度的近似梯度;Soble算子功能集合高斯平滑和微分求导,又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方法与Y方向梯度图像。
水平梯度 垂直梯度 最终梯度图像,这里理论上采用平方再开方的形式得到最终梯度图像,但会增加计算时间。因此实践上可采用下面的方法
Scharr函数:
cv::Sobel ( InputArray Src // 输入图像 OutputArray dst// 输出图像,大小与输入图像一致 int depth // 输出图像深度. Int dx. // X方向,几阶导数 int dy // Y方向,几阶导数. int ksize, SOBEL算子kernel大小,必须是1、3、5、7、 double scale = 1 double delta = 0 int borderType = BORDER_DEFAULT ) cv::Scharr ( InputArray Src // 输入图像 OutputArray dst// 输出图像,大小与输入图像一致 int depth // 输出图像深度. Int dx. // X方向,几阶导数 int dy // Y方向,几阶导数. double scale = 1 double delta = 0 int borderType = BORDER_DEFAULT )
这里在选择输出图像的深度时,可以采用比输入图像深度 更高位数的深度,防止因为得到的像素过大而被截断
一般步骤:高斯平滑->转灰度->求梯度->图像混合
int main() { Mat src = imread("D:\\code\\OpenCV\\暗裔剑魔.jpg"); Mat gray, dst,dst1,dst2; GaussianBlur(src, dst, Size(3, 3), 0, 0); cvtColor(dst, gray, COLOR_BGR2GRAY); imshow("灰度", gray); Sobel(gray, dst1, CV_16S, 1, 0);//深度比源图像更大,防止数据截断 Sobel(gray, dst2, CV_16S, 0, 1); convertScaleAbs(dst1, dst1);//将图像的像素值转换为绝对值并进行缩放。 convertScaleAbs(dst2, dst2); addWeighted(dst1, 0.5, dst2, 0.5, 0, dst); imshow("最终图像", dst); waitKey(); return 0; }
Laplance算子
在二阶导数的时候,最大变化处的值为零即边缘是零值。通过二阶导数计算,依据此理论我们可以计算图像二阶导数,提取边缘。
Laplacian( InputArray src, OutputArray dst, int depth, //深度CV_16S int kisze, // 3 double scale = 1, double delta =0.0, int borderType = 4 )
一般步骤:高斯模糊去噪声GaussianBlur()-->>转换为灰度图像cvtColor()-->>拉普拉斯 二阶导数计算Laplacian()-->>取绝对值convertScaleAbs()-->>显示结果
8.4Canny边缘检测
基本步骤:
- 高斯模糊 - GaussianBlur
- 灰度转换 - cvtColor
- 计算梯度 – Sobel/Scharr
- 非最大信号抑制
确定当前像素点的梯度方向。沿着梯度方向的两个相邻像素进行插值,得到梯度方向上的两个临近点。比较当前像素点的梯度大小与其两个临近点的梯度大小。如果当前像素点的梯度大小最大,则保留该像素点作为边缘点。否则,将当前像素点抑制为非边缘点。目的是将检测到的边缘像素细化为更细的线条,同时保持边缘的强度。
5.高低阈值输出二值图像
设置高低两个阈值:
- 高阈值用于确定强边缘,只有梯度幅值超过高阈值的像素点才被认定为强边缘。
- 低阈值用于确定弱边缘,梯度幅值在低阈值和高阈值之间的像素点被认定为弱边缘。
- 低阈值通常选取高阈值的一半或三分之一。
- 弱边缘点只有在与强边缘点相连时才被保留,形成连续的边缘。
API:
Canny( InputArray src, // 8-bit的输入图像 OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色 double threshold1,// 低阈值,常取高阈值的1/2或者1/3 double threshold2,// 高阈值 int aptertureSize,// Soble算子的size,通常3x3,取值3 bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化 )
,默认情况一般选择L1,参数为FALSE
用于在两个同样大小和类型的图像之间完成简单的像素级的复制操作。
void cv::Mat::copyTo(OutputArray dst) const;
根据掩码图像的像素值进行复制操作,只在掩码图像对应像素值非零的区域进行复制。
void cv::Mat::copyTo(OutputArray dst, InputArray mask) const;
int t1_value = 50; int t1_max = 85; void canny_demo(int, void*); const char* tittle = "边缘检测"; Mat src = imread("D:\\code\\OpenCV\\暗裔剑魔.jpg"); Mat dst, gray; int main() { if (!src.data) { printf("图片加载出错\n"); return 1; } namedWindow(tittle, WINDOW_AUTOSIZE); createTrackbar("T1", tittle, &t1_value, t1_max, canny_demo);//不要忘记先创建窗口 canny_demo(0,0); waitKey(); return 0; } void canny_demo(int, void*) { Mat out_image,dst1; GaussianBlur(src, dst1, Size(3, 3), 0, 0); cvtColor(dst1, gray, COLOR_BGR2GRAY); Canny(gray, out_image, t1_value, t1_value * 2, 3, false); dst.create(src.size(), src.type()); src.copyTo(dst, out_image);//将输出的边缘图像作为掩码,进行复制 imshow(tittle, dst); //imshow(tittle, ~out_image);可以通过将图像取反,使背景为白色 }
9.霍夫变换
9.1霍夫线变换
前提条件:边缘检测已经完成
标准的霍夫变换 cv::HoughLines从平面坐标转换到霍夫空间,最终输出是 表示极坐标空间霍夫变换直线概率 cv::HoughLinesP最终输出是直线的两个点
cv::HoughLinesP( InputArray src, // 输入图像,必须8-bit的灰度图像 OutputArray lines, // 输出的极坐标来表示直线 double rho, // 生成极坐标时候的像素扫描步长 double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180 int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线 double minLineLength=0;// 最小直线长度,比设定参数短的就不能显现 double maxLineGap=0;//同一行点与点之间连接起来的最大间隔 )
int main() { Mat src, dst; src = imread("C:\\Users\\86133\\Desktop\\提取.png"); if (src.empty()) { printf("图片加载出错\n"); return 1; } GaussianBlur(src, dst, Size(3, 3), 0, 0); Mat gray,dst1, dst2; Canny(dst, gray, 100, 150); cvtColor(gray, dst1, COLOR_GRAY2BGR);//灰度图像转换为颜色图像 imshow("canny", gray); vector<Vec4f> plines; Scalar color = Scalar(255, 0, 0); HoughLinesP(gray, plines, 1, CV_PI / 180.0, 10, 0, 10); for (size_t i = 0; i < plines.size(); i++) { Vec4f hline = plines[i]; line(dst1, Point(hline[0], hline[1]), Point(hline[2], hline[3]),color,3,LINE_AA); } imshow("霍夫变换", dst1); waitKey(); return 0; }
9.2霍夫圆变换
相关API:
因为霍夫圆检测对噪声比较敏感,所以首先要对图像做中值滤波。基于效率考虑,Opencv中实现的霍夫变换圆检测是基于图像梯度的实现,分为两步:
1. 检测边缘,发现可能的圆心
2. 基于第一步的基础上从候选圆心开始计算最佳半径大小
HoughCircles( InputArray image, // 输入图像 ,必须是8位的单通道灰度图像 OutputArray circles, // 输出结果,发现的圆信息 Int method, // 方法 - HOUGH_GRADIENT Double dp, // dp = 1; Double mindist, // 最短距离-可以分辨是两个圆的,检测到的圆之间的最小距离。如果该值太小, //则可能会检测到相邻的相似圆,如果该值太大,则可能会漏掉某些圆。 Double param1, // 边缘检测阈值,用于筛选强边缘 Double param2, // 中心点累加器阈值 ,值越小,潜在圆的数量越多。 Int minradius, // 最小半径 Int maxradius//最大半径 )
int main() { Mat dst, src = imread("C:\\Users\\86133\\Desktop\\霍夫圆.png"); if (!src.data) { printf("图片加载出错\n"); return 1; } medianBlur(src, dst, 3); Mat gray; cvtColor(dst, gray, COLOR_BGR2GRAY); imshow("灰度", gray); vector<Vec3f> pcricles; HoughCircles(gray, pcricles, HOUGH_GRADIENT, 1, 25, 100, 30, 25, 50); src.copyTo(dst); for (size_t i = 0; i < pcricles.size(); i++) { Vec3f cc = pcricles[i]; circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(255,0,0), 1, LINE_AA);//在dst图上显示圆 circle(dst, Point(cc[0], cc[1]),2, Scalar(0, 0, 255), 1, LINE_AA);//在dst图上 显示圆心 } imshow("霍夫圆", dst); waitKey(); return 0; }
10.重映射
API:
remap:
void remap(InputArray src, OutputArray dst, InputArray map_x, InputArray map_y, int interpolation, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar());
src
:输入源图像,类型为CV_8UC1
、CV_8UC3
、CV_16SC1
、CV_16SC3
、CV_32FC1
或CV_32FC3
。dst
:输出目标图像,与源图像具有相同的大小和类型。map_x
:x 方向的映射坐标,类型为CV_32FC1
或CV_32FC2
。map_y
:y 方向的映射坐标,类型为CV_32FC1
或CV_32FC2
。interpolation
:插值方法,用于确定映射坐标中取样位置的像素值。常见的插值方法有INTER_NEAREST
、INTER_LINEAR
、INTER_CUBIC
等。borderMode
:边界填充模式,用于处理超出边界范围的像素。默认值为BORDER_CONSTANT
,表示使用常数填充。borderValue
:边界填充的常数值,在borderMode
为BORDER_CONSTANT
时使用。waitKey:
int waitKey(int delay = 0);
delay
:等待键盘输入的时间(以毫秒为单位)。默认情况下,它的值为 0,表示程序等待键盘输入,直到用户按下任意按键。
waitKey
函数的返回值是一个整数,表示用户按下的键盘按键的 ASCII 码值。如果没有发生键盘按键事件,返回值为 -1。映射矩阵:
Mat dst, src = imread("D:\\code\\OpenCV\\暗裔剑魔.jpg"); Mat map_x, map_y;//创建映射矩阵 void update_map(int index); int main() { if (!src.data) { printf("图片加载出错\n"); return 0; } map_x.create(src.size(), CV_32FC1); map_y.create(src.size(), CV_32FC1); while (true) { int c = waitKey(0); if ((char)c == 27) break; int index = c % 4; update_map(index); remap(src, dst, map_x, map_y, INTER_LINEAR,BORDER_CONSTANT,Scalar(255,254,0)); imshow("重映射", dst); } return 0; } void update_map(int index) { for (size_t row = 0; row < src.rows; row++) { for (size_t col = 0; col < src.cols; col++) { switch (index) { case 0://图像缩小一半 if (col > src.cols * 0.25 && col<src.cols * 0.75 && row>src.rows * 0.25 && row < src.rows * 0.75) { map_x.at<float>(row, col) = 2 * (col - src.cols * 0.25); map_y.at<float>(row, col) = 2 * (row - src.rows * 0.25); } else { map_x.at<float>(row, col) = 0; map_y.at<float>(row, col) = 0; } break; case 1://Y方向对调 map_x.at<float>(row, col) = col; map_y.at<float>(row, col) = src.rows-row; break; case 2://X方向对调 map_x.at<float>(row, col) = src.cols-col; map_y.at<float>(row, col) = row; break; case 3://x,y方向对调 map_x.at<float>(row, col) = src.cols - col; map_y.at<float>(row, col) = src.rows - row; } } } }
11.直方图
直方图:通过统计图像中每个像素值的数量来构建;其实对图像梯度、每个像素的角度、等一切图像的属性值,我们都可以建立直方图。
以像素为例:
dims(维度):指的是直方图中考虑的数据特征的数量。在二维图像中,通常使用亮度(灰度)作为唯一的特征,这时 dims 的取值为 1。在彩色图像中,可以同时考虑三个颜色通道的特征,这时 dims 的取值为 3。
bins(箱子/区间):是将像素值范围划分为多个小区间的过程。一个直方图由多个箱子组成,每个箱子表示一个像素值区间。通过调整 bins 的数量,可以控制直方图的精细度。较多的箱子会提供更详细的直方图信息,而较少的箱子会提供更简化的直方图。
range(范围):是指定图像中关注的像素值范围。常见的像素值范围是 0-255,对应于 8 位图像的灰度级。在某些情况下,我们可能只对一部分像素值感兴趣,那么可以通过限定 range 来选择特定的像素值范围进行直方图分析。
11.1直方图均衡化
一种用于增强图像对比度的图像处理技术。
- 原理:它通过重新分布图像的像素值,使得图像中的灰度级能够更均匀地覆盖整个像素值范围。
- 作用:增加图像的通用对比度,使得图像更清晰、更鲜明。
API: equalizeHist
equalizeHist( InputArray src,//输入图像,必须是8-bit的单通道图像 OutputArray dst// 输出结果 )
11.2直方图计算
步骤:分通道显示->计算直方图->归一化->绘制
API:split、calcHist、cvRound
split(// 把多通道图像分为多个单通道图像 const Mat &src, //输入图像 Mat* mvbegin)// 输出的通道图像数组
calcHist( const Mat* images,//输入图像指针 int images,// 图像数目 const int* channels,// 通道数 InputArray mask,// 输入mask;如果提供了掩码,那么只有位于掩码为非零的像素才会被计算在内。 OutputArray hist,//输出的直方图数据;用于存储计算得到的直方图数据 int dims,// 维数 const int* histsize,// 直方图级数;以方数组形式指定的箱子数量 const float** ranges,// 值域范围,以数组形式给出 bool uniform,// 指示是否对直方图进行归一化,默认为true。 bool accumulate// 控制是否累积直方图。如果为true,则直方图累积计算,默认为false。 )
int cvRound(double value);//用于将浮点数或双精度数值四舍五入为最接近的整数值。
int main() { Mat histimage, src = imread("D:\\code\\OpenCV\\暗裔剑魔.jpg"); //分通道显示 vector<Mat> bgr_plane; split(src, bgr_plane); //计算直方图 int histsize = 256; float range[] = { 0,256 }; const float* histrange[] = {range}; Mat b_hist, g_hist, r_hist; calcHist(&bgr_plane[0], 1, 0,Mat(), b_hist,1, &histsize, histrange, true, false); calcHist(&bgr_plane[1], 1, 0, Mat(),g_hist, 1, &histsize, histrange, true, false); calcHist(&bgr_plane[2], 1, 0, Mat(),r_hist, 1, &histsize, histrange, true, false); //归一化 int hist_h = 400; int hist_w = 512; int bin_w = hist_w / histsize; Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0)); normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX); normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX); normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX); //绘制直方图 for (size_t i = 1; i <= histsize; i++) { //坐标体系中零点坐标为图片的左上角,转换为传统的坐标系,因此要用hist_h- line(histImage, Point((i-1)*bin_w, hist_h - cvRound(b_hist.at<float>(i - 1))), Point(i*bin_w,hist_h-cvRound(b_hist.at<float>(i))),Scalar(255,0,0),2,LINE_AA); line(histImage, Point((i-1)*bin_w, hist_h - cvRound(g_hist.at<float>(i - 1))), Point(i*bin_w, hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, LINE_AA); line(histImage, Point((i-1)*bin_w, hist_h - cvRound(r_hist.at<float>(i - 1))), Point(i*bin_w, hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, LINE_AA); } imshow("直方图", histImage); waitKey(); return 0; }
11.3直方图比较:
API:compareHist
cv::HISTCMP_CORREL
:相关性相关性(越接近1表示越相似,越接近-1表示越不相似)cv::HISTCMP_CHISQR
:卡方距离(越接近0表示越相似)cv::HISTCMP_INTERSECT
:交集(越大表示越相似)cv::HISTCMP_BHATTACHARYYA
:Bhattacharyya距离(越接近0表示越相似)double cv::compareHist(InputArray H1, InputArray H2, int method);
int main() { Mat src1, src2; src1 = imread("D:\\code\\OpenCV\\暗裔剑魔.jpg"); src2 = imread("D:\\code\\OpenCV\\死亡歌颂者.jpg"); if (src1.empty() || src2.empty()) { printf("图片加载出错\n"); return 1; } //转换成HSV图像 Mat hsv1, hsv2; cvtColor(src1, hsv1, COLOR_BGR2HSV); cvtColor(src2, hsv2, COLOR_BGR2HSV); //计算直方图 MatND hist1, hist2; int channels[] = { 0,1 }; int histSize[] = { 50,60 }; float h_range[] = { 0,180 }; float s_range[] = { 0,256 }; const float* ranges[] = { h_range,s_range }; calcHist(&hsv1, 1, channels, Mat(), hist1, 2, histSize, ranges, true, false); normalize(hist1, hist1, 0, 1, NORM_MINMAX); calcHist(&hsv2, 1, channels, Mat(), hist2, 2, histSize, ranges, true, false); normalize(hist2, hist2, 0, 1, NORM_MINMAX); double base = compareHist(hist1, hist2, HISTCMP_CHISQR); putText(src1, convertToString(base), Point(50, 50), FONT_HERSHEY_COMPLEX, 1, Scalar(123, 123, 123), 2, LINE_AA); cout << base << endl; waitKey(); return 0; }
11.4反向投影
API:mixChannels calcBackProject
void mixChannels(const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs);
- src:输入源图像数组指针
- nsrcs:源图像数组的大小,通常为1
- dst:输出目标图像数组指针
- ndsts:目标图像数组的大小,通常为1
- fromTo:通道映射规则数组指针,每对映射关系由两个int值表示,表示源图像通道索引和目标图像通道索引
- npairs:通道映射规则对的数量
void calcBackProject(const Mat* images, int nimages, const int* channels, const Mat& hist, Mat& backProject, const float** ranges, double scale = 1, bool uniform = true);
- images:输入图像数组指针
- nimages:输入图像数组的大小
- channels:通道索引数组指针,用于指定用于计算反向投影的图像通道
- hist:输入的直方图模型
- backProject:反向投影图像输出,与输入图像大小一致
- ranges:指向每个通道的取值范围数组指针,每个通道对应一个浮点数组,表示该通道的取值范围
- scale:反向投影图像的缩放因子,用于调整输出图像的亮度,默认值为1
- uniform:一个布尔值,用于指定直方图是否均匀,默认为true。如果为true,每个bin的权重为1/N,其中N为bin的数量。如果为false,权重由实际的直方图值确定。
Mat src, hsv, hue; int bin = 30; void binchange(int, void*); int main() { src = imread("C:\\Users\\86133\\Desktop\\opencv图片\\手掌.png"); if (!src.data) { cout << "图片加载出错" << endl; return 1; } cvtColor(src, hsv, COLOR_BGR2HSV); //分离hue 色调通道 hue.create(hsv.size(), hsv.depth()); int channel[] = { 0,0 }; mixChannels(&hsv, 1, &hue, 1, channel, 1); namedWindow("原始图", WINDOW_AUTOSIZE); createTrackbar("色调组距", "原始图", &bin, 180, binchange); binchange(0, 0); imshow("原始图", src); waitKey(); return 0; } void binchange(int, void*) { Mat hist; int histSize = MAX(bin, 2); float range[] = { 0,180 }; const float* histrange[] = { range }; calcHist(&hue, 1, 0, Mat(), hist, 1, &histSize, histrange,true, false); normalize(hist, hist,0,255, NORM_MINMAX); //绘制反向投影 Mat backproj; calcBackProject(&hue,1,0,hist,backproj,histrange,1,true); imshow("反向投影", backproj); //绘制直方图 int hist_h = 400; int hist_w = 400; Mat histImage(hist_w,hist_h,CV_8UC3,Scalar(0,0,0)); int bin_w = cvRound((double)hist_w / histSize); for (size_t i = 0; i < bin; i++) { rectangle(histImage, Point(i * bin_w, hist_h), Point((i + 1) * bin_w, hist_h - cvRound(hist.at<float>(i)*hist_h / 255.0)), Scalar(100, 123, 255), 2,LINE_AA); } imshow("直方图", histImage); }
11.5模板匹配
API: minmaxLoc、matchTemplate
void cv::minMaxLoc( InputArray src, // 输入数组,可以是单通道或多通道图像,数据类型可以是CV_8U、CV_16U、CV_32F等 double* minVal, // 输出参数,用于存储最小值 double* maxVal, // 输出参数,用于存储最大值 Point* minLoc = nullptr, // 输出参数,用于存储最小值的位置坐标(可选) Point* maxLoc = nullptr, // 输出参数,用于存储最大值的位置坐标(可选) InputArray mask = noArray() // 可选参数,用于指定掩码图像 );
void cv::matchTemplate( InputArray image, // 输入图像 InputArray templ, // 模板图像 OutputArray result, // 匹配结果图像 int method // 匹配方法,可以是以下几种之一:CV_TM_SQDIFF、CV_TM_SQDIFF_NORMED、 //CV_TM_CCORR、CV_TM_CCORR_NORMED、 //CV_TM_CCOEFF、CV_TM_CCOEFF_NORMED );
- image:要在其中查找模板的输入图像。
- templ:要搜索的模板图像。
- result:输出的匹配结果图像,类型为CV_32F。
- method:指定匹配方法。常用的匹配方法包括CV_TM_SQDIFF、CV_TM_CCOEFF和CV_TM_CCORR。这些方法具有不同的工作原理和适用场景。
Mat src, dst,templ; int match_method = TM_SQDIFF; int match_max = 5; const char* input = "输入图"; const char* model = "模板图"; const char* output = "输出图"; void match_Demo(int, void*); int main() { src = imread("C:\\Users\\86133\\Desktop\\opencv图片\\暗裔剑魔.jpg"); templ = imread("C:\\Users\\86133\\Desktop\\opencv图片\\暗裔剑魔_手.png"); if (src.empty() || src.empty()) { cout << "图片加载出错" << endl; return 0; } //创建窗口 namedWindow(input); namedWindow(output); namedWindow(model); createTrackbar("匹配方法", input, &match_method, match_max, match_Demo); match_Demo(0, 0); waitKey(); return 0; } void match_Demo(int, void*) { int width = src.cols - templ.cols + 1;//设置最终输出窗口的宽和高 int high = src.rows - templ.rows + 1; Mat result(width, high, CV_32FC1); //模板匹配并对结果进行归一化 matchTemplate(src, templ, result, match_method, Mat()); normalize(result, result, 0, 1, NORM_MINMAX); Point minLoc, maxLoc,realLoc; double min, max; minMaxLoc(result, &min, &max, &minLoc, &maxLoc); //根据不同方法寻找所需的点 if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED) realLoc = minLoc; else realLoc = maxLoc; src.copyTo(dst); rectangle(result,Rect(realLoc.x, realLoc.y, templ.cols, templ.rows), Scalar(0, 255, 0), 2, LINE_AA); rectangle(dst, Rect(realLoc.x, realLoc.y, templ.cols, templ.rows), Scalar(0, 255, 0), 2, LINE_AA); imshow(input, dst); imshow(model, templ); imshow(output, result); }