OpenCV 2.4+ C++ 边缘梯度计算

很久没有看到这么好的文章了,必转~~~

图像的边缘

图像的边缘从数学上是如何表示的呢?

图像的边缘上,邻近的像素值应当显著地改变了。而在数学上,导数是表示改变快慢的一种方法。梯度值的大变预示着图像中内容的显著变化了。

用更加形象的图像来解释,假设我们有一张一维图形。下图中灰度值的“跃升”表示边缘的存在:

    

使用一阶微分求导我们可以更加清晰的看到边缘“跃升”的存在(这里显示为高峰值):

    

由此我们可以得出:边缘可以通过定位梯度值大于邻域的相素的方法找到。

 

卷积

卷积可以近似地表示求导运算。

那么卷积是什么呢?

卷积是在每一个图像块与某个算子(核)之间进行的运算。

核?!

核就是一个固定大小的数值数组。该数组带有一个锚点 ,一般位于数组中央。

 可是这怎么运算啊?

假如你想得到图像的某个特定位置的卷积值,可用下列方法计算:

  1. 将核的锚点放在该特定位置的像素上,同时,核内的其他值与该像素邻域的各像素重合;
  2. 将核内各值与相应像素值相乘,并将乘积相加;
  3. 将所得结果放到与锚点对应的像素上;
  4. 对图像所有像素重复上述过程。

用公式表示上述过程如下:

    

在图像边缘的卷积怎么办呢?

计算卷积前,OpenCV通过复制源图像的边界创建虚拟像素,这样边缘的地方也有足够像素计算卷积了。

 

近似梯度

比如内核为3时。

首先对x方向计算近似导数:

然后对y方向计算近似导数:

然后计算梯度:

当然你也可以写成:

 

开始求梯度

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

int main( int argc, char** argv ){

    Mat src, src_gray;
    Mat grad;
    char* window_name = "求解梯度";
    int scale = 1;
    int delta = 0;
    int ddepth = CV_16S;

    int c;

    src = imread( argv[1] );

    if( !src.data ){
        return -1;
    }

    //高斯模糊
    GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

    //转成灰度图
    cvtColor( src, src_gray, CV_RGB2GRAY );

    namedWindow( window_name, CV_WINDOW_AUTOSIZE );

    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;

    Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_x, abs_grad_x );

    Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_y, abs_grad_y );

    addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

    imshow( window_name, grad );

    waitKey(0);

    return 0;
}

 

Sobel函数

索贝尔算子(Sobel operator)计算。

C++: void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3,
double scale=1, double delta=0, intborderType=BORDER_DEFAULT )
参数
  • src – 输入图像。
  • dst – 输出图像,与输入图像同样大小,拥有同样个数的通道。
  • ddepth –
    输出图片深度;下面是输入图像支持深度和输出图像支持深度的关系:
    • src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F
    • src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F
    • src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F
    • src.depth() = CV_64F, ddepth = -1/CV_64F

    当 ddepth为-1时, 输出图像将和输入图像有相同的深度。输入8位图像则会截取顶端的导数。

  • xorder – x方向导数运算参数。
  • yorder – y方向导数运算参数。
  • ksize – Sobel内核的大小,可以是:1,3,5,7。
  • scale – 可选的缩放导数的比例常数。
  • delta – 可选的增量常数被叠加到导数中。
  • borderType – 用于判断图像边界的模式。

代码注释:

//在x方向求图像近似导数
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );

//在y方向求图像近似导数
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );

如果我们打印上面两个输出矩阵,可以看到grad_x和grad_y中的元素有正有负。

当然,正方向递增就是正的,正方向递减则是负值。

这很重要,我们可以用来判断梯度方向。

 

convertScaleAbs函数

线性变换转换输入数组元素成8位无符号整型。

C++: void convertScaleAbs(InputArray src, OutputArray dst, double alpha=1, double beta=0)
参数
  • src – 输入数组。
  • dst – 输出数组。
  • alpha – 可选缩放比例常数。
  • beta – 可选叠加到结果的常数。

对于每个输入数组的元素函数convertScaleAbs 进行三次操作依次是:缩放,得到一个绝对值,转换成无符号8位类型。

对于多通道矩阵,该函数对各通道独立处理。如果输出不是8位,将调用Mat::convertTo 方法并计算结果的绝对值,例如:

Mat_<float> A(30,30);
randu(A, Scalar(-100), Scalar(100));
Mat_<float> B = A*5 + 3;
B = abs(B);

为了能够用图像显示,提供一个直观的图形,我们利用该方法,将-256 — 255的导数值,转成0 — 255的无符号8位类型。

 

addWeighted函数

计算两个矩阵的加权和。

C++: void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma,
OutputArray dst, intdtype=-1)
参数
  • src1 – 第一个输入数组。
  • alpha – 第一个数组的加权系数。
  • src2 – 第二个输入数组,必须和第一个数组拥有相同的大小和通道。
  • beta – 第二个数组的加权系数。
  • dst – 输出数组,和第一个数组拥有相同的大小和通道。
  • gamma – 对所有和的叠加的常量。
  • dtype – 输出数组中的可选的深度,当两个数组具有相同的深度,此系数可设为-1,意义等同于选择与第一个数组相同的深度。

函数addWeighted 两个数组的加权和公式如下:

    

在多通道情况下,每个通道是独立处理的,该函数可以被替换成一个函数表达式:

    dst = src1*alpha + src2*beta + gamma;

利用convertScaleAbs和addWeighted,我们可以对梯度进行一个可以用图像显示的近似表达。

这样我们就可以得到下面的效果:

 

梯度方向

但有时候边界还不够,我们希望得到图片色块之间的关系,或者研究样本的梯度特征来对机器训练识别物体时候,我们还需要梯度的方向。

二维平面的梯度定义为:

    

这很好理解,其表明颜色增长的方向与x轴的夹角。

但Sobel算子对于沿x轴和y轴的排列表示的较好,但是对于其他角度表示却不够精确。这时候我们可以使用Scharr滤波器。

Scharr滤波器的内核为:

    

这样能提供更好的角度信息,现在我们修改原程序,改为使用Scharr滤波器进行计算:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

int main( int argc, char** argv ){

    Mat src, src_gray;
    Mat grad;
    char* window_name = "梯度计算";
    int scale = 1;
    int delta = 0;
    int ddepth = CV_16S;

    int c;

    src = imread( argv[1] );

    if( !src.data ){
        return -1;
    }

    GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

    cvtColor( src, src_gray, CV_RGB2GRAY );

    namedWindow( window_name, CV_WINDOW_AUTOSIZE );

    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;

    //改为Scharr滤波器计算x轴导数
    Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_x, abs_grad_x );

    //改为Scharr滤波器计算y轴导数
    Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
    convertScaleAbs( grad_y, abs_grad_y );

    addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

    imshow( window_name, grad );

    waitKey(0);

    return 0;
}

Scharr函数接受参数与Sobel函数相似,这里就不叙述了。

下面我们通过divide函数就能得到一个x/y的矩阵。

对两个输入数组的每个元素执行除操作。

C++: void divide(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
C++: void divide(double scale, InputArray src2, OutputArray dst, int dtype=-1)
参数
  • src1 – 第一个输入数组。
  • src2 – 第二个输入数组,必须和第一个数组拥有相同的大小和通道。
  • scale – 缩放系数。
  • dst – 输出数组,和第二个数组拥有相同的大小和通道。
  • dtype – 输出数组中的可选的深度,当两个数组具有相同的深度,此系数可设为-1,意义等同于选择与第一个数组相同的深度。

该函数对两个数组进行除法:

  

或则只是缩放系数除以一个数组:

  

这种情况如果src2是0,那么dst也是0。不同的通道是独立处理的。

 

被山寨的原文

Sobel
Derivatives
 . OpenCV.org

Image
Filtering
 . OpenCV.org

时间: 2024-12-02 12:08:15

OpenCV 2.4+ C++ 边缘梯度计算的相关文章

Javascript图像处理:边缘梯度计算

前言 上一篇文章,我们讲解了图像处理中的膨胀和腐蚀函数,这篇文章将做边缘梯度计算函数.直接摘自OpenCV 2.4+ C++ 边缘梯度计算. 图像的边缘 图像的边缘从数学上是如何表示的呢? 图像的边缘上,邻近的像素值应当显著地改变了.而在数学上,导数是表示改变快慢的一种方法.梯度值的大变预示着图像中内容的显著变化了. 用更加形象的图像来解释,假设我们有一张一维图形.下图中灰度值的"跃升"表示边缘的存在: 使用一阶微分求导我们可以更加清晰的看到边缘"跃升"的存在(这里

javascript图像处理—边缘梯度计算函数_javascript技巧

前言 上一篇文章,我们讲解了图像处理中的膨胀和腐蚀函数,这篇文章将做边缘梯度计算函数. 图像的边缘 图像的边缘从数学上是如何表示的呢? 图像的边缘上,邻近的像素值应当显著地改变了.而在数学上,导数是表示改变快慢的一种方法.梯度值的大变预示着图像中内容的显著变化了. 用更加形象的图像来解释,假设我们有一张一维图形.下图中灰度值的"跃升"表示边缘的存在: 使用一阶微分求导我们可以更加清晰的看到边缘"跃升"的存在(这里显示为高峰值): 由此我们可以得出:边缘可以通过定位梯

Javascript图像处理:平滑处理

前言 上一篇文章,我们讲解了图像的虚拟边缘,这篇文章开始进行平滑(也就是模糊)处理. 基本原理 这里直接引用OpenCV 2.4+ C++ 平滑处理和OpenCV 2.4+ C++ 边缘梯度计算的相关内容: 平滑也称模糊, 是 一项简单且使用频率很高的图像处理方法. 平滑处理时需要用到一个滤波器. 最常用的滤波器是线性滤波器,线性 滤波处理的输出像素值(例如:g(i,j))是输入像素值(例如:f(i+k,j+l))的加权平均: g(i,j) = sum_{k,l} f (i+k, j+l) h(

Javascript图像处理—平滑处理实现原理_javascript技巧

前言 上一篇文章,我们讲解了图像的虚拟边缘,这篇文章开始进行平滑(也就是模糊)处理. 基本原理 这里直接引用OpenCV 2.4+ C++ 平滑处理和OpenCV 2.4+ C++ 边缘梯度计算的相关内容: 平滑也称模糊, 是一项简单且使用频率很高的图像处理方法. 平滑处理时需要用到一个滤波器 . 最常用的滤波器是线性 滤波器,线性滤波处理的输出像素值(例如:)是输入像素值(例如:)的加权平均: 称为核 , 它仅仅是一个加权系数. 这里涉及一种叫做"卷积"的运算,那么卷积是什么呢? 卷

Javascript图像处理:图像金字塔

前言 上一篇文章,我们讲解了边缘梯度计算函数,这篇文章我们来了解图像金字塔. 图像金字塔? 图像金字塔被广泛用于计算机视觉应用中. 图像金字塔是一个图像集合,集合中所有的图像都源于同一个原始图像,而且是通过对原始图像连续降采样获得的. --<学习OpenCV> 常见的图像金字塔有下面两种: 高斯金字塔(Gaussian pyramid): 用来向下采样 拉普拉斯金字塔(Laplacian pyramid): 用来从金字塔低层图像重建上层未采样图像 高斯金字塔 类似金字塔一样,高斯金字塔从底层原

jsvascript图像处理—(计算机视觉应用)图像金字塔_javascript技巧

前言 上一篇文章,我们讲解了边缘梯度计算函数,这篇文章我们来了解图像金字塔. 图像金字塔? 图像金字塔被广泛用于计算机视觉应用中. 图像金字塔是一个图像集合,集合中所有的图像都源于同一个原始图像,而且是通过对原始图像连续降采样获得的. 常见的图像金字塔有下面两种: •高斯金字塔(Gaussian pyramid): 用来向下采样 •拉普拉斯金字塔(Laplacian pyramid): 用来从金字塔低层图像重建上层未采样图像 高斯金字塔 类似金字塔一样,高斯金字塔从底层原始图逐渐向下采样,越来越

赛特斯柔性网络产品总监李晏:SDN/NFV视角下的边缘网络和边缘计算

由天地互连.下一代互联网国家工程中心主办的"2017全球SDNFV技术大会" (2017.chinasdn.org)于 2017年8月2-3日,在北京国宾酒店火热开幕.在3日下午的大会中,赛特斯柔性网络产品总监李晏做了题为"SDN/NFV视角下的边缘网络和边缘计算"的精彩演讲. 赛特斯柔性网络产品总监李晏 以下为现场演讲实录: 李晏:大家好.我们的客户主要是电信运营商,包括广电这样的客户.我们主要做的是基于SDN和NFV技术给我们的客户提供一些解决方案.我今天的题目

边缘计算将取代云计算?别闹了!

任何人都知道,物联网并不是一个玩笑,而且它确实是云的一个组成部分.对于物联网来说有一个关键的问题,就是如何从大量的设备中获取数据.思科系统预测,到2020年,云流量可能会增加近四倍,从2015年开始平均每年全年增加3.9 ZB,到2020年,平均每年增加量将会达到14.1 ZB. 因此,我们可以从物联网的发展中获得云计算的"完美风暴".毕竟,物联网是在处理设备生成的有意义的数据,而云计算则通过集中计算和存储来使用这些数据.这两者的增长都会变得难以控制. 那么我们该怎么做呢?答案就是&q

边缘计算将取代云计算?别开玩笑了

任何人都知道,物联网并不是一个玩笑,而且它确实是云的一个组成部分.对于物联网来说有一个关键的问题,就是如何从大量的设备中获取数据.思科系统预测,到2020年,云流量可能会增加近四倍,从2015年开始平均每年全年增加3.9 ZB,到2020年,平均每年增加量将会达到14.1 ZB. 因此,我们可以从物联网的发展中获得云计算的"完美风暴".毕竟,物联网是在处理设备生成的有意义的数据,而云计算则通过集中计算和存储来使用这些数据.这两者的增长都会变得难以控制. 那么我们该怎么做呢?答案就是&q