xiaozongpeng 发表于 2021-12-21 09:32

图像处理基础 (四)边缘提取之 LOG 和 DOG 算子

原理

Laplace

对连续二元标量函数 https://www.zhihu.com/equation?tex=f%28x%2Cy%29 ,其拉普拉斯算子是梯度的散度,即

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cnabla+%5Ccdot+%7B%5Cnabla%7Bf%7D%7D+++%3D+%5Cfrac%7B%5Cpartial%5E2%7Bf%7D%7D%7B%5Cpartial%7Bx%5E2%7D%7D%2B+%5Cfrac%7B%5Cpartial%5E2%7Bf%7D%7D%7B%5Cpartial%7By%5E2%7D%7D%5Cend%7Baligned%7D
对于离散图像而言,

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cnabla+%5Ccdot+%7B%5Cnabla%7Bf%7D%7D+++%3D++f%28x+%2B+1%2Cy%29+%2B+f%28x+-+1%2Cy%29+%2B+f%28x%2C+y+%2B+1%29+%2B+f%28x%2C+y+-+1%29+-+4f%28x%2Cy%29+%5Cend%7Baligned%7D
对应的滤波模板是


如果结果 < 0,说明滤波中心是局部极大值,可能是噪声点,也可能是边缘上的点;结果 = 0,说明滤波中心很可能处于平坦区域;结果 > 0,说明滤波中心是局部极小值,可能是噪声点,也可能是边缘。
很明显,laplace 可以用于定位局部极值,用于寻找边缘
cv::Mat laplace_extract_edges(const cv::Mat& source) {
    // 获取信息
    const int H = source.rows;
    const int W = source.cols;
    // padding
    const auto padded_image = make_pad(source, 1, 1);
    const int W2 = padded_image.cols;
    // 准备结果
    auto result = source.clone();
    // 开始滤波
    for(int i = 0;i < H; ++i) {
      const uchar* row_ptr = padded_image.data + (1 + i) * W2 + 1;
      uchar* const res_ptr = result.data + i * W;
      for(int j = 0;j < W; ++j) {
            // 每个点, 找上下左右四个点
            const uchar u = row_ptr, d = row_ptr, l = row_ptr, r = row_ptr;
            res_ptr = cv::saturate_cast<uchar>(std::abs(u + d + l + r - 4 * row_ptr));
      }
    }
    return result;
}



Laplace 边缘检测的例子

但是 laplace 对于噪声点也能检测出来,所以给上面的图像加一些高斯噪声,边缘检测结果就会差得多



Laplace 边缘检测 - 对噪声图像的处理

LOG

为了解决上面的问题,一个很简单的想法就是先对图像做高斯平滑,去除一些噪声,再使用 laplace 处理图像。这两步都可以是卷积的方式。连续的卷积,可以先求高斯平滑函数的 laplace,然后再对图像做滤波,证明略。
因此,接下来求高斯平滑函数的 laplace 算子
二元连续的高斯函数https://www.zhihu.com/equation?tex=G%28x%2Cy%2C%5Csigma%29+%3D+%5Cfrac%7B1%7D%7B2+%5Cpi+%5Csigma%5E2%7D+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D ,求 laplace, 梯度的散度,需要求两次偏导

https://www.zhihu.com/equation?tex=%5Cfrac%7B%5Cpartial%7BG%7D%7D%7B%5Cpartial%7Bx%7D%7D+%3D+%5Cfrac%7B1%7D%7B2%5Cpi+%5Csigma%5E2%7D+%5Ccdot+-%5Cfrac%7B1%7D%7B2+%5Csigma%5E2%7D2x+%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D+%3D+-%5Cfrac%7Bx%7D%7B2%5Cpi+%5Csigma%5E4%7D%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cfrac%7B%5Cpartial%5E2%7BG%7D%7D%7B%5Cpartial%7Bx%7D%5E2%7D+%26%3D++-%5CBig%28++%5Cfrac%7B1%7D%7B2%5Cpi+%5Csigma%5E4%7D%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D+-%5Cfrac%7B2x%7D%7B2%5Csigma%5E2%7D+%5Ccdot++e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D+%5Ccdot+%5Cfrac%7Bx%7D%7B2%5Cpi%5Csigma%5E4%7D+++++%5CBig%29+%5C%5C++%26%3D+-+++%5Cfrac%7B1%7D%7B2%5Cpi+%5Csigma%5E4%7D%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D++++%2B+%5Cfrac%7Bx%5E2%7D%7B2%5Cpi%5Csigma%5E6%7D+%5Ccdot++e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D+%5C%5C++%26%3D+%5Cfrac%7Bx%5E2+-+%5Csigma%5E2%7D%7B2%5Cpi+%5Csigma%5E6%7D+%5Ccdot++e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D+%5Cend%7Baligned%7D+++
由于 https://www.zhihu.com/equation?tex=x%2Cy 在函数中是对称的,因此,很容易可以得到

https://www.zhihu.com/equation?tex=%5Cfrac%7B%5Cpartial%7BG%7D%7D%7B%5Cpartial%7By%7D%7D++%3D+-%5Cfrac%7By%7D%7B2%5Cpi+%5Csigma%5E4%7D%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cfrac%7B%5Cpartial%5E2%7BG%7D%7D%7B%5Cpartial%7By%7D%5E2%7D+++%26%3D+%5Cfrac%7By%5E2+-+%5Csigma%5E2%7D%7B2%5Cpi+%5Csigma%5E6%7D+%5Ccdot++e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D+%5Cend%7Baligned%7D+++
由此可得,高斯函数的 laplace ,LOG

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cnabla+%5Ccdot+%7B%5Cnabla%7BG%28x%2Cy%2C%5Csigma%29%7D%7D+++%3D+%5Cfrac%7B%5Cpartial%5E2%7BG%7D%7D%7B%5Cpartial%7Bx%5E2%7D%7D%2B+%5Cfrac%7B%5Cpartial%5E2%7BG%7D%7D%7B%5Cpartial%7By%5E2%7D%7D%5Cend%7Baligned%7D+%3D++%5Cfrac%7Bx%5E2+%2B+y%5E2+-+2%5Csigma%5E2%7D%7B2%5Cpi+%5Csigma%5E6%7D+%5Ccdot++e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D

https://www.zhihu.com/equation?tex=5+%5Ctimes++5 的 LOG 模板如下



5x5 高斯拉普拉斯 LOG 模板

在图像处理中,平坦区域的 laplace 为 0,因为没有梯度。上面的 https://www.zhihu.com/equation?tex=5+%5Ctimes+5 模板是离散的,和不为 0,因此需要略微修改,简单的做法是统一减去一个微小量,使得和为 0。为了加快处理速度,也可以全转成 int,但会损失一些精度。



高斯拉普拉斯 LOG 函数

上图是二元连续 LOG 函数,可以看到,从滤波中心往外扩散,有一个先增大后减小的过程。对图像滤波时,分三种情况
平坦区域
因为没有梯度,因此不管怎么加权相加减,LOG 模板的响应值为 0



平坦区域,一维切面的 LOG 响应

边缘
LOG 模板和图像无关,考虑下图灰度剧烈变化的区域



灰度剧烈变化区域,一维切面的 LOG 响应

黑色线表示灰度值,从一个较低的平坦区域到一个较高的平坦区域,两侧的平坦区域处(蓝色)的 LOG 响应值都为 0;灰度跃升时,考虑红色部分,刚开始由于右边加入了一些高区域(绿色)的灰度,所以,LOG 响应值 > 0。



灰度剧烈变化区域,一维切面的 LOG 响应

继续向右移动,由于还剩下一部分在低灰度(绿色),此时,LOG 响应值 < 0。
从 > 0 渐变到 < 0,LOG 模板在从低灰度区域到高灰度区域移动的过程中,存在某个时刻的响应值 = 0,但由于是离散的,这个点不一定是图像中的点,粗略检测边缘的时候,可以检查两侧 LOG 响应值符号是否相反,如果一正一负,说明中间存在灰度剧烈变化的点,可以看作边缘。
一正一负,在边缘的两侧,靠近低灰度的区域 LOG 响应值 > 0,靠近高灰度的区域 LOG 区域响应值 < 0。
噪声点


如果 LOG 遇到的是噪声点,考虑上面的情况,如果噪声处在 LOG 模板 < 0的区域(绿色),那么 LOG 响应值一定 < 0,因为突然减去了一个高灰度值,而其它平坦区域不变。


如果考虑噪声处在 LOG 模板 > 0 的区域,那么 LOG 响应值是 > 0 的,同时考虑另一侧的情况


也是一样的,平坦区域为 0,相比之下加入了一个高灰度值,LOG 滤波结果 > 0,因此 LOG 对于噪声的响应值变化如下图


两侧的平坦区域,LOG 响应值 = 0;噪声处于 LOG > 0 区域时,LOG 响应值 > 0;噪声处于 LOG< 0区域时,LOG 响应值 < 0。
结论是:按照之前边缘检测的方法,找两侧一正一负,LOG 依旧无法避免噪声。。???
综上,可以得到 LOG 检测边缘的具体步骤

[*]根据方差和滤波核大小确定 LOG 模板
[*]LOG 模板对图像做滤波
[*]在滤波结果中,找两侧响应值分别一正一负的点
[*]设置一个阈值 t,两侧像素之差 > t 的点被视作真正的边缘
具体实现时,考虑以下 4 种一正一负的情况



寻找过 0 点,一正一负的四种情况

代码
std::vector<double> laplace_of_gaussi(const cv::Mat& source, const int radius, const double sigma) {
    // padding 处理边缘
    const auto padded_image = make_pad(source, radius, radius);
    const int W2 = padded_image.cols;
    // 准备一个 LOG 模板
    const int window_len = (radius << 1) + 1;
    const int window_size = window_len * window_len;
    const double sigma_2 = sigma * sigma;
    const double sigma_6 = sigma_2 * sigma_2 * sigma_2;
    double LOG;
    int LOG_offset;
    int offset = 0;
    for(int i = -radius; i <= radius; ++i) {
      for(int j = -radius; j <= radius; ++j) {
            const double distance = i * i + j * j;
            LOG = (distance - 2 * sigma_2) / sigma_6 * std::exp(-distance / (2 * sigma_2));
            LOG_offset = i * W2 + j;
            ++offset;
      }
    }
    /*
   0.001 0.0110.0440.0670.044 0.011 0.001
   0.011 0.1000.2720.3260.272 0.100 0.011
   0.044 0.2720.088 -0.6290.088 0.272 0.044
   0.067 0.326 -0.629 -2.460 -0.629 0.326 0.067
   0.043 0.2720.088 -0.6290.088 0.272 0.044
   0.011 0.1000.2720.3260.272 0.100 0.011
   0.001 0.0110.0440.0670.044 0.011 0.001
   */
    // 平坦区域, LOG 响应为 0
    double sum_value = 0.0;
    for(int i = 0;i < offset; ++i) sum_value += LOG;
    for(int i = 0;i < offset; ++i) LOG -= sum_value / offset;
    // 收集原始图像信息
    const int H = source.rows;
    const int W = source.cols;
    const int length = H * W;
    // LOG 模板扫过
    std::vector<double> LOG_result(length, 0);
    for(int i = 0;i < H; ++i) {
      const uchar* const row_ptr = padded_image.data + i * W2;
      double* const res_ptr = LOG_result.data() + i * W;
      for(int j = 0;j < W; ++j) {
            double conv_sum = 0;
            for(int k = 0;k < offset; ++k)
                conv_sum += LOG * row_ptr];
            res_ptr = conv_sum;
      }
    }
    return LOG_result;
}

cv::Mat laplace_of_gaussi_edge_detection(const cv::Mat& source, const int radius, const double sigma, const double threshold) {
    // 获取 LOG 滤波的结果
    const auto LOG_result = laplace_of_gaussi(source, radius, sigma);
    const int H = source.rows;
    const int W = source.cols;
    // 准备结果
    auto result_image = source.clone();
    // 现在 LOG_result 里面是结果, 开始找两侧一正一负的点
    for(int i = 1;i < H - 1; ++i) {
      const double* const row_ptr = LOG_result.data() + i * W;
      uchar* const res_ptr = result_image.data + i * W;
      for(int j = 1;j < W - 1; ++j) {
            // 开始找四个方向
            if((row_ptr * row_ptr < 0 and std::abs(row_ptr - row_ptr) > threshold) or
               (row_ptr * row_ptr < 0 and std::abs(row_ptr - row_ptr) > threshold) or
               (row_ptr * row_ptr < 0 and std::abs(row_ptr - row_ptr) > threshold) or
               (row_ptr * row_ptr < 0 and std::abs(row_ptr - row_ptr) > threshold)) {
                res_ptr = 255;
            }
            else res_ptr = 0;
      }
    }
    return result_image;
}





LOG 边缘检测结果

对于之前的噪声图像





laplace of gaussi(LOG)

虽然无法完全去除噪声,但是和之前直接用 laplace 的效果相比,还是减弱了一些



laplace

DOG

二元连续高斯函数 https://www.zhihu.com/equation?tex=G%28x%2Cy%2C%5Csigma%29+%3D+%5Cfrac%7B1%7D%7B2%5Cpi+%7D%5Csigma%5E%7B-2%7D+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%7D+%5Csigma%5E%7B-2%7D%7D 对方差求导,可以得到

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cfrac%7B%5Cpartial%7BG%7D%7D%7B%5Cpartial%7B%5Csigma%7D%7D+%26%3D+%5Cfrac%7B1%7D%7B2%5Cpi%7D+%5Ccdot+%28-2%29+%5Ccdot+%5Csigma%5E%7B-3%7D+%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%7D+%5Csigma%5E%7B-2%7D%7D+-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%7D+%5Ccdot+%28-2%29+%5Ccdot+%5Csigma%5E%7B-3%7D+%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%7D+%5Csigma%5E%7B-2%7D%7D++%5Ccdot+%5Cfrac%7B1%7D%7B2%5Cpi%7D%5Csigma%5E%7B-2%7D+%5C%5C++%26%3D+%5CBig%28+-%5Cfrac%7B1%7D%7B%5Cpi%5Csigma%5E%7B3%7D%7D+%2B+%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Cpi%5Csigma%5E5%7D+%5CBig%29+%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%7D+%5Csigma%5E%7B-2%7D%7D+%5C%5C++%26%3D+%5Cfrac%7Bx%5E2+%2B+y%5E2+-+2%5Csigma%5E2%7D%7B2%5Cpi%5Csigma%5E5%7D++%5Ccdot+e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E%7B2%7D%7D+%7D+%5C%5C+%5Cend%7Baligned%7D
很巧地,前面得到的

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+LOG%28x%2Cy%2C%5Csigma%29+%3D+%5Cfrac%7B%5Cpartial%5E2%7BG%7D%7D%7B%5Cpartial%7Bx%5E2%7D%7D%2B+%5Cfrac%7B%5Cpartial%5E2%7BG%7D%7D%7B%5Cpartial%7By%5E2%7D%7D%5Cend%7Baligned%7D+%3D++%5Cfrac%7Bx%5E2+%2B+y%5E2+-+2%5Csigma%5E2%7D%7B2%5Cpi+%5Csigma%5E6%7D+%5Ccdot++e%5E%7B-%5Cfrac%7Bx%5E2+%2B+y%5E2%7D%7B2%5Csigma%5E2%7D%7D ,
二者就差一个,即

https://www.zhihu.com/equation?tex=%5Cfrac%7B%5Cpartial%7BG%7D%7D%7B%5Cpartial%7B%5Csigma%7D%7D++%3D+%5Csigma+%5Ccdot+LOG
根据极限,又可以得到

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cfrac%7B%5Cpartial%7BG%7D%7D%7B%5Cpartial%7B%5Csigma%7D%7D+%26%3D+%5Clim_%7B%5CDelta%5Csigma%5Crightarrow0%7D%7B%5Cfrac%7BG%28x%2C+y%2C+%5Csigma+%2B+%5CDelta%5Csigma%29+-+G%28x%2C+y%2C+%5Csigma%29%7D%7B%5CDelta%5Csigma%7D%7D%5C%5C++%26%3D+%5Clim_%7Bk%5Crightarrow1%7D%7B%5Cfrac%7BG%28x%2C+y%2C+k+%5Csigma+%29+-+G%28x%2C+y%2C+%5Csigma%29%7D%7B%28k+-+1%29%5Ccdot+%5Csigma%7D%7D%5C%5C+%5Cend%7Baligned%7D
结合前面两式,当 https://www.zhihu.com/equation?tex=k+%5Capprox+1 ,有

https://www.zhihu.com/equation?tex=%5Cbegin%7Baligned%7D+%5Cfrac%7BG%28x%2C+y%2C+k+%5Csigma+%29+-+G%28x%2C+y%2C+%5Csigma%29%7D%7B%28k+-+1%29%5Ccdot+%5Csigma%7D+%26%5Capprox+%5Csigma+%5Ccdot+LOG+%5C%5C++G%28x%2C+y%2C+k+%5Csigma+%29+-+G%28x%2C+y%2C+%5Csigma%29+%26%5Capprox+%28k+-+1%29%5Ccdot+%5Csigma%5E2%5Ccdot+LOG+%5Cend%7Baligned%7D
两个不同方差的高斯函数相减,记作 https://www.zhihu.com/equation?tex=DOG 即可近似得到 https://www.zhihu.com/equation?tex=LOG ,可见,当方差确定时,二者的函数曲线增减趋势是十分相近的,如下图



DOG 和 LOG 函数,一维切面(图片来源于网络)

可以认为,DOG 近似 LOG。
边缘检测时,二者相比

[*]DOG 只需要两个高斯模板分别对图像滤波,然后相减,计算上可以加速,例如分离成两个一维的模板
[*]LOG 更为精确。
#include "faster_gaussi_filter.h"
std::vector<double> difference_of_gaussi(const cv::Mat& source, const int radius, const double sigma, const double k) {
    // 两个高斯卷积
    const auto lhs = faster_2_gaussi_filter_channel(source, 2 * radius + 1, k * sigma, k * sigma);
    const auto rhs = faster_2_gaussi_filter_channel(source, 2 * radius + 1, sigma, sigma);
    // 准备结果
    const int length = source.rows * source.cols;
    std::vector<double> result_double(length, 0);
    for(int i = 0;i < length; ++i) result_double = lhs.data - rhs.data;//
    returnresult_double;
}

cv::Mat laplace_of_gaussi_edge_detection(const cv::Mat& source, const int radius, const double sigma, const double threshold) {
    // 获取 LOG 结果
    // const auto LOG_result = laplace_of_gaussi(source, radius, sigma);
    const auto LOG_result = difference_of_gaussi(source, radius, sigma, 1.1);
    // ...... 定位过零的点作为边缘
}



DOG 检测含噪声图像的边缘

极值点检测

DOG 比较著名的应用就是在 sift 中定位特征点。每个金字塔都包含不同尺度的高斯模糊之后的图像,下图是某个金字塔(某个分辨率)的 5 个尺度高斯模糊的图像组,相减得到 4 个 DOG 高斯差分图像,



DOGin sift

之后,相邻三层 DOG 之间,寻找中间那一层的极值点,如下


比较同一个 DOG 以及上下相邻的 DOG,在邻域 3x3 内共 26 个点,判定是否为极大值或者极小值,如果在多个尺度下都能保证是极值,则可以认为是稳定的特征点,以下是一个粗略的实现
// 单通道关键点检测
std::pair< keypoints_type, keypoints_type > difference_of_gaussi_keypoints_detection(
      const cv::Mat& source, const int radius,
      const std::vector< double > sigma_list,
      const double threshold) {
    const int C = source.channels();
    if(C != 1) {
      std::cout << "只支持单通道图像 !" << std::endl;
      return std::pair< keypoints_type, keypoints_type >();
    }
    // 四次高斯模糊, 3 张 DOG 图
    assert(sigma_list.size() == 4);

    // 根据 sigma_list 的方差分别做高斯模糊
    const int kernel_size = (radius << 1) + 1;
    const auto gaussi_1 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list, sigma_list);
    const auto gaussi_2 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list, sigma_list);
    const auto gaussi_3 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list, sigma_list);
    const auto gaussi_4 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list, sigma_list);
   
    // 分别求 DOG
    const int H = source.rows;
    const int W = source.cols;
    const int length = H * W;
    std::vector<double> DOG_down(length), DOG_mid(length), DOG_up(length);
    for(int i = 0;i < length; ++i) DOG_down = gaussi_1.data - gaussi_2.data;
    for(int i = 0;i < length; ++i) DOG_mid = gaussi_2.data - gaussi_3.data;
    for(int i = 0;i < length; ++i) DOG_up = gaussi_3.data - gaussi_4.data;

    // 准备结果, 返回极大值与极小值的点
    keypoints_type max_points;
    keypoints_type min_points;

    // 三层之间找极值
    for(int i = 1;i < H - 1; ++i) {
      double* const down = DOG_down.data() + i * W;
      double* const mid = DOG_mid.data() + i * W;
      double* const up = DOG_up.data() + i * W;
      for(int j = 1;j < W - 1; ++j) {
            // 中间这个点的值, 和最近的 26 个点比较大小
            const auto center = mid;
            // 极大值
            if(center > mid and center > mid and
               center > mid and center > mid and center > mid and
               center > mid and center > mid and center > mid and

               center > down and center > down and center > down and
               center > down and center > down and center > down and
               center > down and center > down and center > down and

               center > up and center > up and center > up and
               center > up and center > up and center > up and
               center > up and center > up and center > up) {
                //保留响应值比较强的点作为特征点
                if(std::abs(center) > threshold) max_points.emplace_back(i, j);
            }
            // 极小值
            if(center < mid and center < mid and
               center < mid and center < mid and center < mid and
               center < mid and center < mid and center < mid and

               center < down and center < down and center < down and
               center < down and center < down and center < down and
               center < down and center < down and center < down and

               center < up and center < up and center < up and
               center < up and center < up and center < up and
               center < up and center < up and center < up) {
                if(std::abs(center) > threshold) min_points.emplace_back(i, j);
            }
      }
    }
    return std::make_pair(max_points, min_points);
}


void demo_5() {
    // 读取图像
    const std::string image_path("../images/detail/a0015-DSC_0081.png");
    auto origin_image = cv::imread(image_path);
   
    cv::Mat detected_result = origin_image.clone();
    // BGR -> Gray 支持灰度图像
    cv::cvtColor(origin_image, origin_image, cv::COLOR_BGR2GRAY);

    // LOG 检测边缘
    std::pair<keypoints_type, keypoints_type > key_points;
    key_points = difference_of_gaussi_keypoints_detection(origin_image, 3, {0.3, 0.4, 0.5, 0.6}, 6);

    // 极大值
    for(const auto point : key_points.first) cv::circle(detected_result, cv::Point(point.second, point.first), 3, CV_RGB(255, 0, 0));

    // 极小值
    for(const auto point : key_points.second) cv::circle(detected_result, cv::Point(point.second, point.first), 3, CV_RGB(0, 255, 0));

    // 展示结果, 保存
    auto comparison_results = cv_concat({detected_result});
    cv_show(comparison_results);
    cv_write(comparison_results, "./images/output/LOG_keypoints_detection.png");
}
结果



四个尺度的高斯模糊结果,方差分别是0.3,0.4,0.5,0.6



得到的 3 个相邻尺度的 DOG 图像



粗略检测到的特征点,极大值(红),极小值(绿)

看起来效果还可以。
另外测试几张图像




后续将在 sift 中优化。
问题


[*]LOG 检测边缘时,寻找两侧响应值相反的时候,是离散的点,太过粗略
改进


[*]double 改成 int,损失一些精度,加快计算
[*]LOG 模板也是对称的,而且可以分离成两个一维的 LOG 加快计算

参考资料


[*]https://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm
[*]https://homepages.inf.ed.ac.uk/rbf/HIPR2/zeros.htm
[*]https://senitco.github.io/2017/06/20/image-feature-LoG-DoG/
[*]https://www.cnblogs.com/YiXiaoZhou/p/5891645.html
[*]https://blog.csdn.net/gnehcuoz/article/details/52793654
[*]https://blog.csdn.net/u014453898/article/details/106461500
[*]SIFT - Lowe D G. Distinctive image features from scale-invariant keypoints. International journal of computer vision, 2004, 60(2): 91-110.
[*]测试图片来源 - MIT-Adobe-5K

acecase 发表于 2021-12-21 09:36

马克,慢慢看。

kirin77 发表于 2021-12-21 09:43

我做个总结,如果有写的不对的地方,还望指正[捂脸]。边缘检测的效果不太好,让我一度怀疑我是不是哪一步推错了,或是哪里理解错了
页: [1]
查看完整版本: 图像处理基础 (四)边缘提取之 LOG 和 DOG 算子