当前位置: 首页 > news >正文

【18】OpenCV C++实战篇——【项目实战】OpenCV C++ 精准定位“十字刻度尺”中心坐标,过滤图片中的干扰,精准获取十字交点坐标

文章目录

  • 1 问题及分析
  • 2 多尺度霍夫直线 与 渐进概率霍夫线段 细节对比
    • 2.1 多尺度霍夫直线 HoughLines
    • 2.2 渐进概率霍夫线段 HoughLinesP
    • 2.3 HoughLines 和 HoughLinesP 所求结果细节对比
    • 2.4 为什么 HoughLinesP 直线两端没有呈放射状态呢?直线总是平行吗?
    • 2.5 HoughLines 直线角度,输出有顺序?
  • 3 获取十字交点坐标
    • 3.1 找到两直线
      • 3.1.1 获得 HoughLines 横竖两条线
      • 3.1.2 获得HoughLinesP 横竖两条线
    • 3.2 计算交点坐标原理 及实现
  • 4 项目源码
    • 4.1 .h文件
    • 4.2 .cpp文件
    • 4.3 main文件
  • 5 将 以上两种封装成一个函数——新增方法切换,转换大图坐标等功能

1 问题及分析

该问题,是由实际工业项目中玻璃出来的,实验详细记录分析,便于日后查看;
(虽然使用的知识点简单,但要把简单的知识点 用于解决好 实际工业问题 还是要做很多优化与改进的)

问题:
2000万像素的工业相机,要对拍摄的画面中“十字刻度尺”精准定位 (改刻度尺在原图中占比很小);
原图很大,下面截取需要定位的ROI区域如下图 。

难点:图像较暗,有红色黄色干扰,刻度尺上有记号笔涂抹干扰,图像细节模糊;

在这里插入图片描述
定位结果
在这里插入图片描述

思路 :

  • 先对原图灰度化,
  • 再进行反色使刻度尺部分凸显出来;
  • 二值化,找直线,直线筛选,求两线交点。

在这里插入图片描述
在这里插入图片描述

2 多尺度霍夫直线 与 渐进概率霍夫线段 细节对比

2.1 多尺度霍夫直线 HoughLines

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

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

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 );
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);
	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 直线精度不高,稍微的歪斜,放射状没表现出来吗?

	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;
更换倾斜的十字测试
发现,线集是平行的,没有放射状;
在这里插入图片描述

linesP.size() = 5
[262, 390, 319, 28]
[107, 215, 475, 215]
[108, 216, 474, 216]
[260, 391, 318, 23]
[107, 214, 474, 214]

在这里插入图片描述

更换倾斜的十字测试
发现,线集是平行的,还是没有放射状;
在这里插入图片描述

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度,横向;



依次竖向,横向,竖向,横向。。。循环输出所有直线 (输出顺序似乎是碰巧)

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

在这里插入图片描述

在这里插入图片描述
输出顺序似乎是碰巧,再换一个图像试试;
这次就么那么规律了,横向和竖向的线 “群居”;

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 获取十字交点坐标

前面只是获得了 横竖直线,视觉上交点找到了,但交点坐标是多少?还不知道

思路:横竖方向各选取一条直线,然后求两线交点坐标;

两线交点坐标函数如下,为了方便,每条线各取两个点带入;

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带出来;

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线集是平行的,当同一方向有多条线时,随便选一条会导致中心不准,
当同一方向有多条线时,端点坐标取均值;

//输入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 计算交点坐标原理 及实现

////****************************************************************************************
//  求二条直线的交点的公式
//  有如下方程 (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=A1C2if (det == 0) return false;//平行或重合//Now this is cross point of linescrossPoint.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文件

#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文件

#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=A1C2if (det == 0) return false;//平行或重合//Now this is cross point of linescrossPoint.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,CDauto 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文件

#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所在的图像上)

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所在的图像上;
// 获得黑十字刻度尺交点坐标,有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;
}

绘制两种霍夫直线的函数

//返回每条直线上两坐标点
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();}
}

下面的三个函数,前面已经介绍过,函数内容没变,只是名字改了;

//行列式法,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);
http://www.dtcms.com/a/324246.html

相关文章:

  • 力扣559:N叉树的最大深度
  • XGBoost算法在机器学习中的实现
  • C语言:指针(2)
  • Gin vs Beego vs Echo:三大主流 Go Web 框架深度对比
  • 前端开发中的常见问题与实战解决方案​
  • JS数组排序算法
  • scanpy单细胞转录组python教程(三):单样本数据分析之数据标准化、特征选择、细胞周期计算、回归等
  • 2025.8.10总结
  • 学生成绩管理系统的 SQL 表设计与多表查询实战
  • 部署一个免费开源的博客系统
  • 库的制作和原理
  • 双亲委派机制是什么?
  • 大模型工具集成四层架构:识别、协议、执行与实现
  • reinterpret_cast and static cast
  • Lua的数组、迭代器、table、模块
  • Elasticsearch 搜索模板(Search Templates)把“可配置查询”装进 Mustache
  • 从MySQL到大数据平台:基于Spark的离线分析实战指南
  • 重学React(四):状态管理二
  • Spark执行计划与UI分析
  • 【软考中级网络工程师】知识点之 DCC 深度剖析
  • 系统架构设计师备考之架构设计高级知识
  • 企业高性能web服务器——Nginx
  • App Trace 功能详解 (开发者视角)
  • IDEA 如何导入系统设置
  • 从0到1学LangChain之Agent代理:解锁大模型应用新姿势
  • 【机器学习深度学习】Embedding 模型详解:从基础原理到实际应用场景
  • Xstream反序列化,fastjson,jcakson靶场复现
  • 刑法视野下的虚拟财产属性争议:法律风险与市场潜力解析
  • ThinkPHP8学习篇(二):路由
  • Day39--动态规划--198. 打家劫舍,213. 打家劫舍 II,337. 打家劫舍 III