OpenCV之求多边形最小外接矩形中心点坐标及旋转度数

该博客介绍了如何使用OpenCV库进行二值图像处理,找到图像中的轮廓,并对轮廓进行排序和过滤。通过`findContours`函数获取轮廓,然后计算并绘制每个轮廓的边界框和最小外接矩形。重点关注大尺寸对象,忽略小对象,输出形心坐标。

代码

#include <opencv2\opencv.hpp>
#include <vector>  
#include <algorithm> 

using namespace cv;
using namespace std;

Mat toBinary(Mat src);
bool ascendSort(vector<Point> a, vector<Point> b);
Mat getContour(Mat src, Mat binary);

Mat srcImage, binaryImage, contourImage;

int main()
{
	srcImage = imread("222.png");//input image
	/*namedWindow("srcImage", 0);
	imshow("srcImage", srcImage);*/

	binaryImage = toBinary(srcImage);//convert to binary image
	/*namedWindow("binaryImage", 0);
	imshow("binaryImage", binaryImage);*/

	contourImage = getContour(srcImage, binaryImage);
	namedWindow("contourImage", 0);
	imshow("contourImage", contourImage);

	waitKey(0);
	return 0;
}

Mat toBinary(Mat src)
{
	Mat temp = src.clone();
	int thresh = 200, maxValue = 255;
	cvtColor(temp, temp, COLOR_BGR2GRAY);//convert to gray image
	threshold(temp, temp, thresh, maxValue, THRESH_BINARY);//binary processing
	return temp;
}

bool ascendSort(vector<Point> a, vector<Point> b)
{
	return a.size() > b.size();
}

Mat getContour(Mat src, Mat binary)
{
	Mat temp = src.clone();
	vector< vector< Point> > contours;//save all contours data
	vector<Vec4i> hierarchy;
	findContours(binary, contours, hierarchy, RETR_LIST, CHAIN_APPROX_NONE);//find contours
	//sort(contours.begin(), contours.end(), ascendSort);//ascending sort
	vector< vector<Point> >::iterator itc = contours.begin(); //iterator of contour vector
	int i = 0;
	while (itc != contours.end())
	{
		if (itc->size() > 150)//ignore the small object
		{
			if (i > 0)
			{
				Rect rect = boundingRect(*itc);//get the rectangle bounding
				rectangle(temp, rect, { 0, 0, 255 }, 2, 8);//draw the rectangle
				RotatedRect resultRect;
				resultRect = minAreaRect(*itc);//get the min area rectangle   
				Point2f pt[4];
				resultRect.points(pt);//get the coordinate of vertex
				//draw the min area rectangle
				line(temp, pt[0], pt[1], Scalar(255, 0, 0), 2, 8);
				line(temp, pt[1], pt[2], Scalar(255, 0, 0), 2, 8);
				line(temp, pt[2], pt[3], Scalar(255, 0, 0), 2, 8);
				line(temp, pt[3], pt[0], Scalar(255, 0, 0), 2, 8);
				circle(temp, resultRect.center, 5, Scalar(0, 255, 0), -1, 8, 0);

				cout << "**************形心*************" << endl;
				cout << "X坐标:" << resultRect.center.x << "   Y坐标:" << resultRect.center.y << "   偏转角度:" << resultRect.angle << endl;
				cout << "*******************************" << endl;
			}
			i++;
		}
		++itc;
	}

	return temp;
}

效果

 

<think>我们被要根据圆心、半径和起始角度计算圆弧在图像上的点。注意:圆弧通常还需要结束角度或者圆弧的角度范围。但问题中只给了起始角度,所以我们需要假设用户可能还需要结束角度或者角度范围。然而,问题描述中只提到了起始角度、半径和圆心。因此,我们需要考虑两种情况:1.用户可能只提供了起始角度,而默认的结束角度可能是起始角度加上一个固定值(比如360度,即整个圆)?但这样就不是圆弧而是整个圆了。2.更常见的是,用户还需要提供结束角度或者圆弧的跨度(例如,从起始角度开始,跨过多少度)。由于问题中没有明确,我们假设用户还需要提供结束角度(或跨度)才能计算圆弧上的点。但是,问题要是根据“起始角度、半径和圆心”,所以可能用户希望的是从起始角度开始,按一定步长生成一系列点直到某个条件(但条件未给出)?这并不明确。因此,在回答中,我们需要:1.指出需要结束角度或角度范围。2.如果用户没有提供,我们可以提供一个默认的示例(比如整个圆,或者90度的弧)。另外,注意在图像坐标系中,原点在左上角,y轴向下为正。而数学中的坐标系通常是原点在中心,y轴向上为正。而且角度通常以x轴正方向为0度,逆时针为正方向。但是,在图像中绘制圆弧时,OpenCV的角度表示:-OpenCV中,角度是以顺时针方向为正(因为y轴向下),并且0度指向x轴正方向(与数学相同)。-因此,如果我们希望使用数学中常见的角度(逆时针为正),则需要注意转换:可以将角度取负,或者用360减去角度,具体取决于我们如何定义起始角度。然而,OpenCV的函数如`cv::ellipse`和`ellipse2Poly`等,在绘制圆弧时,角度参数是这样定义的:-startAngle:圆弧的起始角度(以度为单位),从水平方向(x轴正方向)开始,顺时针为正。-endAngle:圆弧的结束角度(以度为单位),也是顺时针方向。注意:这两个角度都是相对于椭圆的长轴(如果是圆则任意)的,并且是顺时针方向。而且,endAngle是相对于startAngle的结束位置,但实际上是整个圆弧的绝对角度?实际上,OpenCV的ellipse函数中,startAngle和endAngle都是绝对角度,但方向是顺时针。但是,我们这里并不是直接绘制,而是计算点。我们可以使用`cv::ellipse2Poly`函数来计算圆弧上的点,这个函数可以生成椭圆(或圆)弧的多边形逼近点。步骤:1.确定圆心(center)、半径(radius)。因为是圆,所以长轴和短轴都是半径。2.确定起始角度(start_angle)和结束角度(end_angle)。注意:在OpenCV中,角度是顺时针方向,0度指向x轴正方向。3.确定角度增量(步长)。我们可以根据圆弧的长度来确定需要多少个点,或者用户指定点的个数。但是,函数`ellipse2Poly`的参数要我们给出起始角度和结束角度(绝对角度),以及一个角度步长(或者通过delta参数指定步长,但实际步长是由函数内部决定的,我们只能通过指定点的数量来影响?)。实际上,`ellipse2Poly`函数有两种形式,其中一种可以指定角度步长(delta),但通常我们使用另一种形式,即指定每个相邻点之间的角度步长(以度为单位)。然而,我们也可以使用参数方程来计算圆弧上的点:x=center.x+radius*cos(theta)y=center.y+radius*sin(theta)但是注意:在图像坐标系中,y轴向下,所以如果我们希望使用数学中的坐标系(y向上),则y值应该取负。但是,在图像中,我们的圆心坐标(center)已经是图像坐标系,所以直接使用上述公式时,sin(theta)应该取负吗?实际上,如果我们使用数学中的角度(0度为x正方向,逆时针为正),那么在图像坐标系中,顺时针方向相当于数学中的角度递减?或者我们可以这样:在数学坐标系中,逆时针为正,那么图像坐标系中,如果我们想要顺时针绘制,则角度递增时,数学角度应该是递减的?这样很混乱。为了避免混淆,我们可以这样:-在图像中,一个点相对于圆心的位置:x_relative=radius*cos(angle_in_radian)#这里angle_in_radian是数学中的角度(逆时针为正)y_relative=-radius*sin(angle_in_radian)#因为图像y轴向下,所以取负,这样数学坐标系中的正y方向就对应图像中的向上(负y方向)但是,如果我们按照OpenCV的约定(角度顺时针为正),那么我们可以定义:angle_in_radian=start_angle_in_degree*CV_PI/180.0#这里start_angle_in_degree是顺时针角度,0度是x正方向然后:x_relative=radius*cos(angle_in_radian)y_relative=radius*sin(angle_in_radian)#因为顺时针旋转90度到y正方向(向下),sin为正,所以这样y_relative就是正的,符合图像坐标系。因此,如果我们使用OpenCV的约定(顺时针为正),那么计算相对坐标的公式就是:x=center.x+radius*cos(angle)y=center.y+radius*sin(angle)注意:这里的angle是顺时针从x轴正方向开始的角度(弧度制)。所以,我们可以这样生成点:从起始角度(start_angle)到结束角度(end_angle),按一定步长(angle_step)生成角度序列,然后对每个角度计算坐标。但是,我们需要考虑角度跨度的方向:从start_angle到end_angle,可能是顺时针也可能是逆时针?通常,我们按顺时针方向从start_angle到end_angle,如果end_angle小于start_angle,则可能需要加上360度。然而,在圆弧中,我们通常希望绘制小于360度的弧。所以,我们假设用户给出的起始角度和结束角度,并且我们按顺时针方向从起始角度到结束角度绘制。如果结束角度小于起始角度,我们可以认为圆弧跨越了0度线,那么我们可以将结束角度加上360度,然后按顺时针方向绘制(但这样会超过360度,实际上我们也可以分段)。为了简化,我们可以使用以下方法:如果end_angle<start_angle,则end_angle+=360。然后按步长递增,直到达到end_angle(注意,如果步长过小,可能超过,所以需要控制)。但是,如果用户希望绘制逆时针的弧?按照OpenCV的约定,我们只支持顺时针。所以用户给出的角度应该是顺时针方向。另一种方法:使用参数方程,我们可以指定角度的范围(从start_angle到end_angle,按顺时针方向,即角度递增?不对,顺时针方向角度是递增吗?在数学中,顺时针是角度递减。但按照OpenCV的约定,在函数ellipse2Poly中,它是按顺时针方向从start_angle到end_angle,且start_angle和end_angle都是绝对角度,并且函数内部会处理方向。因此,为了与OpenCV一致,我们使用`ellipse2Poly`函数来计算点,这样我们就不需要自己处理参数方程,并且可以保证与OpenCV绘制函数使用的点一致。函数原型(C++):voidellipse2Poly(Pointcenter,Sizeaxes,intangle,intarcStart,intarcEnd,intdelta,std::vector<Point>&pts)注意:这个函数在C++中,axes是椭圆的长短轴,如果是圆,则Size(radius,radius)。angle是椭圆的旋转角度(对于圆是0)。arcStart和arcEnd是圆弧的起始和结束角度(单位是度,0度表示x轴正方向,顺时针为正)。delta是角度步长(单位度),它表示两个相邻点之间的角度差。pts是输出的点集。但是,这个函数有一个问题:它只能生成整数坐标点,并且步长是整数度。如果我们想要更精细的点,可以设置delta=1,但这样点会很多。另外,还有一个重载函数,可以处理浮点精度的点?实际上,OpenCV的ellipse2Poly输出的是整数点(Point),所以对于小半径的圆,可能点会不光滑。如果我们需要浮点坐标,可以自己计算。考虑到用户可能需要浮点坐标,或者整数坐标,我们可以提供两种方法。步骤:1.确认参数:圆心(center),半径(radius),起始角度(start_angle),结束角度(end_angle)。如果没有结束角度,我们可以默认画90度的弧(即结束角度=起始角度+90)。2.使用ellipse2Poly函数计算点集(整数坐标),或者使用参数方程计算(可以得到浮点坐标)。由于问题中没有明确要,我们可以提供两种方法的示例代码。但是,注意:如果使用参数方程,我们需要考虑图像坐标系(y向下),所以公式为:x=center.x+radius*cos(angle_rad)y=center.y+radius*sin(angle_rad)这里angle_rad是从起始角度到结束角度,按顺时针方向(即角度值递增?在数学中,顺时针旋转时角度值在减小,但我们可以用负角度表示,或者用360减?)实际上,我们可以这样:在参数方程中,我们使用与OpenCV相同的约定:角度θ表示从x轴正方向顺时针旋转的角度,所以θ在0~360度范围内(弧度:0~2π),并且θ增加表示顺时针旋转。因此,在参数方程中,我们使用:θ=start_angle+t*(end_angle-start_angle)(t从0到1)但是,如果end_angle<start_angle,则需要调整(加上360度),然后如果计算出的θ大于360度,则减去360度。然而,更简单的方法是:我们生成从start_angle到end_angle(顺时针)的一系列角度,如果end_angle<start_angle,则分成两段:start_angle到360和0到end_angle。考虑到简单性,我们假设用户提供的结束角度大于起始角度,且小于360度。如果用户需要跨0度,我们可以让用户自己分段,或者我们在函数中处理。我们这里先提供使用参数方程的方法(可以控制步长,得到浮点坐标),然后转换为整数坐标(如果需要绘制,可能需要整数坐标)。代码步骤(使用参数方程):1.将起始角度和结束角度转换为弧度(注意:OpenCV的角度是度,但三角函数需要弧度)。2.如果结束角度小于起始角度,则结束角度加上360度(转换为一个大于起始角度的值)。3.确定步长:例如,每1度一个点,或者根据弧长和半径计算步长(比如步长=1/半径,这样每1像素一个点?)。这里我们可以让用户指定步长(角度步长或点数)。4.生成角度序列:从start_angle_deg到end_angle_deg,按步长step_deg递增。注意:如果超过end_angle_deg,则最后一个点为end_angle_deg对应的点。5.对每个角度(度)转换为弧度,然后计算坐标(浮点)。6.将浮点坐标四舍五入为整数(如果需要整数)。但是,如果用户没有提供结束角度,我们可以要用户提供,或者默认使用整个圆(360度)。考虑到问题中只给了起始角度,我们假设用户需要整个圆?但问题说的是“圆弧”,所以通常不是整个圆。因此,在代码中,我们将结束角度作为一个参数,并提供默认值(起始角度+90度)。下面我们写一个函数,计算圆弧上的点(使用参数方程,浮点坐标)。函数设计:vector<Point2f>calculateArcPoints(Point2fcenter,floatradius,floatstart_angle,floatend_angle,floatangle_step_deg=1.0f)注意:角度步长默认为1度。但是,如果end_angle小于start_angle,我们将其加上360度,然后生成角度序列。在计算过程中,如果当前角度大于等于360度,则减去360度(因为三角函数有周期性)。另一种方法:我们不调整end_angle,而是按顺时针方向,如果end_angle小于start_angle,则从start_angle到360+end_angle,然后对超过360的角度取模(减去360)。实现:步骤:vector<Point2f>points;if(end_angle<start_angle)end_angle+=360.0f;for(floatangle_deg=start_angle;angle_deg<=end_angle;angle_deg+=angle_step_deg){floatcurrent_angle_deg=angle_deg;//如果当前角度超过360,则归一化到0~360if(current_angle_deg>=360.0f)current_angle_deg=fmod(current_angle_deg,360.0f);floatangle_rad=current_angle_deg*CV_PI/180.0f;floatx=center.x+radius*cos(angle_rad);floaty=center.y+radius*sin(angle_rad);points.push_back(Point2f(x,y));}//确保最后一个点被加入(由于浮点误差,可能最后一个点没加,所以我们再单独加一下结束角度?)//上面的循环可能没有包含end_angle(因为步长可能不是整数倍),所以我们再单独计算一下结束角度的点?//或者,我们可以在循环结束后,检查最后一个点是否小于end_angle,然后单独加入end_angle对应的点。但是,由于浮点数的精度问题,我们可能无法精确到达end_angle。为了避免遗漏,我们可以单独添加结束点(如果还没有添加的话)。改进://在循环结束后,检查最后一个点对应的角度是否小于end_angle(在归一化后),如果是,则添加end_angle对应的点。//注意:归一化后的end_angle可能已经减去360,所以我们使用原始end_angle(即start_angle+360)?但循环中我们归一化了,所以添加结束角度时也要归一化。实际上,我们可以先添加起始点,然后按步长添加,最后确保结束点被添加。但这样可能会重复。我们可以这样:points.clear();//计算总角度跨度floattotal_angle=end_angle-start_angle;//已经保证end_angle>=start_angleintn=(int)ceil(total_angle/angle_step_deg);//需要的步数for(inti=0;i<=n;i++)//包括起始点和结束点{floatangle_deg=start_angle+i*angle_step_deg;if(angle_deg>end_angle)//最后一步可能超过,所以取end_angleangle_deg=end_angle;floatcurrent_angle_deg=angle_deg;if(current_angle_deg>=360.0f)current_angle_deg=fmod(current_angle_deg,360.0f);floatangle_rad=current_angle_deg*CV_PI/180.0f;floatx=center.x+radius*cos(angle_rad);floaty=center.y+radius*sin(angle_rad);points.push_back(Point2f(x,y));}这样,我们保证了从start_angle到end_angle,包括结束点。注意:如果end_angle被加上了360,那么归一化后就是原始的角度(比如end_angle=30,加上360后是390,归一化后是30)。但是,如果用户提供的end_angle小于start_angle,并且我们调整了(加360),那么总跨度就是(end_angle+360)-start_angle,可能大于360?但用户可能只想要小于360的弧。所以,我们最好要用户提供起始和结束角度,并且我们按照从起始角度顺时针到结束角度(即使结束角度小于起始角度,我们也认为圆弧跨过了0度,即从start_angle顺时针到360,再从0到end_angle)。所以,我们需要分段。因此,更通用的方法是处理两种情形:情形1:start_angle<=end_angle,则直接从start_angle到end_angle。情形2:start_angle>end_angle,则分成两段:start_angle到360,和0到end_angle。所以,我们可以这样:vector<Point2f>points;if(start_angle<=end_angle){//直接生成for(floatangle_deg=start_angle;angle_deg<=end_angle;angle_deg+=angle_step_deg){//这里我们仍然使用上面的方法,用步数来控制,避免浮点累计误差//但为了简单,我们暂时用上面的步数控制方法}//或者调用上面的步数控制方法,这里我们写一个内部函数points=generateArcPoints(center,radius,start_angle,end_angle,angle_step_deg);}else{//生成两段points=generateArcPoints(center,radius,start_angle,360.0f,angle_step_deg);vector<Point2f>points2=generateArcPoints(center,radius,0.0f,end_angle,angle_step_deg);//将第二段点合并到第一段,注意:第一段的最后一个点可能是360度(归一化为0),而第二段的第一个点是0度,重复了。所以我们可以去掉第一段的最后一个点(360度)?或者不去掉,因为360度和0度是同一个点,但坐标相同,所以没关系。points.insert(points.end(),points2.begin(),points2.end());}但是,这样写起来代码较长。我们可以使用上面调整end_angle(加上360)的方法,然后生成点,然后归一化。这样就不需要分段,而且归一化后,0度和360度是同一个点,但坐标相同,所以不会影响。因此,我们采用调整end_angle的方法(即如果start_angle>end_angle,则end_angle+=360),然后生成从start_angle到end_angle的点,每个点都归一化到0~360度,然后计算坐标。但是,归一化后,同一个角度(比如0度和360度)会得到相同的点。所以,如果我们生成的点中包含360度,归一化后变成0度,那么它的坐标和0度相同。这没有问题。所以,我们采用调整end_angle的方法,并利用步数控制来生成点。下面我们写一个函数generateArcPoints,它处理start_angle和end_angle(保证end_angle>=start_angle),然后生成点。注意:我们要end_angle>=start_angle,如果不是,调用前调整。代码实现(C++):</think>### 计算圆弧上的图像坐标点 在 OpenCV计算圆弧上的点,需要以下参数: 1. **圆心坐标** `(cx, cy)` 2. **半径** `r` 3. **起始角度** `start_angle` 4. **结束角度** `end_angle` 5. **角度步长**(可选,控制点密度) #### 核心原理 使用参数方程计算圆弧点: $$ \begin{cases} x = cx + r \cdot \cos(\theta) \\ y = cy + r \cdot \sin(\theta) \end{cases} $$ 其中 $\theta$ 为弧度制角度,需注意: - OpenCV 图像坐标系:**原点在左上角,Y 轴向下** - 角度方向:**顺时针为正**(0° 指向 X 轴正方向) #### 代码实现(Python) ```python import cv2 import numpy as np def calculate_arc_points(center, radius, start_angle, end_angle, angle_step=1): """ 计算圆弧上的坐标点 :param center: 圆心坐标 (cx, cy) :param radius: 半径 :param start_angle: 起始角度(度) :param end_angle: 结束角度(度) :param angle_step: 角度步长(度,默认1°) :return: 点坐标列表 [ [x1, y1], [x2, y2], ... ] """ points = [] cx, cy = center # 处理角度跨0°的情况(如从350°到10°) if end_angle < start_angle: end_angle += 360 # 生成角度序列并转换为弧度 angles_deg = np.arange(start_angle, end_angle + angle_step, angle_step) angles_rad = np.deg2rad(angles_deg) for theta in angles_rad: # 注意:OpenCV坐标系Y轴向下,sin值直接使用(无需取负) x = cx + radius * np.cos(theta) y = cy + radius * np.sin(theta) points.append([int(x), int(y)]) return np.array(points) # 示例用法 center = (300, 300) # 圆心坐标 radius = 100 # 半径 start_angle = 45 # 起始角度(度) end_angle = 135 # 结束角度(度) arc_points = calculate_arc_points(center, radius, start_angle, end_angle) # 可视化(在图像上绘制圆弧) image = np.zeros((600, 600, 3), dtype=np.uint8) for pt in arc_points: cv2.circle(image, tuple(pt), 2, (0, 255, 0), -1) # 绘制绿点 cv2.imshow("Arc Points", image) cv2.waitKey(0) ``` #### 关键说明 1. **角度处理**: - 起始/结束角度使用**度数制** - 自动处理跨0°圆弧(如 350°→10°) 2. **坐标系适配**: - 公式中 $\sin(\theta)$ 直接使用(非数学坐标系中的 $-\sin(\theta)$) - 符合 OpenCV 的 Y 轴向下坐标系 3. **点密度控制**: - 通过 `angle_step` 调整采样密度(默认1°一个点) - 半径较大时建议减小步长 #### 示例输出 对于圆心 (300,300),半径100,45°到135°的圆弧: - 将生成从右上到左上的1/4圆弧 - 图像显示为连续的绿色点序列 ### 相关问题 1. **如何将圆弧坐标转换为整数像素位置?** > 代码中通过 `int(x), int(y)` 进行坐标取整,但会引入量化误差。更高精度方案建议保留浮点坐标,绘制时再取整。 2. **如何绘制带抗锯齿的圆弧?** > 使用 `cv2.ellipse()` 直接绘制抗锯齿圆弧: > ```python > cv2.ellipse(img, center, (radius, radius), 0, start_angle, end_angle, color, thickness) > ``` 3. **角度方向与数学坐标系不一致时如何转换?** > 若需使用标准数学坐标系(逆时针为正): > ```python > math_angle = 360 - cv_angle # 角度值转换 > ``` 4. **如何计算圆弧的实际长度?** > 使用公式:$ \text{弧长} = r \times |\theta_{\text{end}} - \theta_{\text{start}}| \times \frac{\pi}{180} $ > 其中 $\theta$ 为角度差值(度)[^1]。 [^1]: OpenCV 的几何计算基于笛卡尔坐标系,角度处理遵循计算机图形学标准。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值