• OpenCV C++案例实战二十七《角度测量》



    前言

    本案例通过使用OpenCV中的鼠标点击事件进行物体角度测量。以鼠标点击三点确定一个角度。第一个点:即为需要测量角度所在位置点(中心点),第二、三点确定角度。

    一、鼠标响应事件

    原图如图所示:
    请添加图片描述
    首先第一步,利用鼠标响应事件进行取点操作。OpenCV中的setMouseCallback可以完成此操作。参数也比较简单。

    void setMouseCallback(
    	const String& winname,  //窗口名称
    	MouseCallback onMouse,  //响应回调函数
    	void* userdata = 0  //用户传入数据,可选
     );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.1功能源码

    具体请看源码实现

    //利用鼠标响应事件进行取点
    void DrawCircle(int event, int x, int y, int flags, void* userdata)
    {
    	//鼠标左键点击,记录并绘制圆点
    	/*
    		鼠标点击三点确定一个角度。第一个点:即为需要测量角度所在位置点(中心点);第二、三个点:确定角度
    	*/
    
    	Mat canvas = *((Mat*)userdata);  //传入图像
    
    	if (event == EVENT_LBUTTONDOWN)
    	{
    		if (x > 0 && y > 0)
    		{
    			point.x = x;  //当鼠标左键点击时,记录鼠标点击位置
    			point.y = y;
    		}
    	}
    	if (event == EVENT_LBUTTONUP)
    	{ 
    		//当鼠标左键抬起时,保存鼠标点击坐标位置
    		clickcount++; //点击次数+1
    		myPoints.push_back(point);
    		circle(canvas, point, 5, Scalar(0, 0, 255), -1);//绘制点
    		putText(canvas, to_string(clickcount), Point(point.x-10,point.y-10), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 3);
    		imshow("Demo", canvas);
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    1.2功能效果

    在这里插入图片描述

    二、计算直线角度

    2.1 计算直线斜率

    根据直线起始点计算直线斜率。k=(y2-y1) / (x2-x1)

    //计算直线斜率
    double gradient(Point2f pt1, Point2f pt2)
    {
    	if (pt1.x == pt2.x)
    	{	
    		return 9999999.9;	//斜率不存在
    	}
    	else
    	{
    		return (pt2.y - pt1.y) / (pt2.x - pt1.x);
    	}	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    根据传入的三个点,我们可以分别计算出两条直线的斜率,分别是k1,k2。

    2.2计算直线角度

    两直线角度公式如下:tanθ=|(k1-k2)/ (1+k1*k2) |
    在这里插入图片描述
    此时,我们计算出来的θ还是弧度,我们需要把它转为角度制。

    弧度与角度转换公式为:

    1° = π / 180 ≈ 0.01745 rad
    1 rad = 180 / π = 57.30°

    所以我们最终的角度为: Angle = θ*180 / π

    2.3功能源码

    //计算两直线所成角度
    double getAngle(vector<Point2f>myPoints, Point2f &ArcCenter, Point2f &StartPoint, Point2f &EndPoint)
    {
    	ArcCenter = myPoints[0];//中心点,确定需要测量哪个角
    	StartPoint = myPoints[1];//起点
    	EndPoint = myPoints[2];//终点
    
    	//两直线斜率
    	double k1 = gradient(StartPoint, ArcCenter);
    	double k2 = gradient(EndPoint, ArcCenter);
    
    	//弧度
    	double theta = atan(abs((k2 - k1) / (1 + k1 * k2)));
    
    	//角度
    	double Angle = theta * 180 / CV_PI;
    	
    	return Angle;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    三、绘制圆弧

    至此我们已经完成了取点、角度计算工作。为了效果显示,这里,我们将三点形成的角度用圆弧绘制出来。

    这里我贴出某点绕原点旋转θ角度后坐标位置推到过程。

    请添加图片描述

    类似的,我们可以推导出,平面中某一点绕任意点旋转θ角度后坐标

    平面中,一点(x,y)绕任意点(dx,dy)逆时针旋转θ角度后坐标位置
    x1 = dx + (x-dx)cos(θ) - (y-dy)sin(θ)
    y1 = dy + (x-dx)sin(θ) + (y-dy)cos(θ)

    平面中,一点(x,y)绕任意点(dx,dy)顺时针旋转θ角度后坐标位置
    x1 = dx + (x-dx)cos(-θ) - (y-dy) sin(-θ)
    y1 = dy + (x-dx)sin(-θ) + (y-dy)cos(-θ)

    3.1功能源码

    //绘制圆弧
    void DrawArc(Mat src, Point2f& ArcCenter, Point2f& StartPoint, Point2f& EndPoint, double &angle)
    {	
    	double Angle1 = atan2((StartPoint.y - ArcCenter.y), (StartPoint.x - ArcCenter.x));//起始弧度
    	double Angle2 = atan2((EndPoint.y - ArcCenter.y), (EndPoint.x - ArcCenter.x));//终止弧度
    	double Angle = Angle2 - Angle1;//总弧度
    	Angle = Angle * 180.0 / CV_PI;//弧度转角度
    	if (Angle < 0) Angle = 360 + Angle;
    	if (Angle == 0) Angle = 360;
    	int  ArcLength = floor(Angle / 1); // 向下取整
    	
    	vector<Point2f> ArcPoints;//取出所有圆弧上的点
    	for (int i = 0; i < ArcLength; i++)
    	{
    		//每隔一度取一个点
    		double SinTheta = sin(i * CV_PI / 180);
    		double CosTheta = cos(i * CV_PI / 180);
    		double x = ArcCenter.x + CosTheta * (StartPoint.x - ArcCenter.x) - SinTheta * (StartPoint.y - ArcCenter.y);
    		double y = ArcCenter.y + SinTheta * (StartPoint.x - ArcCenter.x) + CosTheta * (StartPoint.y - ArcCenter.y);
    		ArcPoints.push_back(Point2f(x, y));
    	}
    
    	//绘制圆弧
    	for (int i = 0; i < ArcPoints.size() - 1; i++)
    	{
    		line(src, Point(ArcPoints[i]), Point(ArcPoints[(i + 1)]), Scalar(0, 255, 0), 2);
    	}
    	line(src, Point(ArcCenter), Point(StartPoint), Scalar(0, 255, 0), 2);
    	line(src, Point(ArcCenter), Point(EndPoint), Scalar(0, 255, 0), 2);
    	putText(src, to_string(angle), EndPoint, FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 255, 0), 2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    四、结果显示

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

    五、源码

    具体功能实现请看源码,有不清楚的地方可私信我。。

    #include
    #include
    #include
    using namespace std;
    using namespace cv;
    
    Point2f point(-1, -1);//初始化鼠标点击坐标
    vector<Point2f>myPoints;//将鼠标点击到的坐标存入vector,作为全局变量
    int clickcount = 0;//记录鼠标点击次数
    
    //利用鼠标响应事件进行取点
    void DrawCircle(int event, int x, int y, int flags, void* userdata)
    {
    	//鼠标左键点击,记录并绘制圆点
    	/*
    		鼠标点击三点确定一个角度。第一个点:即为需要测量角度所在位置点(中心点);第二、三个点:确定角度
    	*/
    
    	Mat canvas = *((Mat*)userdata);  //传入图像
    
    	if (event == EVENT_LBUTTONDOWN)
    	{
    		if (x > 0 && y > 0)
    		{
    			point.x = x;  //当鼠标左键点击时,记录鼠标点击位置
    			point.y = y;
    		}
    	}
    	if (event == EVENT_LBUTTONUP)
    	{ 
    		//当鼠标左键抬起时,保存鼠标点击坐标位置
    		clickcount++; //点击次数+1
    		myPoints.push_back(point);
    		circle(canvas, point, 5, Scalar(0, 0, 255), -1);//绘制点
    		putText(canvas, to_string(clickcount), Point(point.x-10,point.y-10), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 3);
    		imshow("Demo", canvas);
    
    	}
    }
    
    
    //计算直线斜率
    double gradient(Point2f pt1, Point2f pt2)
    {
    	if (pt1.x == pt2.x)
    	{	
    		return 9999999.9;	//斜率不存在
    	}
    	else
    	{
    		return (pt2.y - pt1.y) / (pt2.x - pt1.x);
    	}	
    }
    
    
    //计算两直线所成角度
    double getAngle(vector<Point2f>myPoints, Point2f &ArcCenter, Point2f &StartPoint, Point2f &EndPoint)
    {
    	ArcCenter = myPoints[0];//中心点,确定需要测量哪个角
    	StartPoint = myPoints[1];//起点
    	EndPoint = myPoints[2];//终点
    
    	//两直线斜率
    	double k1 = gradient(StartPoint, ArcCenter);
    	double k2 = gradient(EndPoint, ArcCenter);
    
    	//弧度
    	double theta = atan(abs((k2 - k1) / (1 + k1 * k2)));
    
    	//角度
    	double Angle = theta * 180.0 / CV_PI;
    	
    	return Angle;
    }
    
    
    //绘制圆弧
    void DrawArc(Mat src, Point2f& ArcCenter, Point2f& StartPoint, Point2f& EndPoint, double &angle)
    {	
    	double Angle1 = atan2((StartPoint.y - ArcCenter.y), (StartPoint.x - ArcCenter.x));//起始弧度
    	double Angle2 = atan2((EndPoint.y - ArcCenter.y), (EndPoint.x - ArcCenter.x));//终止弧度
    	double Angle = Angle2 - Angle1;//总弧度
    	Angle = Angle * 180.0 / CV_PI;//弧度转角度
    	if (Angle < 0) Angle = 360 + Angle;
    	if (Angle == 0) Angle = 360;
    	int  ArcLength = floor(Angle / 1); // 向下取整
    	
    	vector<Point2f> ArcPoints;//取出所有圆弧上的点
    	for (int i = 0; i < ArcLength; i++)
    	{
    		//每隔一度取一个点
    		double SinTheta = sin(i * CV_PI / 180);
    		double CosTheta = cos(i * CV_PI / 180);
    		double x = ArcCenter.x + CosTheta * (StartPoint.x - ArcCenter.x) - SinTheta * (StartPoint.y - ArcCenter.y);
    		double y = ArcCenter.y + SinTheta * (StartPoint.x - ArcCenter.x) + CosTheta * (StartPoint.y - ArcCenter.y);
    		ArcPoints.push_back(Point2f(x, y));
    	}
    
    	//绘制圆弧
    	for (int i = 0; i < ArcPoints.size() - 1; i++)
    	{
    		line(src, Point(ArcPoints[i]), Point(ArcPoints[(i + 1)]), Scalar(0, 255, 0), 2);
    	}
    	line(src, Point(ArcCenter), Point(StartPoint), Scalar(0, 255, 0), 2);
    	line(src, Point(ArcCenter), Point(EndPoint), Scalar(0, 255, 0), 2);
    	putText(src, to_string(angle), EndPoint, FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 255, 0), 2);
    }
    
    
    int main()
    {
    	Mat src = imread("src.jpg");
    	if (src.empty())
    	{
    		cout << "can not read the image..." << endl;
    		system("pause");
    		return-1;
    	}
    
    	while (true)
    	{
    		imshow("Demo", src);
    		namedWindow("Demo", WINDOW_AUTOSIZE);
    
    		setMouseCallback("Demo", DrawCircle, &src);
    
    		if (clickcount == 3)
    		{
    			Point2f ArcCenter, StartPoint, EndPoint;
    	
    			double Angle = getAngle(myPoints, ArcCenter, StartPoint, EndPoint);
    
    			DrawArc(src, ArcCenter, StartPoint, EndPoint, Angle);
    
    			myPoints.clear();//当完成一次测量后,重置数据
    			clickcount = 0;
    		}
    		
    		char key = waitKey(1);
    		if (key == 'c')
    		{
    			//按c键则重新加载图像
    			src = imread("src.jpg");
    		}
    		else if (key == 27)
    		{
    			//按esc键退出程序
    			break;
    		}
    	}
    
    	destroyAllWindows();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155

    总结

    本文使用OpenCV C++ 进行物体角度测量,主要操作有以下几点。
    1、利用鼠标响应事件取点,三点确定一个角度
    2、利用两直线角度公式计算直线角度,注意弧度转角度
    3、绘制圆弧,便于显示。注意某一点绕任意点旋转θ角度后的坐标计算公式。

  • 相关阅读:
    echarts+高德地图结合开发
    计算机毕业设计Java毕业设计管理系统(源码+系统+mysql数据库+lw文档)
    分析逆向案例九——奥鹏教育教师登录密码加密
    深信服面经——技服岗位
    基于JAVA音乐资源分享网站系统计算机毕业设计源码+系统+数据库+lw文档+部署
    编程,不止有代码,还有艺术
    自动化办公篇之python批量改名
    01.jvm介绍
    PID学习
    基于SSM的家居商城系统
  • 原文地址:https://blog.csdn.net/Zero___Chen/article/details/125815819