OpenCV_C++基础
Opencv_C++
1.开发环境搭建
1.1 安装VS2022
自行百度安装即可
1.2 opencv开发包下载安装
1.2.1 访问官网
https://opencv.org/releases/
点击windows 会自动跳转到github下载
https://github.com/opencv/opencv/releases/download/4.12.0/opencv-4.12.0-windows.exe
1.2.2 双击安装,解压文件到某个目录
1.2.3 设置bin文件目录到系统环境变量PATH
1.2.4新建一个C++控制台工程,配置头文件和库
视图–>其他窗口–>属性管理器:
Debug|x64–>右键属性–>VC++目录–>包含目录:
E:workopencvpackageopencvuildinclude
E:workopencvpackageopencvuildincludeopencv2
VC++目录–>库目录:
E:workopencvpackageopencvuildx64vc16lib
连接器–>输入–>附加依赖项:
opencv_world4120d.lib
1.2.5 编写main函数测试
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main()
{
Mat img = imread("E:/work/opencv/wokespace/images/apple.jpg");
if (img.empty()) {
printf("can not load image
");
return -1;
}
imshow("test opencv image", img);
waitKey(0);
destroyAllWindows();
return 0;
}
2.加载修改保存图像
2.1 函数说明
加载图像(imread)
Mat imread( const String& filename, int flags = IMREAD_COLOR_BGR );
功能:imread功能是加载图像文件成为一个Mat对象
参数:
第一个参数表示图像名称
第二个参数表示加载的图像类型,支持常见的三种:
IMREAD_UNCHANGED (-1):加载原图,不做任何改变
IMREAD_GRAYSCALE (0):把原图作为灰度图加载
IMREAD_COLOR or IMREAD_COLOR_BGR (1):把原图作为BGR图像加载
注意: opencv支持JPGPNGTIFF等常见格式图像文件的加载
显示图像(cv::namedWindow与cv::imshow)
void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);
功能: 创建一个Opencv窗口,它是由opencv自动创建与释放,无需主动销毁
参数:
第一个参数是窗口名称
第二个参数:
WINDOW_NORMAL:跟QT集成的时候会使用,不能人为修改窗口大小
WINDOW_AUTOSIZE:会自动根据图像大小,选择窗口大小,允许修改窗口大小
void imshow(const String& winname, InputArray mat);
功能: 显示图像到制定窗口上去
参数:
第一个参数是窗口名称
第二个参数是Mat对象
修改图像(cvtColor)
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0, AlgorithmHint hint = cv::ALGO_HINT_DEFAULT );
功能: 把图像从一个彩色空间转换到另一个色彩空间
参数:
src:输入图像,支持8位无符号、16位无符号或单精度浮点类型
dst:输出图像,大小和深度与源图像相同
code:颜色空间转换代码,决定了转换的类型16。常用选项包括:
COLOR_BGR2GRAY:BGR彩色图像转灰度图像
COLOR_BGR2HSV:BGR彩色图像转HSV颜色空间
COLOR_BGR2YCrCb:BGR彩色图像转YCrCb颜色空间
dstCn:目标图像的通道数,默认为0表示自动推断
hint:算法提示,用于优化性能,默认为cv::ALGO_HINT_DEFAULT
保存图像(imwrite)
bool imwrite( const String& filename, InputArray img,
const std::vector<int>& params = std::vector<int>());
imwrite() 函数用于将图像保存到指定文件,其基本用法如下:
函数参数说明
filename:保存图像的文件路径和名称,需要包含正确的图像格式后缀
img:要保存的图像数据,类型为 Mat2
params:可以指定压缩参数
2.2 代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main()
{
Mat img = imread("E:/work/opencv/wokespace/images/apple.jpg", IMREAD_COLOR_BGR);
if (img.empty()) {
printf("can not load image
");
return -1;
}
Mat gray_img, hls_img;
namedWindow("gray img", WINDOW_AUTOSIZE);
cvtColor(img, gray_img, COLOR_BGR2GRAY);//转换为灰度图
imshow("gray img", gray_img);
namedWindow("hls img", WINDOW_AUTOSIZE);
cvtColor(img, hls_img, COLOR_BGR2HLS);//转换为饱和度图片
imshow("hls img", hls_img);
std::vector<int> png_params;
png_params.push_back(cv::IMWRITE_PNG_COMPRESSION);
png_params.push_back(5); // 压缩级别0-9
imwrite("test.png", img, png_params); //压缩保存
//imwrite("test.jpg", img); //简单保存
waitKey(0);
return 0;
}
3.矩阵的掩膜操作
3.1函数说明
获取图像像素指针:
Mat.ptr<uchar>(int i=0):获取像素矩阵的指针,索引i表示第几行,从0开始计数行
获取当前行指针: const uchar *current = src.ptr<uchar>(row)
获取当前像素点P(row,col)的像素值: p(row, col) = current[col]
像素范围处理saturate_cast
saturate_cast<uchar>(-100) 返回0
saturate_cast<uchar>(288) 返回255
saturate_cast<uchar>(100) 返回100
这个函数的功能是确保BGR的值的范围在0-255之间
3.2 掩膜操作解释
掩膜操作十分简单,根据掩膜来重新计算每个像素的像素值,掩膜也被称为kernel
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char* argv[])
{
Mat src, dst;
src = imread("E:/work/opencv/wokespace/images/lena.jpg");
if (src.empty()) {
printf("can not load image
");
return -1;
}
/*
-1
kernel= -1 5 1
1
*/
printf("%d %d %d %d
", src.depth(), src.cols, src.rows, src.channels());
int width = src.cols * src.channels(); //图像的宽度
int height = src.rows;//图像的高度
int offset = src.channels();//像素通道
dst = Mat(src.size(), src.type());
//掩膜计算
for (int row = 1; row < height - 1; row++) {
const uchar* current = src.ptr<uchar>(row);
const uchar* previous = src.ptr<uchar>(row-1);
const uchar* next = src.ptr<uchar>(row+1);
uchar* output = dst.ptr<uchar>(row);
for (int col = offset; col < width; col++) {
output[col] = 5 * current[col] - current[col - offset] + current[col + offset] - previous[col] + next[col];
}
}
//CV_Assert(src.depth() == CV_8U);
namedWindow("input image");
imshow("input image", src);
namedWindow("output image");
imshow("output image", dst);
waitKey(0);
return 0;
}
4.Mat对象
4.1Mat对象与IplImage对象
Mat对象OpenCV2.0之后引进的图像数据结构自动分配内存不存在内存泄漏的问题,是面向对象的数据结构.分两部分,头部和数据部分.
IplImage是从2001年OpenCV发布之后就一直存在,是C语言风格的数据结构,需要开发者自己分配与管理内存,对大的程序使用它容易导致内存泄漏问题.
4.2Mat对象构造函数和常用方法
| 构造函数 | 常用方法 |
|---|---|
| Mat() | void copyTo( OutputArray m ) const |
| Mat(int rows, int cols, int type) | void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const; |
| Mat(Size size, int type) | Mat clone() |
| Mat(int rows, int cols, int type, const Scalar& s) | int channels() |
| Mat(Size size, int type, const Scalar& s) | int depth() |
| Mat(int ndims, const int* sizes, int type) | bool empty() |
| Mat(int ndims, const int* sizes, int type, const Scalar& s) | uchar* ptr(i=0) |
4.3Mat对象使用
部分复制:一般情况下,只会复制Mat对象的头和指针部分,不会复制数据部分
Mat A = imread(filePath)
Mat B(A)
完全复制:如果想把Mat对象的头和数据部分一起复制,可以通过如下两个API实现
Mat f = A.clone() ; Mat G; A.copyTo(G);
四个要点:
输出图像的内存是自动分配的
使用OpenCV的C++接口,不需要考虑内存分配问题
赋值操作和拷贝构造函数只会复制头部分
使用clone和copyTo两个函数实现完全复制
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char* argv[])
{
Mat src;
src = imread("E:/work/opencv/wokespace/images/lena.jpg");
if (src.empty()) {
printf("can not load image
");
return -1;
}
//printf("%d %d %d %d %d
", src.depth(), src.cols, src.rows, src.channels(), src.type());
//CV_Assert(src.depth() == CV_8U);
namedWindow("input image");
imshow("input image", src);
//创建一张空白图
Mat dst;
/*dst = Mat(src.size(), src.type());
dst = Scalar(127, 0, 255);*/
//完全克隆一张图片
//dst = src.clone();
//src.copyTo(dst);
cvtColor(src, dst, COLOR_BGR2GRAY);//转换为灰度图
printf("src image channels = %d type = %d
", src.channels(), src.type());
printf("dst image channels = %d type = %d
", dst.channels(), dst.type());
const uchar* firstRow = dst.ptr<uchar>(0);//灰度图第一行数据
printf("first pixel value = %d
", firstRow[0]);//打印第一个像素值
printf("dst.rows = %d
", dst.rows);
printf("dst.cols = %d
", dst.cols);
namedWindow("output1 image");
imshow("output1 image", dst);
waitKey(0);
return 0;
}
4.4 Mat创建数组
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char* argv[])
{
Mat M1(100, 200, CV_8UC1, Scalar(127));//创建一个通道数为1,行列均为100的数组
Mat M2(100, 400, CV_8UC3, Scalar(0, 255, 0));//创建一个通道数为3,行列均为100的数组
//使用create创建,需要指定Scalar颜色值
Mat M3;
M3.create(M1.size(), M1.type());
M3 = Scalar(255);
namedWindow("M1");
imshow("M1", M1);
namedWindow("M2");
imshow("M2", M2);
namedWindow("M3");
imshow("M3", M3);
Mat M4 = Mat::zeros(M3.size(), M3.type()); //全部初始化为0
//定义一个3*3的掩膜(提高对比度的) ,小数组
Mat dst;
Mat src = imread("E:/work/opencv/wokespace/images/lena.jpg");
Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 2, 1, 0, 1, 0);
filter2D(src, dst, -1, kernel);
namedWindow("dst");
imshow("dst", dst);
while (1) {
if (waitKey(20) == 'q') {
break;
}
}
return 0;
}
5.图像操作
5.1 读写图像
imread可以指定加载为灰度或者RGB图像imwrite保存图像文件,类型由扩展名决定
5.2读写像素和修改像素值
读一个GRAY像素点的像素值(CV_8UC1)
int pixel= img.at(y,x); 或者 int pixel= img.at(Point(x,y))
读一个BGR像素点的像素值
Vec3b pixel= img.at(y,x)
float blue = pixel.val[0]; float green = pixel.val[1]; float red = pixel.val[2]
Vec3b 和 Vec3f
Vec3b对应的数据类型为 uchar; Vec3f对应的数据类型为float;都有三个通道
把CV_8UC 装换为 CV_32F: src.convertTo(CV_32F)
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char* argv[])
{
Mat img, dst;
//img = imread("E:/work/opencv/wokespace/images/lena.jpg",IMREAD_GRAYSCALE);
img = imread("E:/work/opencv/wokespace/images/lena.jpg");
namedWindow("img");
imshow("img", img);
int height = img.rows;
int width = img.cols;
int channels = img.channels();
int depth = img.depth();
dst.create(img.size(), img.type());
#if 0
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (channels == 1) {
int val = img.at<uchar>(y, x);
dst.at<uchar>(y, x) = 255 - val;
}
else if (channels == 3) {
if (depth == CV_8U) {
Vec3b pixel = img.at<Vec3b>(y, x);
//对图像做一个反取值
dst.at<Vec3b>(y, x)[0] = 255 - pixel[0];
dst.at<Vec3b>(y, x)[1] = 255 - pixel[1];
dst.at<Vec3b>(y, x)[2] = 255 - pixel[2];
}
else if (depth == CV_32F) {
Vec3f pixel = img.at<Vec3f>(y, x);
//对图像做一个反取值
dst.at<Vec3f>(y, x)[0] = 255 - pixel[0];
dst.at<Vec3f>(y, x)[1] = 255 - pixel[1];
dst.at<Vec3f>(y, x)[2] = 255 - pixel[2];
}
}
}
}
#else
bitwise_not(img, dst);//和上面的代码是等价的
#endif
namedWindow("img_invert");
imshow("img_invert", dst);
waitKey(0);
return 0;
}
6.图像混合
6.1 理论–线性混合操作
α取值0-1之间;对图像中的所有像素点都进行权重加法,得到一张新的图片
6.2 相关API(addWeighted)
void addWeighted(InputArray src1, double alpha, InputArray src2,
double beta, double gamma, OutputArray dst, int dtype = -1)
dst = saturate(src1 × alpha + src2 × beta + gamma)
功能:计算两个数组(图像)加权和的线性混合
参数:
src1:第一个输入图像数组,必须是 Mat 类型
alpha:第一个数组的权重系数,决定 src1 在融合中的贡献程度
src2:第二个输入图像数组,尺寸和通道数必须与 src1 完全匹配
beta:第二个数组的权重系数
gamma:添加到权重总和上的标量值,常用于调整输出图像的全局亮度
dst:输出数组,存储融合结果,尺寸和类型与输入相同
dtype:输出阵列的可选深度,默认值 -1 表示等同于 src1.depth()
注意:两张图像的大小和类型必须一致
6.3代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char* argv[])
{
Mat img1, img2, dst;
img1 = imread("E:/work/opencv/wokespace/images/alpha.jpeg");
img2 = imread("E:/work/opencv/wokespace/images/beta.jpg");
if (img1.empty() || img2.empty()) {
return -1;
}
//type 表示数据类型与通道数两个属性
//CV_<位数><数据类型><通道数> 例如 CV_8UC3 三通道无符号整数 CV_8UC1 单通道无符号整数
if ((img1.rows != img2.rows) || (img1.cols != img2.cols) || (img1.type() != img2.type())) {
return -2;
}
addWeighted(img1, 0.4, img2, 0.6, 0, dst, -1);
namedWindow("dst");
imshow("dst", dst);
waitKey(0);
return 0;
}
7.调整图像亮度与对比度
7.1理论
图像变换可以看做如下:
像素变换—点操作
邻域操作—区域
调整图像亮度和对比度属于像素变换—点操作
7.2相关API
Mat new_image = Mat::zeros(image.size(), image.type())
创建一张跟原图像大小和类型一致的空白图像像素初始值为0
saturate_cast<uchar>(value)确保值大小范围为0-255之间
Mat.at<Vec3b>(y,x)[index] = value 给每个像素点每个通道赋值
7.3代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char* argv[])
{
Mat img, dst;
img = imread("E:/work/opencv/wokespace/images/lena.jpg");
if (img.empty()) {
return -1;
}
int height = img.rows;
int width = img.cols;
int ch = img.channels();
dst = Mat::zeros(img.size(), img.type());
float α = (float)1.9;
float β = 10;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (ch == 1) {
int piexl = img.at<uchar>(y, x);
}
else if (ch == 3) {
if (img.depth() == CV_8U) {
Vec3b piexl = img.at<Vec3b>(y, x);
//对三个通道的像素点都做运算
dst.at<Vec3b>(y, x)[0] = saturate_cast<uchar>(α * piexl[0] + β);
dst.at<Vec3b>(y, x)[1] = saturate_cast<uchar>(α * piexl[1] + β);
dst.at<Vec3b>(y, x)[2] = saturate_cast<uchar>(α * piexl[2] + β);
}
}
}
}
namedWindow("img");
imshow("img", img);
namedWindow("dst");
imshow("dst", dst);
waitKey(0);
return 0;
}
8.绘制形状与文字
8.1使用cv::Point 与 cv::Scalar
Point表示2D平面上一个点x,y
Point p
p.x = 1; p.y=2;
或者
p = Point(1,2)
Scalar表示四个元素的向量
Scalar(b,g,r)
8.2绘制线矩形圆椭圆等基本几何形状
线: cv::line (LINE_4LINE_8LINE_AA)椭圆: cv::ellipse矩形: cv::rectangle圆: cv::circle填充多边形: cv::fillPoly
8.3随机生成与绘制文本
文本:putText随机数:RNG
8.4代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
void myLines(Mat img)
{
Point p1 = Point(20, 30);
Point p2 = Point(300, 300);
Scalar color = Scalar(0, 0, 255);
line(img, p1, p2, color, 1, LINE_8, 0);
}
void myRectangle(Mat img)
{
//Point p1 = Point(20, 20);
//Point p2 = Point(300, 300);
Rect rec = Rect(20, 20, 200, 200);
Scalar color = Scalar(0, 255, 0);
//rectangle(img, p1, p2, color, 1, LINE_8, 0);
rectangle(img, rec, color, 1, LINE_8, 0);
}
/********************************************************
img:输入输出图像,即要在其上绘制椭圆的图像
center:椭圆中心点的坐标,类型为cv::Point
axes:椭圆主轴尺寸,以半长轴和半短轴的大小表示
angle:椭圆的旋转角度,按逆时针方向计算
startAngle:椭圆弧的起始角度,按逆时针方向测量
endAngle:椭圆弧的结束角度,按逆时针方向测量
color:椭圆的颜色,使用cv::Scalar类型表示BGR颜色值
thickness:线条粗细,默认值为1。如果设为-1,则绘制实心椭圆
*********************************************************/
void myEllipse(Mat img)
{
ellipse(img, Point(150, 150), Size(50, 20), 20, 0, 180, Scalar(255, 0, 0), 1, LINE_8, 0);
}
void myCircle(Mat img)
{
circle(img, Point(150, 150), 20, Scalar(255, 0, 0), 1, LINE_8, 0);
}
void myFillPoly(Mat img)
{
vector<Point> pts = { Point(10, 70),Point(10, 200) ,Point(20, 70) ,Point(90, 90) ,Point(20, 100) };
fillPoly(img, pts, Scalar(255, 0, 0), LINE_8);
}
void RandomLineDemo(Mat img)
{
RNG rng(time(NULL));//时间作为随机数种子
Point pt1, pt2;
for (int i = 0; i < 100; i++) {
pt1.x = rng.uniform(0, img.cols);
pt1.y = rng.uniform(0, img.rows);
pt2.x = rng.uniform(0, img.cols);
pt2.y = rng.uniform(0, img.rows);
Scalar color = (rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
line(img, pt1, pt2, color);
}
}
int main(int argc, char* argv[])
{
Mat dst;
Mat img(400, 400, CV_8UC3, Scalar(0, 0, 0));
if (img.empty()) {
return -1;
}
dst = img.clone();
myLines(dst);
myRectangle(dst);
myEllipse(dst);
myCircle(dst);
myFillPoly(dst);
//书写文字
putText(dst, "Hello", Point(300, 300),FONT_HERSHEY_PLAIN, 1.0, Scalar(0,255,255));
//随机画线
RandomLineDemo(dst);
namedWindow("dst");
imshow("dst", dst);
waitKey(0);
return 0;
}
9.模糊图像
9.1模糊原理
Smoothlur是图像处理中最简单和常用的操作之一使用该操作的原因之一是为了给图像处理的时候降噪使用smoothlur操作的背后是数学的卷积运算通常这些卷积算子都是线性操作,所以又叫线型滤波
卷积过程:
假如在一个6 x 6 的窗口上面,有一个3 x 3的kernel ,从左往右,从上往下移动,中心点的值等于附近点值的和取平均值,
这个过程称之为均值滤波
模糊处理的方法:
均值滤波和高斯滤波:区别是计算的权重不一样.
9.2 相关API
void blur( InputArray src, OutputArray dst,Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT);
InputArray src:输入图像矩阵
OutputArray dst:模糊处理后输出的图像矩阵
Size ksize:卷积核尺寸,Size(x,y)中x和y取值越大模糊程度越深,且必须为奇数
GaussianBlur( InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT,
AlgorithmHint hint = cv::ALGO_HINT_DEFAULT );
参数:
src:输入图像,支持CV_8U、CV_16U、CV_16S、CV_32F、CV_64F等数据类型的任意通道数图像。
dst:输出图像,与输入图像具有相同的尺寸、通道数和数据类型。
ksize:高斯内核大小,必须为正奇数(如3×3、5×5等)。当尺寸设为0时,会根据标准偏差自动计算尺寸。
sigmaX:X方向的高斯核标准偏差,决定模糊程度。
sigmaY:Y方向的高斯核标准偏差,默认值为0,此时与sigmaX相同。
borderType:像素外推法选择标志,默认使用BORDER_DEFAULT边界处理方式
1. 核大小(ksize)
轻度降噪:3×3或5×5核,保留较多细节
强降噪:7×7及以上核,但可能导致边缘模糊
必须为奇数:确保中心对称性(如3×3、5×5)
标准差(sigmaX/sigmaY)
sigmaX:控制模糊强度,值越大模糊越强。若ksize非零,sigmaX通常为ksize宽度的0.3倍+0.8(如5×5核对应sigmaX≈1.5
sigmaY:默认与sigmaX相同,若需非对称模糊可单独设
自动计算:当ksize=0时,sigmaX/sigmaY决定核大小
9.3代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat src, dst1, dst2;
src = imread("E:/work/opencv/wokespace/images/lena.jpg");
if (src.empty()) {
return -1;
}
//均值模糊
blur(src, dst1, Size(3,3), Point(-1, -1), BORDER_DEFAULT);
//高斯模糊
GaussianBlur(src, dst2, Size(3,3), 0.5, 0.5,BORDER_DEFAULT, cv::ALGO_HINT_DEFAULT);
namedWindow("src");
imshow("src", src);
namedWindow("dst1");
imshow("dst1", dst1);
namedWindow("dst2");
imshow("dst2", dst2);
waitKey(0);
return 0;
}
10.模糊图像二
10.1中值滤波
统计排序滤波器中值滤波对椒盐噪声(图像上有不规则的黑点白点)有很好的抑制作用卷积kernel周围的元素大小做升序排列,中间的值作为 中心的值,能够有效去除0和255这样的值
10.2高斯双边滤波(美颜)
所取的kernel关于中心,的权重是对称的
均值模糊无法克服边缘像素信息丢失缺陷.原因是均值滤波是基于平均权重高斯模糊部分克服了该缺陷,但是无法完全避免,因为没有考虑像素值的不同高斯双边模糊:是边缘保留的滤波方法,避免了边缘信息丢失,保留了图像轮廓不变
10.3相关API
void medianBlur( InputArray src, OutputArray dst, int ksize );
功能:
主要用于去除图像中的椒盐噪声等脉冲噪声,同时能够较好地保留图像边缘信息
参数:
src:输入图像,支持1、3或4通道的图像。当ksize为3或5时,图像深度可以为CV_8U、CV_16U或CV_32F;对于较大孔径尺寸的图像,深度只能是CV_8U。
dst:输出图像,与输入图像具有相同的尺寸和通道数。
ksize:孔径的线性尺寸,必须是大于1的奇数(如3、5、7等)
void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType = BORDER_DEFAULT );
功能:
双边滤波是一种非线性滤波技术,能够在去噪的同时有效保留图像边缘细节;该函数通过结合空间邻近度和像素值相似度来实现保边去噪效果
参数详解:
src:输入图像,支持8位或浮点型单通道、三通道图像
dst:输出图像,尺寸和类型与源图像相同
d:滤波过程中每个像素邻域的直径。若设为非正数,OpenCV会从sigmaSpace参数自动计算该值
sigmaColor:颜色空间的标准差,控制颜色相似性范围。值越大,更多颜色差异的像素被纳入考虑
sigmaSpace:坐标空间的标准差,控制空间邻近度。值越大,更远距离的像素会产生影响
borderType:边界处理模式,默认为BORDER_DEFAULT
参数配置建议:
邻域直径(d)
轻度滤波:d=5-9
中度滤波:d=10-15
强度滤波:d>15
标准差参数
sigmaColor:通常设置为25-75,值越大颜色混合范围越广
sigmaSpace:通常设置为25-75,值越大空间影响范围越大
10.4代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat src, dst1, dst2;
src = imread("E:/work/opencv/wokespace/images/lena.jpg");
if (src.empty()) {
return -1;
}
//中值模糊
medianBlur(src, dst1, 3);
//高斯双边模糊
bilateralFilter(src, dst2, 5, 50, 50, 4);
namedWindow("src");
imshow("src", src);
namedWindow("dst1");
imshow("dst1", dst1);
namedWindow("dst2");
imshow("dst2", dst2);
waitKey(0);
return 0;
}
11.腐蚀与膨胀
11.1 腐蚀膨胀原理
形态学操作
图像形态学:基于形状的一系列图像处理操作的合集,主要基于集合论基础上的形态学数学形态学有四个操作:腐蚀膨胀开闭膨胀与腐蚀是图像处理中最常用的形态学操作手段
膨胀:
跟卷积操作类似,假设有图像A和结构元素B,结构元素B在A上移动,其中B定义其中心为锚点,计算B覆盖下A的最大像素值用来替换锚点的像素,其中B作为结构体可以使任意形状.
腐蚀:
腐蚀和膨胀操作的过程类似,唯一不同的是以最小值替换锚点重叠下图像的像素值.
11.2 相关API
Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
getStructuringElement 是 OpenCV 中用于生成形态学操作所需结构元素(卷积核)的核心函数。
函数参数详解
shape:结构元素的形状,有三种基础形状可选:
MORPH_RECT:矩形元素,生成实心矩形结构元素,所有元素值均为1
MORPH_CROSS:十字形元素,仅中心点及水平、垂直方向的元素为1
MORPH_ELLIPSE:椭圆形元素,元素值为1的区域呈椭圆分布
ksize:结构元素的尺寸,以 Size(width, height) 格式指定。该参数定义了结构元素的最小外接矩形,形状内的值均为1。
anchor:锚点位置,默认为 Point(-1,-1),表示自动选择结构元素的中心作为锚点。锚点主要影响形态学运算结果的偏移。
返回值与应用
函数返回一个 cv::Mat 类型的矩阵,表示生成的结构元素。该结构元素主要用于以下形态学操作函数:
erode():图像腐蚀
dilate():图像膨胀
morphologyEx():高级形态学操作(开运算、闭运算、梯度等)
结构元素示例
void erode( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
用结构元素扫描图像,若覆盖区域存在背景像素(0),则中心像素置为0;仅当所有覆盖像素均为前景(1)时,中心像素才保留为1;
void dilate( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
用结构元素扫描图像,若覆盖区域存在背景像素(1),则中心像素置为1;仅当所有覆盖像素均为前景(0)时,中心像素才保留为0;
动态调整结构元素大小:
int createTrackbar(const String& trackbarname, const String& winname,
int* value, int count,
TrackbarCallback onChange = 0,
void* userdata = 0);
createTrackbar 函数用于在 OpenCV 的指定窗口中创建一个滑动条,方便用户通过拖动滑块来实时调整参数。
其参数含义如下:
trackbarname:滑动条的名称
winname:滑动条所依附的窗口名称,此窗口需通过 namedWindow 提前创建或由 imshow 自动生成
value:指向整型变量的指针,用于反映滑块的当前位置。创建时滑块的初始位置由该变量值决定,滑动过程中该值会随滑块位置实时更新
count:滑块的最大位置值,最小位置值固定为 0
onChange:回调函数指针(可选)。当滑块位置改变时,会自动调用此函数。函数原型需为 void Foo(int, void*),其中第一个参数是滑块位置,第二个参数是用户数据
userdata:传递给回调函数的用户数据(可选)。如果 value 是全局变量,通常可忽略此参数
使用要点:
若指定了回调函数,建议在 createTrackbar 后手动调用一次,以初始化显示
滑动条依附的窗口必须存在,可通过 namedWindow 显式创建或依赖 imshow
可通过 getTrackbarPos 函数获取滑动条的当前值
11.3 代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, dst;
int elements_size = 3;
int max_size = 21;
void onChange(int pos, void* userdata)
{
int s = pos + 1;
Mat kernel = getStructuringElement(MORPH_RECT, Size(s, s));
//腐蚀变大
//erode(src, dst, kernel);
//膨胀变小
dilate(src, dst, kernel);
imshow("dst", dst);
}
int main(int argc, char* argv[])
{
src = imread("E:/work/opencv/wokespace/images/lena.jpg");
if (src.empty()) {
return -1;
}
namedWindow("src");
imshow("src", src);
namedWindow("dst");
createTrackbar("toobar", "dst", &elements_size, max_size, onChange);
onChange(0, 0);
waitKey(0);
return 0;
}
12.形态学操作
12.1 操作类型
开操作-open
先腐蚀后膨胀可以去掉小的对象,假设对象是前景色,背景色是黑色
闭操作-close
先膨胀后腐蚀可以填充小的洞,假设对象是前景色,背景色是黑色
形态学梯度-Morphological Gradient
膨胀减去腐蚀又称为基本梯度(其他还包括-内部梯度方向梯度)
顶帽-top hat
原图像与开操作之间的差值
黑帽-black hat
原图像与闭操作之间的差值
12.2相关API
void morphologyEx(InputArray src, OutputArray dst,
int op, InputArray kernel,
Point anchor = Point(-1, -1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue());
参数:
src:原图像
dst:目标图像
op: 形态学操作类型(MORPH_OPENMORPH_CLOSEMORPH_GRADIENTMORPH_TOPHATMORPH_BLACKHAT)
kernel:结构元素,作为形态学滤波的"滤波器",可由线条、矩形、圆等特殊形状构成
iterations:迭代次数
12.3代码演示
Chi-Square

Bhattacharyya

Intersection

26.2相关API
首先把图像从RGB色彩空间转换到HSV色彩空间(cvtColor)
计算图像的直方图,然后归一化到[0~1]之间(calcHist和normalize)
使用上述四种方法之一进行比较
double compareHist( InputArray H1, InputArray H2, int method );
功能:用于比较两个直方图的相似度
参数:
H1:第一个待比较的直方图数据
H2:第二个待比较的直方图数据,必须和H1具有相同的尺寸
method:比较方法标志 0-HISTCMP_COREL-相关性比较法 1-HISTCMP_CHISQR-卡方比较法
26.3代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int main() {
Mat src1, src2;
// 读取源图像
src1 = imread("E:/work/opencv/wokespace/images/lena.jpg");
src2 = imread("E:/work/opencv/wokespace/images/lena_noise.jpg");
if (src1.empty() || src2.empty()) {
cout << "无法加载图像" << endl;
return -1;
}
cvtColor(src1, src1, COLOR_BGR2HSV); //H(色相):[0, 179], S(饱和度):[0, 255], v(明度):[0, 255]
cvtColor(src2, src2, COLOR_BGR2HSV);
int h_bins = 50; int s_bins = 60;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0, 179 };
float s_ranges[] = { 0, 255 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
MatND hist_src1;
MatND hist_src2;
calcHist(&src1, 1, channels, Mat(), hist_src1, 2, histSize, ranges, true, false);
calcHist(&src2, 1, channels, Mat(), hist_src2, 2, histSize, ranges, true, false);
normalize(hist_src1, hist_src1, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist_src2, hist_src2, 0, 1, NORM_MINMAX, -1, Mat());
double value1 = compareHist(hist_src1, hist_src1, HISTCMP_BHATTACHARYYA);
double value2 = compareHist(hist_src1, hist_src2, HISTCMP_BHATTACHARYYA);
printf("<%lf><%lf>
", value1, value2);
return 0;
}
27.模版匹配
27.1模版匹配介绍
模版匹配就是在整个图像区域发现与给定的子图像匹配的小块区域.所以模版匹配首先需要一个模版图像T(给定的子图像)另外需要一个待检测的图像–原图像S工作方法,在待检测图像上,从左到右,从上到下,计算T与S重叠子图像的匹配度,匹配度越大,两者相同的可能性越大
匹配算法:
1.平方差匹配类
TM_SQDIFF:平方差匹配,最佳匹配结果为0,数值越大匹配越差
TM_SQDIFF_NORMED:归一化平方差匹配,原理相同但进行了归一化处理
2. 相关匹配类
TM_CCORR:相关匹配,采用模板和图像间的乘法操作。最佳匹配是较大的数,最坏匹配为0。
3. 相关系数匹配类
TM_CCOEFF:相关系数匹配,将模板对其均值的相对值与图像对其均值的相关值进行匹配。1表示完美匹配,-1表示糟糕匹配,0表示无相关性。
TM_CCOEFF_NORMED:归一化相关系数匹配,这是最常用的方法之一。
使用流程:
调用matchTemplate计算相似度得分矩阵
使用minMaxLoc找到最佳匹配位置
对于平方差方法,取最小值位置
对于相关方法,取最大值位置
实际应用建议:
新手推荐:先从TM_CCOEFF_NORMED(归一化相关系数匹配法)开始尝试,它对光照变化具有较好的适应性。如果匹配效果不理想,再尝试其他归一化算法进行调整优化。
27.2相关API
void matchTemplate( InputArray image, InputArray templ,
OutputArray result, int method, InputArray mask = noArray() );
核心参数说明:
image:待搜索的原始图像,必须是8位或32位浮点型
templ:用于匹配的模板图像,尺寸需小于等于原始图像,且类型相同
resule:存储匹配结果的矩阵,尺寸为(W-w+1, H-h+1)
method:匹配方法,OpenCV提供6种不同的算法
| 方法类型 | 方法名称 | 最佳匹配值 | 匹配效果判断 |
|---|---|---|---|
| 平方差匹配 | TM_SQDIFF | 0 | 值越小匹配越好 |
| 归一化平方差 | TM_SQDIFF_NORMED | 0 | 值越接近0效果越好 |
| 相关匹配 | TM_CCORR | 较大数值 | 值越大匹配越好 |
| 归一化相关匹配 | TM_CCORR_NORMED | 1 | 越接近1效果越好 |
| 相关系数匹配 | TM_CCOEFF | 1 | 正值越大匹配越好 |
| 归一化相关系数 | TM_CCOEFF_NORMED | 1 | 越接近1效果越好 |
27.3代码示例
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
// 多目标匹配函数
void multiTemplateMatching(Mat& img, Mat& template_img, double threshold = 0.8) {
Mat result;
matchTemplate(img, template_img, result, TM_CCOEFF_NORMED);
// 查找所有超过阈值的匹配位置
vector<Point> locations;
for (int i = 0; i < result.rows; i++) {
for (int j = 0; j < result.cols; j++) {
if (result.at<float>(i, j) >= threshold) {
locations.push_back(Point(j, i));
}
}
}
// 标记所有匹配区域
for (const auto& loc : locations) {
rectangle(img, loc, Point(loc.x + template_img.cols, loc.y + template_img.rows),
Scalar(0, 255, 0), 2);
}
imshow("多目标匹配结果", img);
}
int main(int argc, char* argv[])
{
Mat img, template_img, result, dst;
int methods = TM_CCOEFF_NORMED;
img = imread("E:/work/opencv/wokespace/images/apple.jpg");
template_img = imread("E:/work/opencv/wokespace/images/apple_template.jpg");
if (img.empty() || template_img.empty()) {
return -1;
}
int result_cols = img.cols - template_img.cols + 1;
int result_rows = img.rows - template_img.rows + 1;
result = Mat(result_rows, result_cols, CV_32FC1);
matchTemplate(img, template_img, result, methods);
//normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());//采用归一化算法的,无需再归一化
Point minLoc, maxLoc, tempLoc;
double minVal, maxVal, tempVal;
//计算匹配分数和匹配位置
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
if ((methods == TM_SQDIFF) || (methods == TM_SQDIFF_NORMED)) {
tempLoc = minLoc;
tempVal = minVal;
}
else {
tempLoc = maxLoc;
tempVal = maxVal;
}
img.copyTo(dst);
rectangle(result, Rect(tempLoc.x, tempLoc.y, template_img.cols, template_img.rows), Scalar(0, 0, 255), 1, LINE_8, 0);
rectangle(dst, tempLoc, Point(tempLoc.x + template_img.cols, tempLoc.y + template_img.rows),Scalar(0, 0, 255), 2);
//imshow("img:", img);
imshow("dst:", dst);
imshow("result:", result);
cout << "最佳匹配分数: " << tempVal << endl;
cout << "匹配位置: (" << tempLoc.x << ", " << tempLoc.y << ")" << endl;
waitKey(0);
return 0;
}
28.轮廓发现
28.1 简介
轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法,所以边缘提取的阈值会影响最终轮廓发现的结果.
28.2相关API
void findContours( InputArray image, OutputArrayOfArrays contours,
int mode, int method, Point offset = Point());
功能:从二值图像中提取轮廓
参数:
image:输入的二值图像(8位单通道),非零像素视为前景
contours:输出的轮廓集合,每个轮廓存储为点的向量
mode:轮廓检索模式,决定轮廓的层级关系
method:轮廓近似方法,控制轮廓点的存储方式
offset:可选偏移量,将所有轮廓点平移指定偏移
轮廓检索模式(mode):
RETR_EXTERNAL:只检索最外层轮廓
RETR_LIST:检索所有轮廓,不建立层级关系
RETR_CCOMP:检索所有轮廓,组织为两级层次结构
RETR_TREE:检索所有轮廓,重建完整的轮廓层级
轮廓近似方法(method):
CHAIN_APPROX_NONE:存储所有轮廓点
CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,只保留端点
CHAIN_APPROX_TC89_L1/TC89_KCOS:使用Teh-Chin链近似算法
void drawContours( InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color,
int thickness = 1, int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX, Point offset = Point() );
功能:在图像上绘制检测到的轮廓
参数:
image:输入输出图像,轮廓将绘制在此图像上
contours:从findContours获取的轮廓集合
contourIdx:要绘制的轮廓索引,负数表示绘制所有轮廓
color:轮廓颜色,使用Scalar(B, G, R)格式
thickness:线条粗细,负数表示填充轮廓内部
lineType:线条类型(LINE_8、LINE_4、LINE_AA)
hierarchy:可选的轮廓层级信息
maxLevel:绘制的最大轮廓层级
offset:轮廓点的偏移量
28.3代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, dst;
int t = 150;
void barCallback(int pos, void* userdata)
{
double t2 = 0;
double t1 = 0;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Mat temp;
t2 = pos + 1;
t1 = t2 / 2;
Canny(src, dst, t1, t2, 3, false);
findContours(dst, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
temp = Mat::zeros(src.size(), CV_8UC3);
drawContours(temp, contours, -1, Scalar(0, 255, 0), 2);
imshow("temp", temp);
}
int main(int argc, char* argv[])
{
src = imread("E:/work/opencv/wokespace/images/fish.jpg");
if (src.empty()) {
return -1;
}
namedWindow("temp"); namedWindow("dst");
//第二步转换为灰度图
cvtColor(src, dst, COLOR_BGR2GRAY);
imshow("dst", dst);
createTrackbar("laplacian", "dst", &t, 255, barCallback, 0);
barCallback(0, 0);
waitKey(0);
return 0;
}
29.凸包-Convex Hull
29.1 概念
在一个多边形边缘或者内部任意两个点的连线都包含在多边形边界或内部.
正式定义: 包含点集合s中所有点的最小凸多边形称为凸包
Graham(格雷厄姆)扫描算法
首先选择Y方向最低的点作为起始点p0从p0开始极坐标扫描依次添加p1…pn(排序顺序是根据极坐标的角度大小,逆时针方向)对每个点pi来说,如果添加pi到凸包中导致一个左转向(逆时针方法)则添加改点到凸包,反之则删除改点
29.2API说明
void convexHull(
InputArray points,
OutputArray hull,
bool clockwise = false,
bool returnPoints = true
);
功能:
计算一组 2D 点的凸包(即包含所有点的最小凸多边形)。
参数:
points:输入的 2D 点集。可以是 std::vector<Point>、std::vector<Point2f> 或 Mat(Nx2 或 2xN,类型为 CV_32S 或 CV_32F)。
hull:输出结果。有两种形式:
• 若 returnPoints=true(默认),则输出为点坐标(如 std::vector<Point>);
• 若 returnPoints=false,则输出为原始 points 中的索引(std::vector<int>)。
clockwise:凸包顶点的顺序:
• true:顺时针
• false(默认):逆时针(数学标准方向)
returnPoints:控制 hull 的输出内容:
• true(默认):返回凸包的实际点坐标
• false:返回这些点在输入 points 中的索引
注意:当输入 points 是 Mat 类型且 returnPoints=false 时,hull 的类型通常是 vector<int>;若 returnPoints=true,则 hull 是 vector<Point> 或 vector<Point2f>。
29.3代码演示
首先把图像从RGB转为灰度然后再转为二值图像再通过轮廓得到候选点凸包API调用绘制显示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, gray, thd, dst;
int threshold_val = 50;
void barCallback(int pos, void* userdata)
{
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
threshold_val = pos + 1;
//二值
threshold(gray, thd, threshold_val, 255, THRESH_BINARY);
//找出轮廓
findContours(thd, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
vector<vector<Point>> hulls(contours.size());
//计算凸包
for (int i = 0; i < contours.size(); i++) {
convexHull(contours[i], hulls[i], false, true);
}
dst = Mat::zeros(src.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++) {
drawContours(dst, contours, i, Scalar(0, 255, 0), 1, LINE_AA);//绘制轮廓
drawContours(dst, hulls, i, Scalar(0, 0, 255), 1, LINE_AA);//绘制凸包
}
imshow("dst", dst);
}
int main(int argc, char* argv[])
{
src = imread("E:/work/opencv/wokespace/images/hand.jpg");
if (src.empty()) {
return -1;
}
cvtColor(src, gray, COLOR_BGR2GRAY);//灰度图
blur(gray, gray, Size(3, 3));//滤波处理
namedWindow("dst");
createTrackbar("bar", "dst", &threshold_val, 255, barCallback, NULL);
barCallback(0, 0);
waitKey(0);
return 0;
}
30.轮廓周围绘制矩形框和圆形框
30.1API介绍
void approxPolyDP( InputArray curve,
OutputArray approxCurve,
double epsilon, bool closed );
是 OpenCV 中用于对多边形曲线进行近似(简化)的一个函数。它使用 Douglas-Peucker 算法 来减少曲线中的点数,同时尽可能保留原始形状。
参数说明:
curve:输入的轮廓或曲线,通常是一个 std::vector<Point> 或 Mat,表示一系列点(通常是通过 findContours 得到的轮廓)。
approxCurve:输出的近似曲线,类型与输入相同(如 std::vector<Point>),包含简化后的点集。
epsilon:
近似精度(即最大允许距离)。它是原始曲线与近似曲线之间的最大偏差。
值越大,得到的点越少,形状越粗糙;
值越小,越接近原始轮廓。
通常设置为轮廓周长的一个百分比(例如 0.02 * arcLength(curve, closed))。
closed:
布尔值,指定曲线是否闭合:
true:认为曲线是闭合的(首尾相连);
false:认为是开放曲线。
Rect boundingRect( InputArray array );
功能:
用于计算包含给定点集(如轮廓)的最小正立(即边与坐标轴平行)矩形。
参数:
array
输入的 2D 点集。通常是一个 std::vector<Point> 或 Mat,例如通过 findContours 得到的单个轮廓(contour)。
支持的数据类型:CV_32S(整型点)或 CV_32F(浮点点),但最终返回的 Rect 是整数坐标
RotatedRect minAreaRect( InputArray points );
功能:
用于计算给定点集的最小面积边界矩形(可能旋转)的函数。与 boundingRect 不同,minAreaRect 计算出来的矩形可以是倾斜的,因此它能更紧密地包围点集,特别适用于物体的方向和形状对分析很重要的场合。
参数:
points:
输入的二维点集。通常是一个 std::vector<cv::Point> 或 cv::Mat,例如通过轮廓检测 (findContours) 得到的轮廓。这些点代表了你想要包围的形状或对象。
返回值:
cv::RotatedRect:
包含以下成员:
.center: 矩形的中心点坐标。
.size: 矩形的尺寸 (width, height)。注意:宽度和高度可能是近似值,并且不一定对应于矩形的物理长宽比;它们依赖于点集的实际分布。
.angle: 该矩形相对于水平轴逆时针旋转的角度。当角度在0到-90度之间时,宽度大于高度;否则,若角度在-90到-180度之间,则高度大于宽度,同时意味着矩形实际上是“横向”摆放的。
void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius );
功能:
用于计算包含给定点集的最小外接圆的函数。该圆是能够完全包围所有输入点的面积最小的圆。
参数:
points:
输入的 2D 点集,通常是一个 std::vector<Point> 或 std::vector<Point2f>(也可以是 Mat)。
常见来源:通过 findContours 获取的轮廓。
center(输出):
最小外接圆的圆心坐标,类型为 cv::Point2f(浮点坐标)。
radius(输出):
最小外接圆的半径,单位与图像坐标一致,类型为 float。
30.2代码演示
首先将图像变为二值图像发现轮廓,找到图像轮廓通过相关API在轮廓点上找到包含矩形和圆,旋转矩形和圆绘制它们
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, gray, thd, dst;
int threshold_val = 69;
void barCallback(int pos, void* userdata)
{
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
threshold_val = pos + 1;
//二值
threshold(gray, thd, threshold_val, 255, THRESH_BINARY);
//找出轮廓
findContours(thd, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
vector<Rect> b_rect(contours.size());
vector<RotatedRect> m_rect(contours.size());
vector<Point2f> center(contours.size());
vector<float> radius(contours.size());
//计算矩形坐标
for (size_t i = 0; i < contours.size(); i++) {
vector<Point> approxCurve;
double arc = arcLength(contours[i], true) * 0.02; //轮廓周长
approxPolyDP(contours[i], approxCurve, arc, true);
//最小正立矩形
b_rect[i] = boundingRect(approxCurve);
//最小旋转矩形
m_rect[i] = minAreaRect(approxCurve);
//最小外接圆
minEnclosingCircle(approxCurve,center[i], radius[i]);
}
src.copyTo(dst);
//绘制矩形
for (size_t i = 0; i < contours.size(); i++) {
rectangle(dst, b_rect[i], Scalar(0, 255,0));
circle(dst, center[i], (int)radius[i], Scalar(0, 0, 255));
// 绘制最小旋转矩形
Point2f rectPoints[4];
m_rect[i].points(rectPoints);
for (int j = 0; j < 4; j++) {
line(dst, rectPoints[j], rectPoints[(j + 1) % 4], Scalar(255, 0, 0), 2);
}
}
imshow("dst", dst);
}
int main(int argc, char* argv[])
{
src = imread("E:/work/opencv/wokespace/images/coins.jpg");
if (src.empty()) {
return -1;
}
cvtColor(src, gray, COLOR_BGR2GRAY);//灰度图
blur(gray, gray, Size(3, 3));//滤波处理
imshow("gray", gray);
namedWindow("dst");
createTrackbar("bar", "dst", &threshold_val, 255, barCallback, NULL);
barCallback(25, 0);
waitKey(0);
return 0;
}
31.图像距
31.1图像距的概念
在图像处理和计算机视觉领域,“矩”(Moments)是一个重要的概念,它被广泛用于描述图像或图像中物体的形状特征。OpenCV 提供了计算图像矩的功能,这有助于进行对象识别、姿态估计、位置确定等任务。
矩的基本概念
矩可以理解为对图像像素值及其分布的一种数学描述。对于离散的数字图像而言,矩是关于图像像素强度函数的统计量。根据应用的不同,矩可以分为几种类型,包括空间矩、中心矩、归一化中心矩以及Hu矩等。
空间矩:最基础的一类矩,用于计算图像的质心(重心)。其中零阶矩与图像的总质量有关,一阶矩可用于找到图像的质心。
中心矩:基于从质心计算出的偏移量来计算,用来描述物体形状,不随平移变化。
归一化中心矩:通过除以一个合适的幂次的零阶矩来实现缩放不变性。
Hu矩:由7个矩组成,具有平移、旋转和尺度不变性,非常适合于模式识别和图像分析中的匹配操作。
31.2相关API
Moments moments( InputArray array, bool binaryImage = false );
功能:
用于计算**图像或轮廓的矩(Moments)**的核心函数。这些矩可用于提取形状的几何特征,如面积、质心、方向、甚至进行形状匹配
array:
输入数据,可以是:
• 二值图像(Mat,单通道)
• 轮廓点集(std::vector<Point> 或 std::vector<Point2f>)
binaryImage:
仅当 array 是图像时有效:
• true:将图像视为二值图像,所有非零像素值视为 1(推荐用于轮廓矩计算)
• false:使用像素的实际灰度值计算加权矩
最佳实践:
如果你传入的是轮廓(点集),binaryImage 参数被忽略,应设为任意值(通常不写)。
如果你传入的是二值图像并希望计算前景区域的几何矩(而非灰度加权矩),务必设置 binaryImage=true。
double contourArea(InputArray contour, bool oriented = false);
功能:
用于计算轮廓所围成区域的面积的函数。它是形状分析、目标检测和图像测量中的基础工具。
double arcLength( InputArray curve, bool closed );
功能:
用于计算**轮廓或曲线周长(或弧长)**的函数,广泛应用于形状分析、轮廓简化(如 approxPolyDP)、圆形度计算等任务。
| 符号 | 数学表达式 | 物理/集合意义 |
|---|---|---|
| m00 | ∑1 | 总质量 / 面积 • 二值图中 = 前景像素总数 • 轮廓中 ≈ 包围区域面积(与 contourArea 一致) |
| m10 | ∑x | x 方向的一阶矩 |
| m01 | ∑y | y方向的一阶距 |
| m20 | ∑x² | x² 的加权和,用于计算方差、惯性矩 |
| m11 | ∑xy | xy 的加权和,反映 x 与 y 的相关性 |
| m02 | ∑y² | y²的加权和 |
| m30 | ∑x³ | 三阶矩,用于偏度(skewness)分析 |
| m21 | ∑x²y | 混合三阶矩 |
| m12 | ∑xy² | 混合三阶矩 |
| m03 | ∑y³ | 三阶矩 |
31.3代码演示
提取图像边缘发现轮廓计算每个轮廓对象的距计算每个对象的中心弧长面积
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, gray, thd, dst;
int threshold_val = 69;
void barCallback(int pos, void* userdata)
{
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
threshold_val = pos + 1;
//二值
//threshold(gray, thd, threshold_val, 255, THRESH_BINARY);
Canny(gray, thd, threshold_val, threshold_val * 2);
//找出轮廓
findContours(thd, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
//Moments moments(InputArray array, bool binaryImage = false);
//double contourArea(InputArray contour, bool oriented = false);
//arcLength
//计算图像距
vector<Moments> m(contours.size());
vector<Point2i> ccs(contours.size()); //中心位置
for (size_t i = 0; i < contours.size(); i++) {
m[i] = moments(contours[i]);
ccs[i] = Point(static_cast<int>(m[i].m10 / m[i].m00), static_cast<int>(m[i].m01 / m[i].m00));
}
src.copyTo(dst);
//画出中心位置
for (size_t i = 0; i < contours.size(); i++) {
circle(dst, ccs[i], 1, Scalar(0, 0, 255), 2);
}
//打印周长和面积
for (size_t i = 0; i < contours.size(); i++) {
printf("S = %f, C= %f
", contourArea(contours[i]), arcLength(contours[i], true));
printf("S = %f
", m[i].m00);
}
imshow("dst", dst);
}
int main(int argc, char* argv[])
{
src = imread("E:/work/opencv/wokespace/images/coins.jpg");
if (src.empty()) {
return -1;
}
cvtColor(src, gray, COLOR_BGR2GRAY);//灰度图
GaussianBlur(gray, gray, Size(3, 3), 0, 0);
imshow("gray", gray);
namedWindow("dst");
createTrackbar("bar", "dst", &threshold_val, 255, barCallback, NULL);
barCallback(25, 0);
waitKey(0);
return 0;
}
32.点多边形测试
32.1概念
测试一个点在给定的多边形内部,边缘或者外部
32.2API
double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist);
功能:
用于判断一个点与多边形(或轮廓)之间的位置关系,并可选地计算该点到多边形边界的最短距离。
参数说明:
contour:
输入的轮廓(多边形),通常是一个 std::vector<cv::Point> 或 cv::Mat 类型,表示闭合的多边形轮廓。
pt:
要测试的点,类型为 cv::Point2f(浮点坐标)。
measureDist:
如果为 true:函数返回点 pt 到轮廓边界的有符号最短距离(带正负号的距离)。
正值:点在轮廓内部
负值:点在轮廓外部
零:点在轮廓边界上
如果为 false:函数仅返回一个符号值,不计算实际距离:
1:点在内部
-1:点在外部
0:点在边界上
返回值:
当 measureDist == true:返回有符号的欧氏距离(单位:像素)。
当 measureDist == false:返回 1、0 或 -1。
32.3代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
Mat src, gray, thd, dst;
int threshold_val = 69;
void barCallback(int pos, void* userdata)
{
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
int rc = 0;
threshold_val = pos + 1;
//二值
//threshold(gray, thd, threshold_val, 255, THRESH_BINARY);
Canny(gray, thd, threshold_val, threshold_val * 2);
//找出轮廓
findContours(thd, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
Point2f pt(79, 354);
dst = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++) {
rc = pointPolygonTest(contours[i], pt, true);
if (rc > 0) {
printf("点在 轮廓 %d 的内部, 最短距离 = %d
", (int)i, rc);
}
else if (rc == 0) {
printf("点在 轮廓 %d 的边缘上
", (int)i);
}
else if (rc < 0) {
printf("点在 轮廓 %d 的外部, 最短距离 = %d
", (int)i, rc);
}
}
drawContours(dst, contours, -1, Scalar(0, 255, 0), 2);
imshow("dst", dst);
}
int main(int argc, char* argv[])
{
src = imread("E:/work/opencv/wokespace/images/test1.jpg");
if (src.empty()) {
return -1;
}
cvtColor(src, gray, COLOR_BGR2GRAY);//灰度图
GaussianBlur(gray, gray, Size(3, 3), 0, 0);
imshow("gray", gray);
namedWindow("dst");
createTrackbar("bar", "dst", &threshold_val, 255, barCallback, NULL);
barCallback(25, 0);
waitKey(0);
return 0;
}
33.基于距离变换与分水岭的图像分割
33.1图像分割概念
图像分割是图像处理最重要的处理手段之一图像分割的目的是将图像中的像素根据一定的规则分为若干个cluster(集合),每个集合包括一类像素根据算法分为监督学习方法和无监督学习方法,图像分割算法多数都是无监督学习方法-KMeans
33.2距离变换与分水岭概念
距离变换是一种将二值图像转换为灰度图像的操作,其中每个前景像素(通常为白色,值为255)被替换为其到最近背景像素(黑色,值为0)的欧几里得(或其他度量)距离
想象一幅地形图,灰度值代表海拔高度。向每个“盆地”注水,水从低处向高处蔓延,当不同区域的水相遇时,就形成“分水岭”——即分割边界。
在图像中:
低谷(minima) → 对象内部
山脊(ridges) → 对象边界
33.3相关API
void distanceTransform(InputArray src, OutputArray dst, int distanceType, int maskSize);
参数:
src:输入的二值图像(单通道,8位)
dst:输出的距离图像(32位浮点型)
distanceType:距离类型,如 DIST_L2(欧氏距离)、DIST_L1(曼哈顿距离)等
maskSize:距离变换的掩码大小,通常为 3 或 5;对于 DIST_L2 可设为 0 表示精确计算
void watershed(InputArray image, InputOutputArray markers);
参数:
image:原始彩色图像(3通道),用于可视化结果
markers:标记图像(32位有符号整型),不同区域用不同正整数标记,未知区域标记为 -1
33.4代码演示
处理流程:
将白色背景变为黑色,目的是为后面的变换做准备使用filter2D与拉普拉斯算子实现图像对比度的提高,sharp转为二值图像 threshold距离变换对距离变换结果归一化到[0`1]之间使用阈值,再次二值化,得到标记腐蚀得到每个Peak-erode发现轮廓-findContours绘制轮廓-drawContours分水岭变换watershed对每个分割区域着色输出结果
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat src;
src = imread("E:/work/opencv/wokespace/images/Ace.png");
if (src.empty()) {
return -1;
}
// 1. 白色背景变黑(用 inRange)
// 转换为HSV颜色空间
Mat hsv;
cvtColor(src, hsv, COLOR_BGR2HSV);
// 定义HSV中的背景颜色范围(这里以白色为例)
// 注意:这些数值可能需要根据你的具体图片进行调整
Scalar lower_white = Scalar(0, 0, 200); // H: 0~180, S: 0~255, V: 0~255
Scalar upper_white = Scalar(180, 30, 255);
// 创建掩膜
Mat mask;
inRange(hsv, lower_white, upper_white, mask);
// 将原图中对应mask为背景的部分设置为黑色
src.setTo(Scalar(0, 0, 0), mask);
// 2. 锐化
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
Mat laplace;
filter2D(src, laplace, CV_32F, kernel);
Mat src_f;
src.convertTo(src_f, CV_32F);
Mat sharpen = src_f - laplace;
sharpen.convertTo(sharpen, CV_8UC3);
// 3. 二值化
Mat gray, binary;
cvtColor(sharpen, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
// 4. 距离变换
Mat dist;
distanceTransform(binary, dist, DIST_L2, 3);
normalize(dist, dist, 0, 1, NORM_MINMAX);
// 5-7. 标记生成
threshold(dist, dist, 0.4, 1, THRESH_BINARY);
Mat k = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
erode(dist, dist, k);
Mat dist_8u;
dist.convertTo(dist_8u, CV_8U);
// 8-9. 轮廓 → 标记图
vector<vector<Point>> contours;
findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
Mat markers = Mat::zeros(dist_8u.size(), CV_32SC1);
for (size_t i = 0; i < contours.size(); i++) {
drawContours(markers, contours, (int)i, Scalar((int)i + 1), -1);
}
// 10. 分水岭(关键!第一个参数是原图)
Mat src_copy = src.clone(); // 3通道BGR
watershed(src_copy, markers); // markers 被修改为分水岭结果
// 11. 可视化结果
Mat watershed_vis = Mat::zeros(markers.size(), CV_8UC3);
for (int i = 0; i < markers.rows; i++) {
for (int j = 0; j < markers.cols; j++) {
int label = markers.at<int>(i, j);
if (label == -1) watershed_vis.at<Vec3b>(i, j) = Vec3b(255, 255, 255); // 边界
else if (label > 0) {
watershed_vis.at<Vec3b>(i, j) = Vec3b(
(uchar)(label * 10 % 256),
(uchar)(label * 30 % 256),
(uchar)(label * 50 % 256)
);
}
}
}
imshow("Watershed", watershed_vis);
waitKey(0);
return 0;
}

