为君持酒劝斜阳
且向花间留晚照
曲线
形状
形状是物体的一种属性,往往可以根据形状信息识别物体。图像的边缘点连接起来就是物体二位平面的投影,即物体的轮廓。但是轮廓的表示比较消耗资源,通常使用数学模型比如曲线来表示。
曲线的几何属性
- 长度:
- 曲线切方向:
- 曲率:
曲线的离散化
- 曲线长度:
- 曲率切向量(k斜率):
- 曲率(k曲率):
曲线表示
- 边缘点序列
- 直接用边缘点序列表示边缘
- 链码
- \(\psi-S\)图
- 图表示
- 斜率密度函数
- 链码
- 直接用边缘点序列表示边缘
曲线拟合
给定一系列边缘点,找出一条曲线的函数表达式,使曲线接近所有边缘点以描述物体轮廓
- 直线段近似
- 直线
- 分线段
- 二次曲线
- 圆弧
- 圆锥
- 样条曲线(分段多项式)
- 重要属性:连接处光滑
- 三次样条:通过每个点
- B样条:不必过每个点
曲线拟合通过最小二乘法来逼近,利用均方差MSE作为损失函数
直线方程:
\[\rho = xcos\theta + ysin\theta\]目标函数:
\[d^2 = \sum_i(x_icos\theta+y_isin\theta-\rho)^2\]求解:
\[min_{\rho,\theta}d^2\]即:
\[{\partial d^2 \over \theta} = 0\] \[{\partial d^2 \over \rho} = 0\]PS:对于误差正态分布的点击,最小二乘可以得到最佳估计,但不符合正态分布的就不敏感了
分线段拟合时采用Dauglas-Pecuker算法,即对每一条离散曲线的首末点虚连一条线,计算每个点到直线的距离,并将最大距离与阈值进行比较,如果小于阈值,舍去全部点;如果大于,则将最大距离点保留,用其将曲线分割为两部分,再对每个部分重复这个步骤。
Hough变换
Hough变换是一种基于投票原理的参数估计方法,也是一种重要的的形状检测技术。
y = mx + c —-> c = -xm + y
将(x,y)空间变换为(c,m)空间,为避免垂直带来的问题,转化为极坐标系
特征检测
在图像中搜索特征时,角点是个不错的选择,角点是两条边缘线的接合点,并且大量存在于人造物体中。
对于角点,在任意方向上移动,窗口内灰度值都会发生巨大变化,对于图像I(x,y)平移u、v之后的自相似性如下:
\[E(u,v) = \sum_{x,y}w(x,y)(I(x,y)-I(x+u,y+v))^2\]如果u、v很小,则:
\[I(x+u,y+v) \approx I(x,y) + uI_x(x,y) + vI_y(x,y)\]那么:
\[E(x,y) = \sum_{x,y}w(x,y)( uI_x(x,y) + vI_y(x,y))^2\] \[= [u,v]W(x,y)\left[ \begin{matrix} u \\\\ v \end{matrix} \right]\]其中:
\[M(x,y) = \left[ \begin{matrix} \sum_{x,y}w(x,y)I_x^2 & \sum_{x,y}w(x,y)I_xI_y \\\\ \sum_{x,y}w(x,y)I_xI_y & \sum_{x,y}w(x,y)I_y^2 \end{matrix} \right]\] \[= \left[ \begin{matrix} A & B \\\\ B & C \end{matrix} \right]\]因此:
\[E(x,y) = Au^2 + 2Buv + Cv^2\]某种意义上,这是一个椭圆:
其中\(\lambda_1,\lambda_2\)是M(x,y)的特征值,检测角点时却并不需要计算,我们计算如下的响应值来判断角点:
其中\(detM\)是M的行列式
\[det M = \lambda_1\lambda_2=AC-B^2\] \[traceM=\lambda_1+\lambda_2=A+C\] \[R = AC - B^2 - \alpha(A+C)^2\]void Harris(cv::Mat &img)
{
cv::Mat res(img);
for (int i = 0; i < img.rows-1; i++)
{
for (int j = 0; j < img.cols-1; j++)
{
//std::cout << i << " " << j << std::endl;
uchar gx = img.at<uchar>(i+1,j)-img.at<uchar>(i,j);
uchar gy = img.at<uchar>(i,j+1)-img.at<uchar>(i,j);
uchar gx2 = gx * gx;
uchar gy2 = gy * gy;
uchar gxgy = gx * gy;
uchar a = gx2;//Guassint(gx2) * gx2;
uchar c = gy2;//Guassint(gy2) * gy2;
uchar b = gxgy;//Guassint(gxgy) * gxgy;
uchar r = a*c - b*b - 0.05*(a + c)*(a + c);
//std::cout << static_cast<double>(r) << std::endl;
res.at<uchar>(i,j) = static_cast<double>(r) < 255 ? static_cast<uchar>(255) : static_cast<uchar>(0);
//if (res.at<uchar>(i,j) == static_cast<uchar>(0)) cv::circle(res, cv::Point(i,j), 5, 0);
}
}
cv::erode(res, res, cv::Mat());
cv::imwrite("../Pics/Harris.jpg", res);
};
手撸了一个非常简陋的Harris角点检测算法,效果确实不好,试试OpenCV自带的
void CornerHarris(const cv::Mat &img)
{
cv::Mat res;
cv::cornerHarris(img,res,3,3,0.01);
double threshold = 0.0001;
cv::threshold(res,res,threshold,255,cv::THRESH_BINARY_INV);
cv::imwrite("../Pics/CornerHarris.jpg", res);
};
效果确实比我手撸的好