OPENCV第八章-目标检测
轮廓提取
轮廓概念
一般用二值化的图像
对轮廓1的描述 [2,-1,-1,0]

轮廓检测函数
hierarchy描述的轮廓的结构关系

轮廓绘制函数
示例
#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;
}
轮廓信息统计
轮廓面积
方向性指的是轮廓的像素点是否具有方向性,可以通过正负得出像素点是正序还是负序给出的
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
//用四个点表示三角形轮廓
vector<Point> contour;
contour.push_back(Point2f(0, 0));
contour.push_back(Point2f(10, 0));
contour.push_back(Point2f(10, 10));
contour.push_back(Point2f(5, 5));
double area = contourArea(contour);
cout << "area =" << area << endl;
Mat img = imread("coins.jpg");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
imshow("原图", img);
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY); //转化成灰度图
GaussianBlur(gray, gray, Size(9, 9), 2, 2); //平滑滤波
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++)
{
double area1 = contourArea(contours[t]);
cout << "第" << t << "轮廓面积=" << area1 << endl;
}
return 0;
}
轮廓长度提取
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
//用四个点表示三角形轮廓
vector<Point> contour;
contour.push_back(Point2f(0, 0));
contour.push_back(Point2f(10, 0));
contour.push_back(Point2f(10, 10));
contour.push_back(Point2f(5, 5));
double length0 = arcLength(contour, true);
double length1 = arcLength(contour, false);
cout << "length0 =" << length0 << endl;
cout << "length1 =" << length1 << endl;
Mat img = imread("coins.jpg");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
imshow("原图", img);
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY); //转化成灰度图
GaussianBlur(gray, gray, Size(9, 9), 2, 2); //平滑滤波
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++)
{
double length2 = arcLength(contours[t], true);
cout << "第" << t << "个轮廓长度=" << length2 << endl;
}
return 0;
}
轮廓的拟合
boudingRect给出的是轮廓的外接矩形的Rect,需要输入灰度图像或者2D点集
minAreaRect得到最小的外接矩形(允许矩形进行旋转)得到的旋转的四个点的坐标和中心点的坐标,可以用minarearect.center()和minarearect.points()获取
approxPloyDP获得的是多边形顶点的坐标
示例
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("stuff.jpg");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat img1, img2;
img.copyTo(img1); //深拷贝用来绘制最大外接矩形
img.copyTo(img2); //深拷贝用来绘制最小外接矩形
imshow("img", img);
// 去噪声与二值化
Mat canny;
Canny(img, canny, 80, 160, 3, false);
imshow("", canny);
//膨胀运算,将细小缝隙填补上
Mat kernel = getStructuringElement(0, Size(3, 3));
dilate(canny, canny, kernel);
// 轮廓发现与绘制
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(canny, contours, hierarchy, 0, 2, Point());
//寻找轮廓的外接矩形
for (int n = 0; n < contours.size(); n++)
{
// 最大外接矩形
Rect rect = boundingRect(contours[n]);
rectangle(img1, rect, Scalar(0, 0, 255), 2, 8, 0);
// 最小外接矩形
RotatedRect rrect = minAreaRect(contours[n]);
Point2f points[4];
rrect.points(points); //读取最小外接矩形的四个顶点
Point2f cpt = rrect.center; //最小外接矩形的中心
// 绘制旋转矩形与中心位置
for (int i = 0; i < 4; i++)
{
if (i == 3)
{
line(img2, points[i], points[0], Scalar(0, 255, 0), 2, 8, 0);
break;
}
line(img2, points[i], points[i + 1], Scalar(0, 255, 0), 2, 8, 0);
}
//绘制矩形的中心
circle(img2, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
}
//输出绘制外接矩形的结果
imshow("max", img1);
imshow("min", img2);
waitKey(0);
return 0;
}
示例(多边形拟合)
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
//绘制轮廓函数
void drawapp(Mat result, Mat img2)
{
for (int i = 0; i < result.rows; i++)
{
//最后一个坐标点与第一个坐标点连接
if (i == result.rows - 1)
{
Vec2i point1 = result.at<Vec2i>(i);
Vec2i point2 = result.at<Vec2i>(0);
line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0);
break;
}
Vec2i point1 = result.at<Vec2i>(i);
Vec2i point2 = result.at<Vec2i>(i + 1);
line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0);
}
}
int main(int argc, const char* argv[])
{
Mat img = imread("approx.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
// 边缘检测
Mat canny;
Canny(img, canny, 80, 160, 3, false);
//膨胀运算
Mat kernel = getStructuringElement(0, Size(3, 3));
dilate(canny, canny, kernel);
// 轮廓发现与绘制
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(canny, contours, hierarchy, 0, 2, Point());
//绘制多边形
for (int t = 0; t < contours.size(); t++)
{
//用最小外接矩形求取轮廓中心
RotatedRect rrect = minAreaRect(contours[t]);
Point2f center = rrect.center;
circle(img, center, 2, Scalar(0, 255, 0), 2, 8, 0);
Mat result;
approxPolyDP(contours[t], result, 4, true); //多边形拟合
drawapp(result, img);
cout << "corners : " << result.rows << endl;
//判断形状和绘制轮廓
if (result.rows == 3)
{
putText(img, "triangle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
}
if (result.rows == 4)
{
putText(img, "rectangle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
}
if (result.rows == 8)
{
putText(img, "poly-8", center, 0, 1, Scalar(0, 255, 0), 1, 8);
}
if (result.rows > 12)
{
putText(img, "circle", center, 0, 1, Scalar(0, 255, 0), 1, 8);
}
}
imshow("result", img);
waitKey(0);
return 0;
}
凸包检测
把轮廓拟合成一个凸多边形
可以直接的找到手所在区域的大小
示例
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("hand.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
// 二值化,凸包检测只能输入2D点
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 105, 255, THRESH_BINARY);
//开运算消除细小区域
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
imshow("binary", binary);
// 轮廓发现
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, 0, 2, Point());
for (int n = 0; n < contours.size(); n++)
{
//计算凸包
vector<Point> hull;
convexHull(contours[n], hull);
//绘制凸包
for (int i = 0; i < hull.size(); i++)
{
//绘制凸包顶点
circle(img, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);
//连接凸包
if (i == hull.size() - 1)
{
line(img, hull[i], hull[0], Scalar(0, 0, 255), 2, 8, 0);
break;
}
line(img, hull[i], hull[i + 1], Scalar(0, 0, 255), 2, 8, 0);
}
}
imshow("hull", img);
waitKey(0);
return 0;
}
直线检测
检测直线的霍夫变换原理
原空间的点是参数空间的线,原空间的线是参数空间的点
参数空间交于一点的多条线,表示原空间并线的多个点
为了防止b=0,导致在斜率不存在,用(r,θ)的方式来表示直线(直线距离原点距离以及角度)
检测直线函数
检测结果是(r,θ)需要计算得到两个点才能绘制
示例
#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));//cvround可以把浮点数近似为整数
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);//CV_PI为1度
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;
}
渐进霍夫变换
函数输出的线段的端点坐标
image一般输入的是用Canny算法得到的图像边缘
示例
#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);//Canny算法得到的图像已经是二值化图像了
//利用渐进概率式霍夫变换提取直线
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;
}
圆形检测
霍夫变换同样可以检测图像中是否存在圆形,检测方法与检测直线相似,都是将图像空间x-y直角坐标系中的像素投影到参数空间中,之后寻找是否存在交点,在圆形参数空间中,数学描述的形式如下

霍夫圆检测
函数会自动调用canny检测器,因此不需要二值化,输出的结果在Vec3f里面,其中前两个参数是圆形的中心坐标,第三个参数是圆形的半径,该函数的第三个参数是检测圆形的方法标志,目前仅支持HOUGH_GRADIENT方法,dp = 1时离散化后分辨率图像相同,等于2的时候离散化图像高度和宽度只有一般,mindist的最小距离,防止错误的检测到多个相邻源泉,param1为Canny阈值的较大值,较小值默认为一半。
示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("test1.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
resize(img, img, Size(3840 / 5, 2160 / 5));
imshow("原图", img);
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
equalizeHist(gray, gray);
GaussianBlur(gray, gray, Size(9, 9), 2, 2); //平滑滤波
//检测圆形
vector<Vec3f> circles;
double dp = 2; //
double minDist = 80; //两个圆心之间的最小距离
double param1 = 180; //Canny边缘检测的较大阈值
double param2 = 100; //累加器阈值
int min_radius = 40; //圆形半径的最小值
int max_radius = 100; //圆形半径的最大值
HoughCircles(gray, circles, HOUGH_GRADIENT, dp, minDist, param1, param2,
min_radius, max_radius);
//图像中标记出圆形
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(img, center, 3, Scalar(0, 255, 0), -1, 8, 0);
//绘制圆
circle(img, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}
//显示结果
imshow("圆检测结果", img);
waitKey(0);
return 0;
}
结果(参数可以拿去用)(别忘了均衡化)

点集的拟合
点集拟合的含义

直线拟合

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
Vec4f lines; //存放你和后的直线
vector<Point2f> point; //待检测是否存在直线的所有点
const static float Points[20][2] = {
{ 0.0f, 0.0f },{ 10.0f, 11.0f },{ 21.0f, 20.0f },{ 30.0f, 30.0f },
{ 40.0f, 42.0f },{ 50.0f, 50.0f },{ 60.0f, 60.0f },{ 70.0f, 70.0f },
{ 80.0f, 80.0f },{ 90.0f, 92.0f },{ 100.0f, 100.0f },{ 110.0f, 110.0f },
{ 120.0f, 120.0f },{ 136.0f, 130.0f },{ 138.0f, 140.0f },{ 150.0f, 150.0f },
{ 160.0f, 163.0f },{ 175.0f, 170.0f },{ 181.0f, 180.0f },{ 200.0f, 190.0f }
};
//将所有点存放在vector中,用于输入函数中
for (int i = 0; i < 20; i++)
{
point.push_back(Point2f(Points[i][0], Points[i][1]));
}
//参数设置
double param = 0; //距离模型中的数值参数C
double reps = 0.01; //坐标原点与直线之间的距离精度
double aeps = 0.01; //角度精度
fitLine(point, lines, DIST_L1, 0, 0.01, 0.01);
double k = lines[1] / lines[0]; //直线斜率
cout << "直线斜率:" << k << endl;
cout << "直线上一点坐标x:" << lines[2] << ", y::" << lines[3] << endl;
cout << "直线解析式:y=" << k << "(x-" << lines[2] << ")+" << lines[3] << endl;
return 0;
}
三角形和圆形拟合
示例
#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)
{
line(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;
}
测你们码(QR码)
QR二维码的识别原理
无论二维码尺寸如何,位置探测图形宽度必须是1:1:3:1的形式(1黑1白3黑1白)
可以通过对其标志把倾斜的二维码透视变换回去
二维码定位函数与识别函数

二维码的直接定位和识别函数

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