曲线
约 1117 个字 57 行代码 9 张图片 预计阅读时间 5 分钟
轮廓
从上一课中,我们知道可以利用各种边缘检测算法检测出图像中的边缘点,这些边缘点连接起来就可以知道一个物体在二维平面上的投影边界,我们可以称这一边界为轮廓。
轮廓必须通过某种数学形式来表示,并以此为基础实现视觉信息的计算机处理,并且这种表示必须是高效的、准确的。我们直接用边缘检测得到的边缘点的集合来表示轮廓,但它的时间开销和空间开销都很大。
通常情况下,我们需要找到一个数学模型,比如曲线,来表示轮廓,从而获得更为高效和准确的表征方式。
曲线表示
- 显式表示:y=f(x)
- 隐式表示:f(x,y)=0
- 参数表示:p(t)=(x,y)=(x(t),y(t))
曲线的几何属性
- 曲线长度
∫t1t2(x′(t))2+(y′(t))2
- 曲线切方向
(x(t),y(t))′(x(t),y(t))′
- 曲率
θ=(x(t),y(t))′′
在计算机中,曲线是以离散化形式保存的,因此它的上述几种几何属性就变为了
- 曲线长度
s=i=2∑n(xi−xi−1)2+(yi−yi−1)2
- 曲线切方向
Φ=arctanxi+k−xiyi+k−yi
- 曲率
θ=Φl−Φr
链码(Chain Code)
链码:用相邻边缘点组成的方向序列来表示边缘
曲线拟合
多边形拟合
Douglas-Peucker 算法
对每一条离散曲线的首末点虚连一条直线,求所有点与直线的距离,并找出最大距离值 dmax,用 dmax 与阈值D相比:
- 若 dmax<D,这条曲线上中间点全部舍去;
- 若 dmax⩾D,保留 dmax 对应的点,并以该点为界,把曲线分为两部分,对这两部分重复使用该方法。
椭圆拟合
思路是使用一个二次曲线方程
f(x,y)=ax2+bxy+cy2+dx+ey+f=0
来拟合逼近物体的边缘。即目标函数是
θmin∑f(xi,yi),θ=<a,b,c,d,e,f>
约束条件为 4ac−b2=1
求解思路:使用拉格朗日乘子法 + 最小二乘法
OpenCV 中的椭圆拟合
cv2.fitEllipse(points)
- 输入参数
points
:输入的二维点集,通常是一个轮廓(可以是 numpy 数组或 OpenCV 的轮廓格式)。要求输入的点集至少包括 5 个点,否则无法拟合椭圆。
- 返回一个旋转矩形,表示拟合的椭圆,格式为 (center, axes, angle):
- center:椭圆的中心点坐标 (x, y)。
- axes:椭圆的长轴和短轴长度 (major_axis, minor_axis)。
- angle:椭圆的旋转角度(以度为单位)
使用示例如下
| import cv2
import numpy as np
# 创建一个黑色图像并绘制一个椭圆
image = np.zeros((400, 400), dtype=np.uint8)
cv2.ellipse(image, (200, 200), (100, 50), 30, 0, 360, 255, -1)
# 检测轮廓
contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 确保轮廓点数足够多才能拟合椭圆
if len(contours[0]) >= 5:
ellipse = cv2.fitEllipse(contours[0])
print("椭圆参数:", ellipse)
# 在图像上绘制拟合的椭圆
result = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.ellipse(result, ellipse, (0, 255, 0), 2)
# 显示结果
cv2.imshow("Fitted Ellipse", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
| #include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 创建一个黑色图像并绘制一个椭圆
Mat image = Mat::zeros(400, 400, CV_8UC1);
ellipse(image, Point(200, 200), Size(100, 50), 30, 0, 360, Scalar(255), -1);
// 检测轮廓
vector<vector<Point>> contours;
findContours(image, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 确保轮廓点数足够多才能拟合椭圆
if (contours[0].size() >= 5) {
RotatedRect ellipse = fitEllipse(contours[0]);
cout << "椭圆中心: " << ellipse.center << endl;
cout << "椭圆长轴和短轴: " << ellipse.size << endl;
cout << "椭圆角度: " << ellipse.angle << endl;
// 在图像上绘制拟合的椭圆
Mat result;
cvtColor(image, result, COLOR_GRAY2BGR);
ellipse(result, ellipse, Scalar(0, 255, 0), 2);
// 显示结果
imshow("Fitted Ellipse", result);
waitKey(0);
}
return 0;
}
|
Hough 变换
Hough 变换是基于投票 (Voting) 原理的参数估计方法。基本思想是对图像中每一点对(可能的参数组合)进行表决,赢得多数票的参数组合为最终结果。
直线检测
为了避免垂直直线带来的问题,我们常会改用极坐标表示直线。
Hough 变换可以概括为以下 4 步:
- 适当地量化参数空间(合适的精度即可)
-
假定参数空间的每一个单元都是一个累加器,把累加器初始化为零
-
对图像空间的每一点,在其所满足的参数方程对应的累加器上加 1
- 累加器阵列的最大值就拟合得到的模型的参数
Example
从可视化的角度来看,就相当于每个点会给参数空间中的某一条曲线亮度加 1,最后得到的亮度最高的点就是我们应该选择的参数
圆弧检测
- 量化关于 a,b 的参数空间到合适精度
- 初始化所有累加器为 0
- 计算图像空间中边缘点的梯度幅度和角度
- 若边缘点参数坐标满足则该参数坐标对应的累加器加 1
- 拥有最大值的累加器所在的坐标即为图像空间中的圆心所在位置
- 得到圆心坐标之后,我们可以很容易反求出半径 r