文章目录
- [1 问题及分析](#1 问题及分析)
- [2 多尺度霍夫直线 与 渐进概率霍夫线段 细节对比](#2 多尺度霍夫直线 与 渐进概率霍夫线段 细节对比)
-
- [2.1 多尺度霍夫直线 HoughLines](#2.1 多尺度霍夫直线 HoughLines)
- [2.2 渐进概率霍夫线段 HoughLinesP](#2.2 渐进概率霍夫线段 HoughLinesP)
- [2.3 HoughLines 和 HoughLinesP 所求结果细节对比](#2.3 HoughLines 和 HoughLinesP 所求结果细节对比)
- [2.4 为什么 HoughLinesP 直线两端没有呈放射状态呢?直线总是平行吗?](#2.4 为什么 HoughLinesP 直线两端没有呈放射状态呢?直线总是平行吗?)
- [2.5 HoughLines 直线角度,输出有顺序?](#2.5 HoughLines 直线角度,输出有顺序?)
- [3 获取十字交点坐标](#3 获取十字交点坐标)
-
- [3.1 找到两直线](#3.1 找到两直线)
-
- [3.1.1 获得 HoughLines 横竖两条线](#3.1.1 获得 HoughLines 横竖两条线)
- [3.1.2 获得HoughLinesP 横竖两条线](#3.1.2 获得HoughLinesP 横竖两条线)
- [3.2 计算交点坐标原理 及实现](#3.2 计算交点坐标原理 及实现)
- [4 项目源码](#4 项目源码)
-
- [4.1 .h文件](#4.1 .h文件)
- [4.2 .cpp文件](#4.2 .cpp文件)
- [4.3 main文件](#4.3 main文件)
- [5 将 以上两种封装成一个函数------新增方法切换,转换大图坐标等功能](#5 将 以上两种封装成一个函数——新增方法切换,转换大图坐标等功能)
1 问题及分析
该问题,是由实际工业项目中玻璃出来的,实验详细记录分析,便于日后查看;
(虽然使用的知识点简单,但要把简单的知识点 用于解决好 实际工业问题 还是要做很多优化与改进的)
问题:
2000万像素的工业相机 ,要对拍摄的画面中"十字刻度尺 "精准定位 (改刻度尺在原图中占比很小);
原图很大,下面截取需要定位的ROI区域如下图 。
难点:图像较暗,有红色黄色干扰,刻度尺上有记号笔涂抹干扰,图像细节模糊;

定位结果
思路 :
- 先对原图灰度化,
- 再进行反色使刻度尺部分凸显出来;
- 二值化,找直线,直线筛选,求两线交点。
2 多尺度霍夫直线 与 渐进概率霍夫线段 细节对比
2.1 多尺度霍夫直线 HoughLines
csharp
int getCrossScale_HoughL(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{
cv::Mat grayImg, binaryImg, grayImg2;
cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
//反色
grayImg2 = 255 - grayImg;
cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);
vector<Vec2f> lines;//极坐标(r,theta)
HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器阈值threshold,越大线条越少,反之越多
drawHoughLine(srcImg, lines,Scalar(0,0,255),1);
//getLineCross(srcImg, lines, crossPoint, isShow);
if (isShow)
{
cv::imshow("灰度图", grayImg);
cv::imshow("反色", grayImg2);
cv::imshow("二值化", binaryImg);
cv::imshow("getCrossScale_HoughL", srcImg);
cv::waitKey(0);
destroyAllWindows();
}
return 0;
}
横竖各找到3条线,
倾斜较为明显,线段两端呈放射状;

再看中间交点 横向占2个像素,竖向占3个像素
2.2 渐进概率霍夫线段 HoughLinesP
csharp
int getCrossScale_HoughLP(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{
cv::Mat grayImg, binaryImg, grayImg2;
cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
//反色
grayImg2 = 255 - grayImg;
cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);
//利用渐进概率式霍夫变换提取 直线段
vector<Vec4i> linesP;//每条线有四个参数,分别是选段两端点坐标(x1,y1,x2,y2)
HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //两个点连接最大距离10,CV_PI / 180表示1度对应的弧度
for (size_t i = 0; i < linesP.size(); i++)
{
line(srcImg, Point(linesP[i][0], linesP[i][1]), Point(linesP[i][2], linesP[i][3]), Scalar(255,0,0), 1);
//测试坐标点
//cout << "linesP1[" << i << "][0]" << linesP1[i][0] << "," << "linesP1[" << i << "][1]" << linesP1[i][1] << endl;
}
//getLineCross(srcImg, lines, crossPoint, isShow);
if (isShow)
{
cv::imshow("灰度图", grayImg);
cv::imshow("反色", grayImg2);
cv::imshow("二值化", binaryImg);
cv::imshow("getCrossScale_HoughLP", srcImg);
cv::waitKey(0);
destroyAllWindows();
}
return 0;
}
横向找到3条线,竖向找到1条线;
横向线段 3条线非常贴合,看似平行;(线段两端没有呈放射状;)
再看中间交点 横向占3个像素,竖向占1个像素
2.3 HoughLines 和 HoughLinesP 所求结果细节对比
对比两函数参数,发现HoughLinesP 似乎比 HoughLines 有更多的约束条件;
HoughLinesP 多了最先线段长度minLineLength
,最小间隙maxLineGap
;
csharp
void HoughLines( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double srn = 0, double stn = 0,
double min_theta = 0, double max_theta = CV_PI );
csharp
void HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );

- 在使用上的区别,HoughLinesP设定 最小那线段长度为 图片高度的0.6倍 ;两线之间最小间隙为5;
- 此外在使用上HoughLinesP,似乎更方便
HoughLines
, 每条线返回2个参数,(r,theta);HoughLinesP
,每条线返回4个参数,分别是选段两端点坐标(x1,y1,x2,y2);
csharp
vector<Vec2f> lines;////每条线有2个参数,极坐标(r,theta)
HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器阈值threshold,越大线条越少,反之越多
//利用渐进概率式霍夫变换提取 直线段
vector<Vec4i> linesP;//每条线有四个参数,分别是选段两端点坐标(x1,y1,x2,y2)
HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //两个点连接最大距离10,CV_PI / 180表示1度对应的弧度
红色 HoughLines
:横竖各找到3条线;线段两端呈放射状;
蓝色 HoughLinesP
:横向找到3条线,竖向找到1条线;横向线段 3条线非常贴合,看似平行,线段两端没有呈放射状;

红色 HoughLines
: 再看中间交点 横向占2个像素,竖向占3个像素;
蓝色 HoughLinesP
:再看中间交点 横向占3个像素,竖向占1个像素

对比来看,HoughLinesP
效果更好一下;
2.4 为什么 HoughLinesP 直线两端没有呈放射状态呢?直线总是平行吗?
在观察发现 HoughLinesP ,返回的直线vector linesP是 int型,而HoughLines 返回的直线是flaot类型;
难道是HoughLinesP 直线精度不高,稍微的歪斜,放射状没表现出来吗?
csharp
vector<Vec2f> lines;////每条线有2个参数,极坐标(r,theta)
HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器阈值threshold,越大线条越少,反之越多
//利用渐进概率式霍夫变换提取 直线段
vector<Vec4i> linesP;//每条线有四个参数,分别是选段两端点坐标(x1,y1,x2,y2)
HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //两个点连接最大距离10,CV_PI / 180表示1度对应的弧度
将HoughLinesP的直线类型 vector<Vec4i> linesP
;改为float类型vector<Vec4f> linesP
;
更换倾斜的十字测试
发现,线集是平行的,没有放射状;
csharp
linesP.size() = 5
[262, 390, 319, 28]
[107, 215, 475, 215]
[108, 216, 474, 216]
[260, 391, 318, 23]
[107, 214, 474, 214]

更换倾斜的十字测试
发现,线集是平行的,还是没有放射状;
csharp
linesP.size() = 16
[25, 193, 392, 206]
[203, 365, 215, 20]
[196, 363, 208, 17]
[200, 365, 212, 20]
[206, 365, 218, 14]
[202, 365, 214, 19]
[204, 365, 217, 15]
[26, 192, 397, 205]
[26, 195, 387, 207]
[198, 364, 210, 18]
[26, 190, 398, 203]
[27, 7, 286, 48]
[26, 196, 385, 209]
[62, 190, 398, 202]
[197, 365, 208, 51]
[22, 198, 385, 211]

2.5 HoughLines 直线角度,输出有顺序?
一依此绘制出HoughLines
的每一条直线,发现还是有规律的;
如下图所示:
第一条直线 倾斜 9度,竖向;(OpenCV里HoughLines的角度应该是与Y轴的夹角)
第二条直线 倾斜90度,横向;
。
。
。
依次竖向,横向,竖向,横向。。。循环输出所有直线 (输出顺序似乎是碰巧)
csharp
lines 0[319, 0.15708] 角度 9
lines 1[215, 1.5708] 角度 90
lines 2[315, 0.139626] 角度 8
lines 3[221, 1.55334] 角度 89
lines 4[323, 0.174533] 角度 10
lines 5[211, 1.58825] 角度 91


输出顺序似乎是碰巧,再换一个图像试试;
这次就么那么规律了,横向和竖向的线 "群居";
csharp
lines 0[192, 1.6057] 角度 92
lines 1[196, 1.58825] 角度 91
lines 2[189, 1.62316] 角度 93
lines 3[209, 0.0174533] 角度 1
lines 4[185, 1.64061] 角度 94
lines 5[206, 0] 角度 0
lines 6[215, 0.0349066] 角度 2
lines 7[213, 0.0349066] 角度 2
lines 8[216, 0.0523599] 角度 3
lines 9[218, 0.0523599] 角度 3
lines 10[219, 0.0698132] 角度 4
lines 11[222, 0.0698132] 角度 4
lines 12[224, 0.0872665] 角度 5
lines 13[199, 1.5708] 角度 90
3 获取十字交点坐标
前面只是获得了 横竖直线,视觉上交点找到了,但交点坐标是多少?还不知道
思路:横竖方向各选取一条直线,然后求两线交点坐标;
两线交点坐标函数如下,为了方便,每条线各取两个点带入;
csharp
bool get2linesIntersectionPoint3(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint);
3.1 找到两直线
3.1.1 获得 HoughLines 横竖两条线
注意 : HoughLines 水平线90度,竖直线0度;
OpenCV里HoughLines的角度应该是与Y轴的夹角;
思路:
输入HoughLines直线lines,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来;
csharp
bool getPointOn2Line(Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine,bool isShow)
{
vector<Vec2f> linesHV;
float rho, theta;
bool linesH = false, linesV = false;
//找水平、垂直,两条线
//水平线90度,竖直线0度;OpenCV里HoughLines的角度应该是与Y轴的夹角
for (size_t i = 0; i < lines.size(); i++)
{
theta = lines[i][1]; //直线过坐标原点垂线与x轴夹角
if (!linesH && (theta > CV_PI / 2 - 0.2 && theta < CV_PI / 2 + 0.2))//横线theta接近π/2,±δ,
{
linesHV.push_back(lines[i]);
linesH = true;
}
else if (!linesV && ((theta > -0.2 && theta < 0.2) || theta > CV_PI - 0.2 && theta < CV_PI + 0.2))//竖线theta接近π或0,±δ
{
linesHV.push_back(lines[i]);
linesV = true;
}
//横线竖线都找到了,跳出循环
if (linesH && linesV)
{
break;
}
}
//求两条直线上的四个点
if (linesHV.size() == 2)
{
drawHoughLine(img, linesHV, ptsOnLine, Scalar(0, 0, 255), 1, isShow);
}
else
{
cout << "没找到两条直线" << endl;
return false;
}
cv::Rect rect;
return true;
}
交点坐标为(248.8,215.0)
3.1.2 获得HoughLinesP 横竖两条线
思路:
输入HoughLinesP直线linesP,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来
注意:
因为HoughLinesP线集是平行的,当同一方向有多条线时,随便选一条会导致中心不准,
当同一方向有多条线时,端点坐标取均值;
csharp
//输入HoughLinesP直线linesP,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来
bool getPointOn2LineP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow)
{
float rho, theta;
Point2f pt1, pt2;
double angle;
Point2f pts1_H(0,0), pts2_H(0, 0), pts1_V(0, 0), pts2_V(0, 0);
int h = 0, v = 0;
//找水平、垂直,两条线
//因为HoughLinesP线集是平行的,当同一方向有多条线时,随便选一条会导致中心不准,
//当同一方向有多条线端点坐标取均值;
for (size_t i = 0; i < linesP.size(); i++)
{
pt1.x = linesP[i][0];
pt1.y = linesP[i][1];
pt2.x = linesP[i][2];
pt2.y = linesP[i][3];
//linesP的每条直线与y轴的夹角
angle = getLineAngle(pt1, pt2, Point2f(0,0), Point2f(0,img.rows));
//水平线90度,竖直线0度;;;直线与Y轴的夹角(与HoughLines的夹角保持一致)
if (angle > 70 && angle < 110)//如果横线90°±20°
{
pts1_H.x += pt1.x;
pts1_H.y += pt1.y;
pts2_H.x += pt2.x;
pts2_H.y += pt2.y;
h++;
}
else if (angle > -20 && angle < 20)//如果竖线0°±20°
{
pts1_V.x += pt1.x;
pts1_V.y += pt1.y;
pts2_V.x += pt2.x;
pts2_V.y += pt2.y;
v++;
}
}
pts1_H.x /= h;
pts1_H.y /= h;
pts2_H.x /= h;
pts2_H.y /= h;
pts1_V.x /= v;
pts1_V.y /= v;
pts2_V.x /= v;
pts2_V.y /= v;
ptsOnLine.push_back(pts1_H);
ptsOnLine.push_back(pts2_H);
ptsOnLine.push_back(pts1_V);
ptsOnLine.push_back(pts2_V);
line(img, pts1_H, pts2_H, Scalar(255, 0, 0), 1);
line(img, pts1_V, pts2_V, Scalar(255, 0, 0), 1);
if(isShow)
{
cv::imshow("img", img);
cv::waitKey(0);
destroyAllWindows();
}
return true;
}
交点坐标为(249.1,215.0)
3.2 计算交点坐标原理 及实现
csharp
////****************************************************************************************
// 求二条直线的交点的公式
// 有如下方程 (x-x1)/(y-y1) = (x2-x1)/(y2-y1) ==> a1*x+b1*y=c1
// (x-x3)/(y-y3) = (x4-x3)/(y4-y3) ==> a2*x+b2*y=c2
// 则交点为
// x= D1/D =| c1 b1| / | a1 b1 | y= D2/D= | a1 c1| / | a1 b1 | //当两条线平行或重合时,分母为零
// | c2 b2| / | a2 b2 | | a2 c2| / | a2 b2 |
//
// 注:D是其次ax+by=0,的行列式,Di是把第i列换成等式右边的列(常数),i是所求未知数所在的列,如x在第一列,D1
//
// a1= y2-y1
// b1= x1-x2
// c1= x1*y2-x2*y1,这里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一个负号关系
// a2= y4-y3
// b2= x3-x4
// c2= x3*y4-x4*y3
//
////****************************************************************************************
////
////行列式法,x= D1/D, y= D2/D,求两直线的交点, //适合任意情况(斜率存在,不存在)
bool get2linesIntersectionPoint(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint)
{
float a1 = pointB.y - pointA.y;
float b1 = pointA.x - pointB.x;
float c1 = pointA.x * pointB.y - pointB.x * pointA.y;//这里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一个负号关系
float a2 = pointD.y - pointC.y;
float b2 = pointC.x - pointD.x;
float c2 = pointC.x * pointD.y - pointD.x * pointC.y;
float det = a1 * b2 - a2 * b1;
// 直线平行:A1/A2=B1/B2≠C1/C2 (A2B2C2≠0); 重合:A1/A2=B1/B2=C1/C2(A2B2C2≠0)
// 直线平行:A1B2=A2B1; 重合:A1B2=A2B1=A1C2
if (det == 0) return false;//平行或重合
//Now this is cross point of lines
crossPoint.x = (b2 * c1 - b1 * c2) / det;//这里和公式法(ABC)也是负号关系
crossPoint.y = (a1 * c2 - a2 * c1) / det;
return true;
}
由上面两种方式获取的直线,分别带入交点坐标函数,求得交点如图:
红色 HoughLines
: 交点坐标为(248.8,215.0)
蓝色 HoughLinesP
:交点坐标为(249.1,215.0)
二者横坐标,相差0.3像素,纵坐标相等,
该工业相机为2000万像素,可见精度还是很高的


比较细节,看看哪个更准?
比较发现,由于刻度尺上端被黑色的记号笔涂抹记号,对直线检测结果有一定影响
;
但比较细节,蓝色受影响更小一些; 如果想要更加精准,ROI范围可以再小一些,把记号笔涂抹部分去掉;

4 项目源码
4.1 .h文件
csharp
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//获得黑十字刻度尺 交点坐标(转换为在B2大ROI中的位置),显示在srcImgBigROI上
int getCrossScale_HoughL(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow);
int getCrossScale_HoughLP(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow);
bool getPointOn2Line( Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine, bool isShow);
bool getPointOn2LineP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow);
//返回直线上两坐标点,isDraw设置是否绘制直线
void drawHoughLine(Mat& img, vector<Vec2f> lines, vector<cv::Point2f> &ptsOnLine, const Scalar& color, int thickness, bool isDraw);//要标记直线的图像,检测的直线数据
bool get2linesIntersectionPoint(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint);
//已知每条直线的两个点求夹角,,返回角度
double getLineAngle(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD);
4.2 .cpp文件
csharp
#include "getCross.h"
int getCrossScale_HoughL(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{
cv::Mat grayImg, binaryImg, grayImg2;
cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
//反色
grayImg2 = 255 - grayImg;
cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);
vector<Vec2f> lines;////每条线有2个参数,极坐标(r,theta)
HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器阈值threshold,越大线条越少,反之越多
vector<cv::Point2f> ptsOnLine;
//获得两直线
getPointOn2Line(srcImg, lines, ptsOnLine, false);
//获得两直线交点
get2linesIntersectionPoint(ptsOnLine[0], ptsOnLine[1], ptsOnLine[2], ptsOnLine[3], crossPoint);
if (isShow)
{
//绘制交点坐标
char buf[50];
memset(buf, '\0', 50);
sprintf_s(buf, "X = %.1f; Y = %.1f;", crossPoint.x, crossPoint.y);
int font_face = FONT_HERSHEY_COMPLEX;
double font_scale = 0.5;
int thickness = 1;
putText(srcImg, buf, Point(crossPoint.x + 20, crossPoint.y - 20), font_face, font_scale, Scalar(0, 0, 255), thickness, 8, 0);
imshow("十字刻度尺 交点:", srcImg);
cv::waitKey(0);
destroyAllWindows();
}
return 0;
}
int getCrossScale_HoughLP(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{
cv::Mat grayImg, binaryImg, grayImg2;
cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
//反色
grayImg2 = 255 - grayImg;
cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);
//利用渐进概率式霍夫变换提取 直线段
vector<Vec4i> linesP;//每条线有四个参数,分别是选段两端点坐标(x1,y1,x2,y2)
HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //两个点连接最大距离10,CV_PI / 180表示1度对应的弧度
////绘制所有线段查看效果
//cout << "linesP.size() = " << linesP.size() << endl;
//for (size_t i = 0; i < linesP.size(); i++)
//{
// //linesP1[i][0]第i条线段的x坐标、linesP1[i][1]第i条线段的y坐标
// line(srcImg, Point(linesP[i][0], linesP[i][1]), Point(linesP[i][2], linesP[i][3]), Scalar(255,0,0), 1);
// cout << linesP[i]<< endl;//测试坐标点
//}
vector<cv::Point2f> ptsOnLine;
//获得两直线
getPointOn2LineP(srcImg, linesP, ptsOnLine, false);
//获得两直线交点
get2linesIntersectionPoint(ptsOnLine[0], ptsOnLine[1], ptsOnLine[2], ptsOnLine[3], crossPoint);
if (isShow)
{
//绘制交点坐标
char buf[50];
memset(buf, '\0', 50);
sprintf_s(buf, "X = %.1f; Y = %.1f;", crossPoint.x, crossPoint.y);
int font_face = FONT_HERSHEY_COMPLEX;
double font_scale = 0.5;
int thickness = 1;
putText(srcImg, buf, Point(crossPoint.x + 20, crossPoint.y - 20), font_face, font_scale, Scalar(0, 0, 255), thickness, 8, 0);
imshow("十字刻度尺 交点:", srcImg);
cv::waitKey(0);
destroyAllWindows();
}
return 0;
}
//输入HoughLines直线lines,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来
bool getPointOn2Line(Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine,bool isShow)
{
vector<Vec2f> linesHV;
float rho, theta;
bool linesH = false, linesV = false;
//找水平、垂直,两条线
//水平线90度,竖直线0度;OpenCV里HoughLines的角度应该是与Y轴的夹角
for (size_t i = 0; i < lines.size(); i++)
{
theta = lines[i][1]; //直线过坐标原点垂线与x轴夹角
if (!linesH && (theta > CV_PI / 2 - 0.2 && theta < CV_PI / 2 + 0.2))//横线theta接近π/2,±δ,
{
linesHV.push_back(lines[i]);
linesH = true;
}
else if (!linesV && ((theta > -0.2 && theta < 0.2) || theta > CV_PI - 0.2 && theta < CV_PI + 0.2))//竖线theta接近π或0,±δ
{
linesHV.push_back(lines[i]);
linesV = true;
}
//横线竖线都找到了,跳出循环
if (linesH && linesV)
{
break;
}
}
//求两条直线上的四个点
if (linesHV.size() == 2)
{
drawHoughLine(img, linesHV, ptsOnLine, Scalar(0, 0, 255), 1, isShow);
}
else
{
cout << "没找到两条直线" << endl;
return false;
}
cv::Rect rect;
return true;
}
//输入HoughLinesP直线linesP,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来
bool getPointOn2LineP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow)
{
float rho, theta;
Point2f pt1, pt2;
double angle;
Point2f pts1_H(0,0), pts2_H(0, 0), pts1_V(0, 0), pts2_V(0, 0);
int h = 0, v = 0;
//找水平、垂直,两条线
//因为HoughLinesP线集是平行的,当同一方向有多条线时,随便选一条会导致中心不准,
//当同一方向有多条线端点坐标取均值;
for (size_t i = 0; i < linesP.size(); i++)
{
pt1.x = linesP[i][0];
pt1.y = linesP[i][1];
pt2.x = linesP[i][2];
pt2.y = linesP[i][3];
//linesP的每条直线与y轴的夹角
angle = getLineAngle(pt1, pt2, Point2f(0,0), Point2f(0,img.rows));
//水平线90度,竖直线0度;;;直线与Y轴的夹角(与HoughLines的夹角保持一致)
if (angle > 70 && angle < 110)//如果横线90°±20°
{
pts1_H.x += pt1.x;
pts1_H.y += pt1.y;
pts2_H.x += pt2.x;
pts2_H.y += pt2.y;
h++;
}
else if (angle > -20 && angle < 20)//如果竖线0°±20°
{
pts1_V.x += pt1.x;
pts1_V.y += pt1.y;
pts2_V.x += pt2.x;
pts2_V.y += pt2.y;
v++;
}
}
pts1_H.x /= h;
pts1_H.y /= h;
pts2_H.x /= h;
pts2_H.y /= h;
pts1_V.x /= v;
pts1_V.y /= v;
pts2_V.x /= v;
pts2_V.y /= v;
ptsOnLine.push_back(pts1_H);
ptsOnLine.push_back(pts2_H);
ptsOnLine.push_back(pts1_V);
ptsOnLine.push_back(pts2_V);
line(img, pts1_H, pts2_H, Scalar(255, 0, 0), 1);
line(img, pts1_V, pts2_V, Scalar(255, 0, 0), 1);
if(isShow)
{
cv::imshow("img", img);
cv::waitKey(0);
destroyAllWindows();
}
return true;
}
////****************************************************************************************
// 求二条直线的交点的公式
// 有如下方程 (x-x1)/(y-y1) = (x2-x1)/(y2-y1) ==> a1*x+b1*y=c1
// (x-x3)/(y-y3) = (x4-x3)/(y4-y3) ==> a2*x+b2*y=c2
// 则交点为
// x= D1/D =| c1 b1| / | a1 b1 | y= D2/D= | a1 c1| / | a1 b1 | //当两条线平行或重合时,分母为零
// | c2 b2| / | a2 b2 | | a2 c2| / | a2 b2 |
//
// 注:D是其次ax+by=0,的行列式,Di是把第i列换成等式右边的列(常数),i是所求未知数所在的列,如x在第一列,D1
//
// a1= y2-y1
// b1= x1-x2
// c1= x1*y2-x2*y1,这里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一个负号关系
// a2= y4-y3
// b2= x3-x4
// c2= x3*y4-x4*y3
//
////****************************************************************************************
////
////行列式法,x= D1/D, y= D2/D,求两直线的交点, //适合任意情况(斜率存在,不存在)
bool get2linesIntersectionPoint(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint)
{
float a1 = pointB.y - pointA.y;
float b1 = pointA.x - pointB.x;
float c1 = pointA.x * pointB.y - pointB.x * pointA.y;//这里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一个负号关系
float a2 = pointD.y - pointC.y;
float b2 = pointC.x - pointD.x;
float c2 = pointC.x * pointD.y - pointD.x * pointC.y;
float det = a1 * b2 - a2 * b1;
// 直线平行:A1/A2=B1/B2≠C1/C2 (A2B2C2≠0); 重合:A1/A2=B1/B2=C1/C2(A2B2C2≠0)
// 直线平行:A1B2=A2B1; 重合:A1B2=A2B1=A1C2
if (det == 0) return false;//平行或重合
//Now this is cross point of lines
crossPoint.x = (b2 * c1 - b1 * c2) / det;//这里和公式法(ABC)也是负号关系
crossPoint.y = (a1 * c2 - a2 * c1) / det;
return true;
}
//*********************************************************
//求两直线的夹角
//已知两点坐标求向量:A(a1, b1), B(a2, b2, ), 则向量AB为:B点坐标减A点坐标,即:向量AB = (a2-a1, b2-b1);
//已知两向量坐标,求两向量夹角:设两个向量分别为a = (x1,y1), b = (x2, y2),其夹角为α,因为ab = |a||b| cosα,所以cosα = ab/|a||b|= (x1x2+y1y2) / (根号(x1^2 + y1^2)根号(x2^2 + y2^2));
//两向量内积:已知两向量a = [a1, a2, ..., an]和b = [b1, b2, ..., bn]的点积定义为:内积就是点积 a·b=a1b1+a2b2+......+anbn;
//向量的模,即向量的长度,设向量a = (x, y),则向量a的模 = 根号(x方 + y方)
//夹角为α = arccos(∑(xiyi) / sqrt((∑(xixi)∑(yiyi)))
//cosα = 两个向量的内积 / 向量的模("长度")的乘积
//*********************************************************
//已知每条直线的两个点求夹角
double getLineAngle(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD)
{
//向量AB,CD
auto v1 = pointB - pointA;
auto v2 = pointD - pointC;
//向量AB,CD的模
double n1 = cv::norm(v1);
double n2 = cv::norm(v2);
//cosα = ab/|a||b|
double cosv = (v1.x * v2.x + v1.y * v2.y) / n1 / n2;
double angle_rad = acos(cosv);
//弧度转角度
return angle_rad * 180 / CV_PI;
}
//返回直线上两坐标点,有时只需要两个点的坐标,并不需要绘制显示直线
void drawHoughLine(Mat& img, vector<Vec2f> lines, vector<cv::Point2f> &ptsOnLine ,const Scalar& color, int thickness,bool isShow )//要标记直线的图像,检测的直线数据
{
double length = max(img.rows, img.cols); //图像高宽的最大值
Point2f pt1, pt2;
float rho, theta;
double a, b, x0, y0;
for (size_t i = 0; i < lines.size(); i++)
{
rho = lines[i][0]; //直线距离坐标原点的距离
theta = lines[i][1]; //直线过坐标原点垂线与x轴夹角
a = cos(theta); //夹角的余弦值
b = sin(theta); //夹角的正弦值
//x = r*cos(θ),y = r*sin(θ)
x0 = a * rho, y0 = b * rho; //直线与过坐标原点的垂线的交点
//计算直线上的一点
pt1.x = x0 + length * (-b);
pt1.y = y0 + length * (a);
//计算直线上另一点
pt2.x = x0 - length * (-b);
pt2.y = y0 - length * (a);
////若想获得整数点,可用cvRound()四舍五入;
//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, color, thickness);
if (isShow)
{
cout << "lines " << i << lines[i] << "\t角度 " << lines[i][1] * 180 / CV_PI << endl;
cv::imshow("img", img);
cv::waitKey(0);
destroyAllWindows();
}
ptsOnLine.push_back(pt1);
ptsOnLine.push_back(pt2);
}
}
4.3 main文件
csharp
#include "getCross.h"
void main()
{
char imgPath[] = "D:\\C_test\\images\\8.bmp";
cv::Mat srcImg = imread(imgPath);
cv::Point2f crossPoint;
//getCrossScale_HoughL(srcImg, crossPoint, true);
getCrossScale_HoughLP(srcImg, crossPoint, true);
}
5 将 以上两种封装成一个函数------新增方法切换,转换大图坐标等功能
获得黑十字刻度尺交点坐标,有HoughLP、HoughL两种方法可选用,(ptsROIltop可将交点转换为大图中的位置,显示在ROI所在的图像上)
csharp
bool getLineCrossPoint2f(cv::Mat srcImg, Mat ImgDraw, string getLineMethod , Point ptsROIltop, cv::Point2f& crossPoint, bool isShow)
新增参数解释:
Mat ImgDraw
,传入用来绘制线条等信息的图像(有时并不需要将线条绘制在处理的图像上);string getLineMethod
,有HoughLP、HoughL两种方法可选用;Point ptsROIltop
是ROI左上角坐标,用于将交点转换为大图中的位置,显示在ROI所在的图像上;
csharp
// 获得黑十字刻度尺交点坐标,有HoughLP、HoughL两种方法可选用,(ptsROIltop可将交点转换为大图中的位置,显示在ROI所在的图像上)
bool getLine::getLineCrossPoint2f(cv::Mat srcImg, Mat ImgDraw, string getLineMethod , Point ptsROIltop, cv::Point2f& crossPoint, bool isShow)
{
cv::Mat grayImg, binaryImg, grayImg2;
cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
//反色
grayImg2 = 255 - grayImg;
cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);
vector<cv::Point2f> ptsOnLine;//获取直线上的两点
if (getLineMethod == "HoughLP")
{
//利用渐进概率式霍夫变换提取 直线段
vector<Vec4i> linesP;//每条线有四个参数,分别是选段两端点坐标(x1,y1,x2,y2)
HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //两个点连接最大距离10,CV_PI / 180表示1度对应的弧度
////绘制所有线段查看效果
//drawHoughLineP(srcImg, linesP, Scalar(0, 0, 255), 1, isShow);
//获得两相交直线
get2CrossHoughLP(srcImg, linesP,ptsOnLine, false);
}
else if (getLineMethod == "HoughL")
{
vector<Vec2f> lines;////每条线有2个参数,极坐标(r,theta)
HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器阈值threshold,越大线条越少,反之越多
//绘制所有直线查看效果
//drawHoughLine(srcImg, lines,ptsOnLine, Scalar(0, 0, 255), 1, isShow);
vector<cv::Point2f> ptsOnLine;
//获得两相交直线,
get2CrossHoughL(srcImg, lines,ptsOnLine, false);
}
if (ptsOnLine.size() == 4)
{
//将直线坐标点转换为大图中的位置,ptsROIltop是ROI左上角坐标, 直线显示在srcImgBigROI上
//当ptsROIltop为(0,0)时,绘制在ROI图上;当为ROI左上角坐标时,直线绘制在ROI所在的大图上;
ptsOnLine[0].x += ptsROIltop.x; ptsOnLine[0].y += ptsROIltop.y;
ptsOnLine[1].x += ptsROIltop.x; ptsOnLine[1].y += ptsROIltop.y;
ptsOnLine[2].x += ptsROIltop.x; ptsOnLine[2].y += ptsROIltop.y;
ptsOnLine[3].x += ptsROIltop.x; ptsOnLine[3].y += ptsROIltop.y;
//获得两直线交点
get2linesIntersectionPoint3(ptsOnLine[0], ptsOnLine[1], ptsOnLine[2], ptsOnLine[3], crossPoint);
}
else
{
cout << "没找到两条直线" << endl;
return false;
}
if (isShow)
{
//绘制两交线
line(ImgDraw, ptsOnLine[0], ptsOnLine[1], Scalar(255, 0, 0), 1);
line(ImgDraw, ptsOnLine[2], ptsOnLine[3], Scalar(255, 0, 0), 1);
//绘制交点坐标
char buf[50];
memset(buf, '\0', 50);
sprintf_s(buf, "X = %.1f; Y = %.1f;", crossPoint.x, crossPoint.y);
int font_face = FONT_HERSHEY_COMPLEX;
double font_scale = 0.5;
int thickness = 1;
putText(ImgDraw, buf, Point(crossPoint.x + 20, crossPoint.y - 20), font_face, font_scale, Scalar(0, 0, 255), thickness, 8, 0);
imshow("十字刻度尺 交点:", ImgDraw);
cv::waitKey(0);
destroyAllWindows();
}
return true;
}
绘制两种霍夫直线的函数
csharp
//返回每条直线上两坐标点
void getLine::drawHoughLine(Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine, const Scalar& color, int thickness, bool isShow)//要标记直线的图像,检测的直线数据
{
double length = max(img.rows, img.cols); //图像高宽的最大值
Point2f pt1, pt2;
float rho, theta;
double a, b, x0, y0;
for (size_t i = 0; i < lines.size(); i++)
{
rho = lines[i][0]; //直线距离坐标原点的距离
theta = lines[i][1]; //直线过坐标原点垂线与x轴夹角
a = cos(theta); //夹角的余弦值
b = sin(theta); //夹角的正弦值
//x = r*cos(θ),y = r*sin(θ)
x0 = a * rho, y0 = b * rho; //直线与过坐标原点的垂线的交点
//计算直线上的一点
pt1.x = x0 + length * (-b);
pt1.y = y0 + length * (a) ;
//计算直线上另一点
pt2.x = x0 - length * (-b);
pt2.y = y0 - length * (a);
////若想获得整数点,可用cvRound()四舍五入;
//pt1.x = cvRound(x0 + length * (-b));//返回跟参数最接近的整数值,即四舍五入;
//pt1.y = cvRound(y0 + length * (a));
//pt2.x = cvRound(x0 - length * (-b));
//pt2.y = cvRound(y0 - length * (a));
//实践发现:
//V方向,y轴向上超出img约 -img.rows,H方向,x轴向左超出img约 -img.cols,
//为了将绘制的直线 限制在img范围内,可将V方向,y轴上端 +img.rows,H方向,x轴左 +img.cols
//这样实际是不准的,因有倾斜,最准的方法用直线表达式,给定x,y,获取指定位置坐标;
//绘制的直线超出img范围,以后在修改
if (isShow)
{
//两点绘制一条直线
line(img, pt1, pt2, color, thickness);
cout << "lines " << i << lines[i] << "\t角度 " << lines[i][1] * 180 / CV_PI << endl;
cv::imshow("img", img);
cv::waitKey(0);
destroyAllWindows();
}
ptsOnLine.push_back(pt1);
ptsOnLine.push_back(pt2);
}
}
void getLine::drawHoughLineP(Mat& img, vector<Vec4i> linesP, const Scalar& color, int thickness, bool isShow)//要标记直线的图像,检测的直线数据
{
cout << "linesP.size() = " << linesP.size() << endl;
for (size_t i = 0; i < linesP.size(); i++)
{
//linesP1[i][0]第i条线段的x坐标、linesP1[i][1]第i条线段的y坐标
line(img, Point(linesP[i][0], linesP[i][1]), Point(linesP[i][2], linesP[i][3]), color, thickness);
cout << linesP[i]<< endl;//测试坐标点
}
if (isShow)
{
cv::imshow("img", img);
cv::waitKey(0);
destroyAllWindows();
}
}
下面的三个函数,前面已经介绍过,函数内容没变,只是名字改了;
csharp
//行列式法,x= D1/D, y= D2/D,求两直线的交点, //适合任意情况(斜率存在,不存在)
bool get2linesIntersectionPoint3(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint);
//输入HoughLines直线lines,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来;
bool get2CrossHoughL( Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine, bool isShow);
//输入HoughLinesP直线linesP,选取横竖两条直线,在选取的直线上各取两个点,由ptsOnLine带出来;
bool get2CrossHoughLP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow);