OpenCV4 快速入门 学习笔记分享【第七章】
本人正在学习 OpenCV 计算机视觉相关知识,此篇为个人学习笔记,参考书籍为冯振等人的《OpenCV4 快速入门》,主要记录 OpenCV 在 Linux 环境下的编译、运行实操流程与相关知识点总结。
笔记内容均为亲自实操验证,过程中踩过不少编译配置、运行参数的坑,整理出来一方面用于自己复盘巩固,另一方面也希望能帮助到刚入门 OpenCV、在编译运行示例程序时遇到问题的小伙伴。文中内容如有疏漏或错误之处,欢迎各位大佬评论区指正交流,共同进步~
第七章 目标检测
7.1 形状检测
7.1.1 直线检测
- 霍夫变换:将图像中的像素在一个空间坐标系中变换到另一个空间坐标系中,使得在这两个空间中形成峰值,成为统计峰值的问题。
- 优点:抗干扰能力强 缺点:时间复杂度和空间复杂度高
- 步骤:1.将坐标轴离散化 2. 将图像中每个非零像素通过映射关系求取参数空间通过的方格
3. 统计参数空间内每个方格出现的次数 4. 将参数空间中表示直线的方格参数作为图像中直线的参数
- 标准霍夫变换和多尺度霍夫变换 HoughLines()函数 ,,,无法知道直线的长度,只能得到图像中是否存在符合要求的直线
void cv::HoughLines(
InputArray image, // 输入图像,必须是CV_8U的单通道二值图像
OutputArray lines, // 输出直线参数
double rho, // ρ步长,以像素为单位的距离分辨率,数值越小,精度越高
double theta, // θ步长,以弧度为单位的角度分辨率,数值越小,精度越高
int threshold, // 投票数阈值(阈值越低,检测的直线越多)
double srn = 0, // ρ方向尺度除数(多尺度用,非负数)
double stn = 0, // θ方向尺度除数(多尺度用,非负数)
double min_theta = 0, // θ最小值
double max_theta = CV_PI // θ最大值
);
示例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void drawLine(Mat &img, //要标记直线的图像
vector<Vec2f> lines, //检测的直线数据
double rows, //原图像的行数(高)
double cols, //原图像的列数(宽)
Scalar scalar, //绘制直线的颜色
int n //绘制直线的线宽
)
{
Point pt1, pt2;
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0]; //直线距离坐标原点的距离
float theta = lines[i][1]; //直线过坐标原点垂线与x轴夹角
double a = cos(theta); //夹角的余弦值
double b = sin(theta); //夹角的正弦值
double x0 = a*rho, y0 = b*rho; //直线与过坐标原点的垂线的交点
double length = max(rows, cols); //图像高宽的最大值
//计算直线上的一点
pt1.x = cvRound(x0 + length * (-b));
pt1.y = cvRound(y0 + length * (a));
//计算直线上另一点
pt2.x = cvRound(x0 - length * (-b));
pt2.y = cvRound(y0 - length * (a));
//两点绘制一条直线
line(img, pt1, pt2, scalar, n);
}
}
int main()
{
Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat edge;
//检测边缘图像,并二值化
Canny(img, edge, 80, 180, 3, false);
threshold(edge, edge, 170, 255, THRESH_BINARY);
//用不同的累加器进行检测直线
vector<Vec2f> lines1, lines2;
HoughLines(edge, lines1, 1, CV_PI / 180, 50, 0, 0);
HoughLines(edge, lines2, 1, CV_PI / 180, 150, 0, 0);
//在原图像中绘制直线
Mat img1, img2;
img.copyTo(img1);
img.copyTo(img2);
drawLine(img1, lines1, edge.rows, edge.cols, Scalar(255), 2);
drawLine(img2, lines2, edge.rows, edge.cols, Scalar(255), 2);
//显示图像
imshow("edge", edge);
imshow("img", img);
imshow("img1", img1);
imshow("img2", img2);
waitKey(0);
return 0;
}
- 渐进概率式霍夫变换 HoughLinesP()函数
void cv::HoughLines(
InputArray image, // 输入图像,必须是CV_8U的单通道二值图像
OutputArray lines, // 输出直线参数
double rho, // ρ步长,以像素为单位的距离分辨率,数值越小,精度越高
double theta, // θ步长,以弧度为单位的角度分辨率,数值越小,精度越高
int threshold, // 投票数阈值(阈值越低,检测的直线越多)
double minLineLength = 0, // 直线的最小长度
double maxLineGap = 0 // 同一直线上相邻两个点之间的最大距离
);
示例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat edge;
//检测边缘图像,并二值化
Canny(img, edge, 80, 180, 3, false);
threshold(edge, edge, 170, 255, THRESH_BINARY);
//利用渐进概率式霍夫变换提取直线
vector<Vec4i> linesP1, linesP2;
HoughLinesP(edge, linesP1, 1, CV_PI / 180, 150, 30, 10); //两个点连接最大距离10
HoughLinesP(edge, linesP2, 1, CV_PI / 180, 150, 30, 30); //两个点连接最大距离30
//绘制两个点连接最大距离10直线检测结果
Mat img1;
img.copyTo(img1);
for (size_t i = 0; i < linesP1.size(); i++)
{
line(img1, Point(linesP1[i][0], linesP1[i][1]),
Point(linesP1[i][2], linesP1[i][3]), Scalar(255), 3);
}
//绘制两个点连接最大距离30直线检测结果
Mat img2;
img.copyTo(img2);
for (size_t i = 0; i < linesP2.size(); i++)
{
line(img2, Point(linesP2[i][0], linesP2[i][1]),
Point(linesP2[i][2], linesP2[i][3]), Scalar(255), 3);
}
//显示图像
imshow("img1", img1);
imshow("img2", img2);
waitKey(0);
return 0;
}
- 在含有坐标系众多点中寻找是否存在直线 HoughLinesPointSet()函数
7.1.2 直线拟合
将所有数据只拟合出一条直线
- 利用最小二乘M-estimator方法拟合直线的fitLine()函数
void cv::fitLine(
cv::InputArray points, // 输入点集(vector<Point>/Mat/Nx2矩阵)
cv::OutputArray line, // 输出直线参数(4维向量:vx, vy, x0, y0)
int distType, // 距离类型(拟合算法选择)
double param, // 距离参数(通常设0,使用默认值)
double reps, // 距离精度(通常设0.01)
double aeps // 角度精度(通常设0.01)
);

7.1.3 圆形检测 HoughCircles()函数
方法与直线检测相似
void cv::HoughCircles(
cv::InputArray image, // 输入图像,必须是CV_8UC1
cv::OutputArray circles, // 输出圆参数(x,y,r)
int method, // 检测方法(仅支持HOUGH_GRADIENT)
double dp, // 图像分辨率缩放因子
double minDist, // 圆心之间的最小距离
double param1 = 100, // Canny边缘检测的高阈值
double param2 = 100, // 圆心投票阈值(核心),阈值越大,检测的越精确
int minRadius = 0, // 最小圆半径
int maxRadius = 0 // 最大圆半径
);
7.2 轮廓检测 findContours()函数
由外到内轮廓的级别越来越低
- 检测轮廓函数
void cv::findContours(
cv::InputArray image, // 输入图像(二值图或单通道灰度图)
cv::OutputArrayOfArrays contours, // 输出轮廓集合
cv::OutputArray hierarchy, // 输出轮廓层级(可选)
int mode, // 轮廓检索模式
int method, // 轮廓逼近方法
cv::Point offset = cv::Point() // 轮廓点偏移量(默认无偏移)
);

- 显示轮廓函数 drawContours()函数
void cv::drawContours(
InputOutputArray image, // 要绘制轮廓的目标图像(彩色/灰度均可)
InputArrayOfArrays contours, // 轮廓集合(来自findContours())
int contourIdx, // 要绘制的轮廓数目,负数表示绘制所有轮廓
const Scalar& color, // 轮廓绘制颜色
int thickness = 1, // 线宽(填充用-1)
int lineType = LINE_8, // 线条类型(默认即可)
InputArray hierarchy = noArray(), // 轮廓层级(可选,用于层级过滤)
int maxLevel = INT_MAX, // 绘制的轮廓层级深度(可选)
Point offset = Point() // 轮廓点坐标偏移(0绘制指定轮廓;1绘制轮廓+嵌套轮廓
// 2绘制轮廓+嵌套轮廓+嵌套的嵌套轮廓)
);
示例:
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
Mat img = imread("keys.jpg");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
imshow("原图", img);
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY); //转化成灰度图
GaussianBlur(gray, gray, Size(13, 13), 4, 4); //平滑滤波
threshold(gray, binary, 170, 255, THRESH_BINARY | THRESH_OTSU); //自适应二值化
// 轮廓发现与绘制
vector<vector<Point>> contours; //轮廓
vector<Vec4i> hierarchy; //存放轮廓结构变量
findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
//绘制轮廓
for (int t = 0; t < contours.size(); t++)
{
drawContours(img, contours, t, Scalar(0, 0, 255), 2, 8);
}
//输出轮廓结构描述
for (int i = 0; i < hierarchy.size(); i++)
{
cout << hierarchy[i] << endl;
}
//显示结果
imshow("轮廓检测结果", img);
waitKey(0);
return 0;
}
7.2.2 轮廓面积 contourArea()函数
定义:指每个轮廓中所有像素点围成的区域面积
double cv::contourArea(
cv::InputArray contour, // 输入单个轮廓(点集合)
bool oriented = false // 是否返回有方向的面积(正/负)
);
7.2.3 轮廓周长 arcLength()函数
轮廓或曲线相邻两个像素点之间的连线距离
double cv::arcLength(
cv::InputArray curve, // 输入轮廓/曲线(点集合)
bool closed // 是否为闭合轮廓(true表示闭合)
);
7.2.4 轮廓外接多边形
- 求取轮廓最大外接矩形 boundingRect()函数
cv::Rect cv::boundingRect(cv::InputArray array) // 输入轮廓/点集合);
- 求取轮廓最小外接矩形 minAreaRect()函数
同上
- 寻找逼近轮廓的多边形 approxPolyDP()函数
void cv::approxPolyDP(
cv::InputArray curve, // 输入轮廓/曲线(点集合)
cv::OutputArray approxCurve, // 输出逼近后的多边形点集合
double epsilon, // 逼近精度(核心参数)
bool closed // 是否闭合多边形
);
7.2.5 点到轮廓距离 pointPolygonTest()函数
double cv::pointPolygonTest(
InputArray contour, // 输入闭合轮廓(点集合)
Point2f pt, // 需要计算与轮廓距离的像素点
bool measureDist ); // 计算距离是否有方向性,当为true,点在轮廓内部时,距离为正,当 为false时,只检测点是否在轮廓内
7.2.6 凸包检测 convexHull()函数
对于形状复杂的物体,利用凸包,将二维平面上的点集最外层的点连接起来构成的凸多边形。
void cv::convexHull(InputArray points, // 输入的二维点集
OutputArray hull, // 输出凸包顶点
bool clockwise = false, // 凸包点的排列方向(true顺时针/逆时针)
bool returnPoints = true // 返回值类型:true=输出凸包顶点的坐标,false=返回原数组索引
);
7.3 矩的计算
矩:描述图像特征的算子,应用于图像检索和识别,图像匹配、重建、压缩等领域。
7.3.1 计算几何矩与中心矩 moments()函数
几何矩的计算方式,,,零阶矩(可以用于计算某个形状的质心),,,一阶矩
质心的计算公式
图像中心矩的计算方式
图像归一化几何矩的计算方式
cv::Moments cv::moments(InputArray array, // 输入数据:二值图/轮廓点集
bool binaryImage = false // 二值图像模式开关
); //会返回一个moments类变量,例如Moments.m00是零阶矩
7.3.2 Hu矩 HuMoments()函数
Hu矩具有旋转、平移和缩放不变性,应用更广泛
void cv::HuMoments(
const Moments& moments, // 输入:cv::moments()计算得到的矩结构体
OutputArray hu // 输出:7个Hu矩值,存储为Mat/数组
);
7.3.3 基于Hu矩的轮廓匹配 matchShapes()函数
对比两个二维形状(轮廓 / 二值图)的相似程度,返回相似度度量值。返回值越小,两个形状越相似;
double cv::matchShapes(
InputArray contour1, // 第一个形状(轮廓/单通道二值图)
InputArray contour2, // 第二个形状(轮廓/单通道二值图)
int method, // 相似度计算方法(3种可选)
double parameter // 扩展参数,OpenCV官方定义为保留参数,固定传0
);
|
方法名称 |
cv2.CONTOURS_MATCH_I1 1方法 |
cv2.CONTOURS_MATCH_I2 2方法 |
cv2.CONTOURS_MATCH_I3 3方法 |
7.4 点集拟合
- 寻找包围二维点集的三角形 minEnclosingTriangle()函数
double cv::minEnclosingTriangle(输入待寻找包围三角形的二维点集,输入拟合出的三角形的3个顶点坐标,vector<Point2f>类型的变量中)
- 寻找包围二维点集的圆形 minEnclosingCircle()函数
void cv::minEnclosingCircle(输入待寻找的二维点集,圆形的圆心,圆形的半径)
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat img(500, 500, CV_8UC3, Scalar::all(0));
RNG& rng = theRNG(); //生成随机点
while (true)
{
int i, count = rng.uniform(1, 101);
vector<Point> points;
//生成随机点
for (i = 0; i < count; i++)
{
Point pt;
pt.x = rng.uniform(img.cols / 4, img.cols * 3 / 4);
pt.y = rng.uniform(img.rows / 4, img.rows * 3 / 4);
points.push_back(pt);
}
//寻找包围点集的三角形
vector<Point2f> triangle;
double area = minEnclosingTriangle(points, triangle);
//寻找包围点集的圆形
Point2f center;
float radius = 0;
minEnclosingCircle(points, center, radius);
//创建两个图片用于输出结果
img = Scalar::all(0);
Mat img2;
img.copyTo(img2);
//在图像中绘制坐标点
for (i = 0; i < count; i++)
{
circle(img, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
circle(img2, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
}
//绘制三角形
for (i = 0; i < 3; i++)
{
if (i==2)
{
ine(img, triangle[i], triangle[0], Scalar(255, 255, 255), 1, 16);
break;
}
line(img, triangle[i], triangle[i + 1], Scalar(255, 255, 255), 1, 16);
}
//绘制圆形
circle(img2, center, cvRound(radius), Scalar(255, 255, 255), 1, LINE_AA);
//输出结果
imshow("triangle", img);
imshow("circle", img2);
//按q键或者ESC键退出程序
char key = (char)waitKey();
if (key == 27 || key == 'q' || key == 'Q')
{
break;
}
}
return 0;
}

7.5 QR二维码检测
- 搜索二维码位置探测图形,三个“回”字区域detect()函数
bool cv::QRCodeDetector::detect(待检测图像,四个顶点坐标(数据类型为vector<Point>))
//有二维码返回true,否则false
- 对二维码进行解码,提取其中的信息decode()函数
std::string cv::QRCodeDetector::decode(含有二维码图像,四个顶点坐标,经过校正和二值化的二维码)
- 直接完成定位+解码 detectAndDecode()函数
std::string cv::QRCodeDetector::detectAndDecode(含有二维码图像,四个顶点坐标,经过校正和二值化的二维码)
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("qrcode2.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat gray, qrcode_bin;
cvtColor(img, gray, COLOR_BGR2GRAY);
QRCodeDetector qrcodedetector;
vector<Point> points;
string information;
bool isQRcode;
isQRcode = qrcodedetector.detect(gray, points); //识别二维码
if (isQRcode)
{
//解码二维码
information = qrcodedetector.decode(gray, points, qrcode_bin);
cout << points << endl; //输出二维码四个顶点的坐标
}
else
{
cout << "无法识别二维码,请确认图像时候含有二维码" << endl;
return -1;
}
//绘制二维码的边框
for (int i = 0; i < points.size(); i++)
{
if (i == points.size() - 1)
{
line(img, points[i], points[0], Scalar(0, 0, 255), 2, 8);
break;
}
line(img, points[i], points[i + 1], Scalar(0, 0, 255), 2, 8);
}
//将解码内容输出到图片上
putText(img, information.c_str(), Point(20, 30), 0, 1.0, Scalar(0, 0, 255), 2, 8);
//利用函数直接定位二维码并解码
string information2;
vector<Point> points2;
information2 = qrcodedetector.detectAndDecode(gray, points2);
cout << points2 << endl;
putText(img, information2.c_str(), Point(20, 55), 0, 1.0, Scalar(0, 0, 0), 2, 8);
//输出结果
imshow("result", img);
namedWindow("qrcode_bin", WINDOW_NORMAL);
imshow("qrcode_bin", qrcode_bin);
waitKey(0);
return 0;
}

7.6 总结

至此,第七章就结束了,望各位读者“一键三连”,再次感谢读到这里的各位读者。有任何问题欢迎评论区留言或者私信。
更多推荐
所有评论(0)