【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.pswp.cn/diannao/95223.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95223.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生应用的DevOps2(Jenkins渗透场景)

结论 Jenkins历史漏洞 Jenkins未授权访问 登录后命令执行 Jenkins代码仓库信息 Jenkins服务器建立多台服务器信任连接 背景 目前我看到红队人员的现状,不管是什么系统就是拿Shell,拿权限,然后把这台机器当作跳板继续横向其它机器。而Jenkins在内网中是经常能够遇到的,…

Gradle 配置教程:与 Maven 对比详解(含完整迁移指南)

一、基础对比&#xff1a;Gradle vs Maven1.1 核心特性对比维度MavenGradle配置语言XML (冗长)Groovy/Kotlin DSL (简洁灵活)构建速度较慢(全量构建)快2-10倍(增量构建缓存)多模块管理<modules> <parent>settings.gradle project()依赖管理<dependencies>d…

pcl 按比例去除点云的噪点

之前halcon3d中是这么做的&#xff1b;1&#xff0c;先查找点云中每个点最近第15个最近点的距离2&#xff0c;将距离进行排序3&#xff0c;求排序后在距离数组70%位置的距离 d4&#xff0c;筛选点云中每个点半径为d&#xff0c;近邻点的数量大于14的点用pcl实现的代码如下&…

站在Vue的角度,对比鸿蒙开发中的递归渲染

第三类 引用数据的操作 引用数据类型 又叫复杂数类型&#xff0c; 典型的代表是对象和数组&#xff0c;而数组和对象往往又嵌套到到一起 普通数组和对象的使用 vue中使用for循环遍历 <template><div>我是关于页面<div v-for"item in arr">{{ i…

VBS 流程控制

一. if else 和 selec case 1. if end if Dim a a2If a0 ThenMsgBox "这里是0"End if 2. if else end if Dim a a2If a0 ThenMsgBox "这里是0"Else MsgBox "这里是2" 弹窗“这里是2”End if 3. if -----elseif-------else-------end…

HCIP项目之OSPF综合实验

一、项目背景随着企业分支机构地理分散化&#xff0c;跨地域网络互联需求激增。MGRE&#xff08;多点 GRE&#xff09;技术因适应动态拓扑、降低链路成本&#xff0c;成为多分支互联的常用方案&#xff1b;OSPF 作为链路状态路由协议&#xff0c;适用于大型网络且支持可变长子网…

class and enmu class

传统枚举与作用域污染及enum class的详细介绍在编程中&#xff0c;枚举&#xff08;enum&#xff09;是一种常见的特性&#xff0c;用于定义一组命名的常量。传统枚举&#xff08;如C中的enum&#xff09;虽然简单易用&#xff0c;但容易导致作用域污染问题。而enum class&…

Numpy科学计算与数据分析:Numpy数组属性入门之形状、维度与大小

Numpy数组属性探索 学习目标 通过本课程的学习&#xff0c;学员将掌握Numpy数组的基本属性&#xff0c;如形状&#xff08;shape&#xff09;、维度&#xff08;ndim&#xff09;和大小&#xff08;size&#xff09;&#xff0c;并能够通过实际操作加深对这些属性的理解。 相…

IF 33.3+ 通过多区域单细胞测序解析肺腺癌的空间和细胞结构

通过多区域单细胞测序解析肺腺癌的空间和细胞结构摘要对于肺腺癌演进过程中单个细胞群的地理空间架构知之甚少。在此&#xff0c;我们对来自5例早期LUAD和14个来自肿瘤的具有明确空间邻近性的多区域正常肺组织的186&#xff0c;916个细胞进行了单细胞RNA测序。我们发现细胞谱系…

【Redis的安装与配置】

一&#xff1a;下载 Redis ✅ 百度网盘分享 &#x1f449; https://pan.baidu.com/s/1xkrLlyUPyM0btCFFpGEhcw?pwdSVIP ✅ 从Github下载 &#x1f449; https://github.com/MicrosoftArchive/redis/releases 二&#xff1a;安装 Redis 1️⃣ 将下载的压缩包文件 解压到 某个文…

TDSQL GTS文件说明

基于时间点恢复&#xff1a;全备xlogGTS文件 在TDSQL的备份恢复体系中&#xff0c;GTS文件是全局时间戳&#xff08;Global Timestamp&#xff09;的存储载体&#xff0c;用于记录事务在分布式环境中的精确执行顺序和时间点 其核心作用体现在以下方面&#xff1a; 1‌。时间基准…

全星APQP数字化平台在汽车零部件行业的深度应用与效益分析

全星APQP数字化平台在汽车零部件行业的深度应用与效益分析 全星研发项目管理APQP软件系统是专为汽车零部件行业打造的数字化研发管理平台&#xff0c;通过深度集成行业核心工具链&#xff0c;实现从产品设计到量产的全程可控。以下为该系统在汽车零部件行业的应用解析&#xf…

网络通信安全:HTTPS协议的密码学基石

引言&#xff1a;从HTTP到HTTPS的安全升级 在网络通信中&#xff0c;数据传输的安全性至关重要。早期的HTTP协议采用明文传输&#xff0c;存在三大安全隐患&#xff1a; 机密性问题&#xff1a;数据在传输过程中可能被窃听&#xff08;如公共Wi-Fi中的监听&#xff09;&#xf…

pip 和 conda,到底用哪个安装?

为什么 pip 有时装不下来而 --prefer-binary 可以&#xff1f;什么是源代码发行版&#xff1f;什么是轮子&#xff1f;conda 和 pip 有什么区别&#xff1f;优先用谁啊&#xff1f;两者适合的场景&#xff08;何时用哪个&#xff09;安装路径&#xff1a;pip / conda 分别装到哪…

bert学习

首先了解一下几种embedding。比如elmo就是一个embedding模型。one-hot编码只能实现one word one embedding&#xff0c;而我们的elmo能实现one token one embeddingElmo是基于双向LSTM&#xff0c;所以每个词其实会有正向和反向两个预测结果&#xff0c;那么我们用哪个呢&#…

Java安全-组件安全

一、Xstream启动环境并访问接下来我们构造反弹shell语句&#xff0c;bash -i >& /dev/tcp/8.152.2.86/9999 0>&1&#xff0c;紧接着对其进行base64编码。接下来使用命令即可首先开启监听接下来执行命令接下来抓包对其进行payload构造即可紧接着回去查看回显发现成…

【10】微网优联——微网优联 嵌入式技术一面,校招,面试问答记录

微网优联——微网优联 嵌入式技术一面&#xff0c;校招&#xff0c;问答记录 1. 2 分钟简单自自我介绍2. 问一遍笔试题目3. IP地址在哪个层4.手动配置过IP地址吗?要配哪几个&#xff1f;5. ARP 是域名找IP地址还是IP地址找域名?6. Linux、计算机网络、操作系统掌握的怎么样&a…

C#使用EPPlus读写Excel

依赖EPPlus 获取依赖可以阅读:Nuget For Unity插件介绍_nugetforunity-CSDN博客 可以参阅该篇快速入门:在Unity中使用Epplus写Excel_unity epplus-CSDN博客 下面是我封装的几个方法: 要在合适的时机配置许可证,比如你的工具类的静态函数.建议使用版本7.7.1 #region Excel封装,…

高性能Web服务器

一、Web服务基础介绍 1.1、互联网发展历程 1993年3月2日&#xff0c;中国科学院高能物理研究所租用AT&T公司的国际卫星信道建立的接入美国SLAC国家实验室的64K专线正式开通&#xff0c;成为我国连入Internet的第一根专线。 1995年马云开始创业并推出了一个web网站中国黄…

《算法导论》第 16 章 - 贪心算法

大家好&#xff01;今天我们来深入探讨《算法导论》第 16 章的核心内容 —— 贪心算法。贪心算法是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法。它在许多优化问题中都有广泛应…