计算机视觉(二)

形状与特征

Posted by Gavin on October 10, 2019

为君持酒劝斜阳

且向花间留晚照

曲线

形状

形状是物体的一种属性,往往可以根据形状信息识别物体。图像的边缘点连接起来就是物体二位平面的投影,即物体的轮廓。但是轮廓的表示比较消耗资源,通常使用数学模型比如曲线来表示。

曲线的几何属性

  • 长度:
\[\int^{t2}_{t1}\sqrt{(x'(t))^2+(y'(t))^2}\]
  • 曲线切方向:
\[\phi = {(x(t),y(t))' \over |(x(t),y(t))'|}\]
  • 曲率:
\[\Theta = (x(t),y(t))''\]

曲线的离散化

  • 曲线长度:
\[s = \sum^n_{i=2}\sqrt{(x_i-x_{i-1})^2+(y_i-y_{i-1})^2}\]
  • 曲率切向量(k斜率):
\[\Phi = \arctan{ {y_{i+k} - y_i} \over x_{i+k} - x_i }\]
  • 曲率(k曲率):
\[\Theta = \theta_l - \theta_r\]

曲线表示

  • 边缘点序列
    • 直接用边缘点序列表示边缘
      • 链码
      • \(\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)的特征值,检测角点时却并不需要计算,我们计算如下的响应值来判断角点:

\[R=det M - \alpha(traceM)^2\]

其中\(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);
};

效果确实比我手撸的好