使用索贝尔(Sobel)进行梯度运算时的数学意义和代码实现研究

对于做图像处理的工程师来说,Sobel非常熟悉且常用。但是当我们需要使用Sobel进行梯度运算,且希望得到“数学结果”(作为下一步运算的基础)而不是“图片效果”的时候,就必须深入了解Sobel的知识原理和OpenCV实现的细节(当然我们是OpenCV支持则)。这里对具体内容进行研究。

一、基本原理

一般来说,用来表示微分的最常用的算子是索贝尔(Sobel)算子,它可以实现任意阶导数和混合偏导数(例如: ?2/?x?y)。

在x方向上用Sobel算子进行近似一阶求导的结果

Sobel算子效果,y方向近似一阶导数

OpenCV中给出了函数使用的定义

void cv::Sobel(
  cv::InputArray  src,                 // 源图像
  cv::OutputArray dst,                 // 目标图像
  int             ddepth,              // 像素深度 (如CV_8U)
  int             xorder,              // x方向对应的倒数顺序
  int             yorder,              // y方向对应的倒数顺序
  cv::Size        ksize      = 3,      // 核大小
  double          scale      = 1,      // 阈值
  double          delta      = 0,      // 偏移
  int             borderType = cv::BORDER_DEFAULT  // 边框外推方法
);

1、其中src和dst是源图像和目标图像,可以通过指明参数ddepth来确定目标图像的深度或类型(如CV_32F)。举个例子,如果src是一幅8位图像,那么dst需要至少CV_16S的深度保证不出现溢出;

2、xorder和yorder是求导顺序,其取值范围为0、1和2。0表示在这个方向上不进行求导,那2代表什么?

3、ksize是一个奇数,表示了调用的滤波器的宽和高,目前最大支持到31;

4、阈值和偏移将在把结果存入dst前调用,这有助于你将求导结果可视化.borderType参数的功能与其他卷积操作完全一样。

上面有一个遗留问题,就是xorder(yorder)取2的时候代表什么?为此翻阅OpenCV源码

step1

step 2

step3

static void getSobelKernels( OutputArray _kx, OutputArray _ky,
                             int dx, int dy, int _ksize, bool normalize, int ktype )
{
    int i, j, ksizeX = _ksize, ksizeY = _ksize;
    if( ksizeX == 1 && dx > 0 )
        ksizeX = 3;
    if( ksizeY == 1 && dy > 0 )
        ksizeY = 3;
    CV_Assert( ktype == CV_32F || ktype == CV_64F );
    _kx.create(ksizeX, 1, ktype, -1, true);
    _ky.create(ksizeY, 1, ktype, -1, true);
    Mat kx = _kx.getMat();
    Mat ky = _ky.getMat();
    if( _ksize % 2 == 0 || _ksize > 31 )
        CV_Error( CV_StsOutOfRange, "The kernel size must be odd and not larger than 31" );
    std::vector<int> kerI(std::max(ksizeX, ksizeY) + 1);
    CV_Assert( dx >= 0 && dy >= 0 && dx+dy > 0 );
    forint k = 0; k < 2; k++ )
    {
        Mat* kernel = k == 0 ? &kx : &ky;
        int order = k == 0 ? dx : dy;
        int ksize = k == 0 ? ksizeX : ksizeY;
        CV_Assert( ksize > order );
        if( ksize == 1 )
            kerI[0] = 1;
        else if( ksize == 3 )
        {
            if( order == 0 )
                kerI[0] = 1, kerI[1] = 2, kerI[2] = 1;
            else if( order == 1 )
                kerI[0] = -1, kerI[1] = 0, kerI[2] = 1;
            else
                kerI[0] = 1, kerI[1] = -2, kerI[2] = 1;
        }
        else
        {
            int oldval, newval;
            kerI[0] = 1;
            for( i = 0; i < ksize; i++ )
                kerI[i+1] = 0;
            for( i = 0; i < ksize - order - 1; i++ )
            {
                oldval = kerI[0];
                for( j = 1; j <= ksize; j++ )
                {
                    newval = kerI[j]+kerI[j-1];
                    kerI[j-1] = oldval;
                    oldval = newval;
                }
            }
            for( i = 0; i < order; i++ )
            {
                oldval = -kerI[0];
                for( j = 1; j <= ksize; j++ )
                {
                    newval = kerI[j-1] - kerI[j];
                    kerI[j-1] = oldval;
                    oldval = newval;
                }
            }
        }
        Mat temp(kernel->rows, kernel->cols, CV_32S, &kerI[0]);
        double scale = !normalize ? 1. : 1./(1 << (ksize-order-1));
        temp.convertTo(*kernel, ktype, scale);
    }
}

其中

那么,可以看见,当order ==2 时候,生成了[1 -2 1]作为类似的模板,不管是什么,这个不是我想要的。

除了上面看到的,还可以发现同时设置xorder 和 yorder的时候,最后并没有看到相加的动作。而如果我们计算的结果是梯度场的时候,就不仅要算xorder,而且要算yorder,并且最后要把这两个结果求和。如果自己编码,那么可能如下:

//求x方向偏导
   vx = *(lpSrc + IMGW + 1) - *(lpSrc + IMGW - 1) +
   *(lpSrc + 1)*2 - *(lpSrc - 1)*2 +
   *(lpSrc - IMGW + 1) - *(lpSrc - IMGW - 1);
   //求y方向偏导
   vy = *(lpSrc + IMGW - 1) - *(lpSrc - IMGW - 1) +
   *(lpSrc + IMGW)*2 - *(lpSrc - IMGW)*2 +
   *(lpSrc + IMGW + 1) - *(lpSrc - IMGW + 1);
   gradSum += (abs(vx)+abs(vy));

二、定量研究

如果直接用自然图片(比如lena),sobel计算之后可能啥都看不出来。为了定量研究,必须自己做图片。

搞成这样,看的清楚

//自己生成实验图片
    Mat matTst = Mat(Size(11,11),CV_8UC1,Scalar(0));
    line(matTst,Point(5,0),Point(5,11),Scalar(255));
    line(matTst,Point(0,5),Point(11,5),Scalar(255));

求一遍X方向的导数

//x方向求导
    Sobel(matTst,matX,CV_16SC1,1,0);

为什么要用16s?首先,所有的模式为CV_{8U,16S,16U,32S,32F,64F}C{1,2,3},其中U代表char,S代表short,F代表float,这个和c++里面一样;8只能是正数,16/32都可以是负数。所有的C1和C都是一样的,比如cv_8uc1 == cv_8u。那么原始图像是CV_8UC1,也就是CV_8U了,那么进行卷积计算,也就是原图像和

-1 0 1

-2 0 2

-1 0 1

或者类似的东西进行卷积计算,那么结果得到最大为 1*255+2*255+1*255 - 0  > 10000,最小的结果为 0 - 1*255+2*255+1*255  < -10000,所以要用CV_16SC1来保存,才合适。那么得到的结果为

?首先是有正有负,集中在中间区域值变化非常的大。取其中一个像素来看

中心位置 使用

-1 0 1

-2 0 2

-1 0 1

进行卷积,那么结果为

0*-1 + 255*0 + 0*1 +255*-2 +255*0 + 255*2 + 0 *-1 +255*0+0*1 = 0+0+0-255+0+255*2+0+0+0 = 0

其他结果也是,那么结果是符合卷积运算的。

计算Y方向卷积

//y方向求导
    Sobel(matTst,matY,CV_16SC1,0,1);

计算XY的卷积

//xy方向
    Sobel(matTst,matXY,CV_16SC1,1,1);

这个结果不是我想要的,我想要的是 (abs(vx)+abs(vy));

那么这样实现

//求和

matXY = abs(matX) + abs(matY);

甚至可以进一步帮助显示

//方便显示

normalize(matXY,matXY,0,255,NORM_MINMAX);

matXY.convertTo(matXY,CV_8UC1);

无论如何,这个结果和直接Sobel(matTst,matXY,CV_16SC1,1,1);都是不一样的

三、小结反思

应该说,Sobel大概能够做什么?这个很早之前就已经知道了。但是为什么能够达到这样的效果?这个问题,一直到我需要使用Sobel进行相关的数学计算的时候才能够搞明白。

掌握知识最后要归结到数学抽象层次,才能够算是彻底掌握。这是Sobel之外的获得。

感谢阅读至此,希望共同进步!

时间: 2024-11-08 10:22:41

使用索贝尔(Sobel)进行梯度运算时的数学意义和代码实现研究的相关文章

细心!SQL语句进行运算时使用字符串时缺失精度的细节!

昨天没有更新,特此来说明下原因,昨天回到家时已经甚晚,正逢公司这几天项目比较紧张(bug多,赶需求,看着bug单齐刷刷的转过来,心都颤抖了一下),没有及时准备素材,今天又加了一天班(现在还在公司,偷个空隙赶紧发博,哈哈哈),所以昨晚没有更博. 今天在改bug的时候发现了如图的小问题,来分享一下,主要还是要细心. 我们有一个页面,需要输入数字传入后台,然后进行相关的运算,再通过运算后的数进行查询,我们页面输入值传入后台的是String类型的,进行运算后得出一个缺失精度的值,而我们用来查询时把这个数

Double 类型运算时的精度问题

double 类型运算时的 计算的精度不高,经常会出现0.999999999999999这样的情况,那么就需要用BigDecimal   它是java提供的用来高精度计算的工具类 下面是对这个类的一个包装,方便使用: package cn.soft.util; import java.io.Serializable; import java.math.BigDecimal; import org.springframework.stereotype.Component; /** *类描述: do

java对byte,short,char,int,long运算时自动类型转化情况说明

大家都知道,在进行运算时,java会隐式的自动进行类型转化,那么有哪些情况会进行转化呢?总结如下: 一.算术运算符 单目运算符:+(取正)-(取负) ++(自增1) --(自减1) 1.1 +(取正)-(取负) 当操作数是byte,short,char时,会自动转化为int类型:返回结果为int. 当操作数是int,long时,不转化,原来是啥类型,还是啥类型. 1.2 ++(自增1) --(自减1) 不管操作数是啥类型,不转化. 双目运算符:+ - * / %(取余) 1.3 + - * /

Java中byte、short、char、int、long运算时自动类型转化问题

-------------------------------------------------------------------------------------------------- ★★自动(隐式.默认)类型转换与强制(显式)类型转换★★ 1) boolean类型不参与转换 2) 默认转换 A:从小到大 B:byte,short,char --? int --? long --? float --? double C:byte,short,char之间不相互转换,直接转成int类

关于Java中用Double型运算时精度丢失的问题

注:转自 https://blog.csdn.net/bleach_kids/article/details/49129943 在使用Java,double 进行运算时,经常出现精度丢失的问题,总是在一个正确的结果左右偏0.0000**1. 特别在实际项目中,通过一个公式校验该值是否大于0,如果大于0我们会做一件事情,小于0我们又处理其他事情. 这样的情况通过double计算出来的结果去和0比较大小,尤其是有小数点的时候,经常会因为精度丢失而导致程序处理流程出错. BigDecimal在<Eff

计算机进行小数运算时出错的原因和避免方法

计算机进行小数运算时出错的原因: 是因为有一些十进制的小数无法转换成二进制数. 如何地避免小数运算错误: 1.回避策略,即无视这些错误.根据程序目的不同,有时一些微笑的偏差并不会造成什么问题 2.就是把小数转换成整数来计算.计算机计算小数时可能会出错,但进行整数计算时一定不会出现问题.因此我们可以将小数暂时转换成整数在输出的时候再以小数的形式来输出 原文地址:https://www.cnblogs.com/hyy123-/p/10664879.html

js常用算术运算符与一元运算符在做运算时不同类型的转换规则

/** * 算术运算符:+, -, *, /, % * 当对非number类型的值进行运算(-, *, /, %)时,会将这些值先转换成number再运算,加法'+'运算除外, * 当对非number类型(string, object)的值做加法(+)运算时会将这些值先转换成string再做拼接而不是相加 * number类型的值与NaN做运算都是NaN */ // number console.log(`1 + 1 = ${1 + 1}`); // 1 + 1 = 2 console.log(

JavaSE7基础 论 在嵌套使用三目运算符时,小括号 与 代码格式 的重要意义

jdk版本  :jdk-7u72-windows-i586系统     :Windows7编辑器   :Notepad++ v7.4.2注意事项 :博文内容仅供参考,不可用于其他用途. 1代码 class Demo{ public static void main(String[] args){ int max=0; int num1=1; int num2=2; int num3=3; max=(num1 > num2) ?((num1>num3)? num1 : num3) :((num2

【安卓】数据库基于脚本的&quot;增量更新&quot;,每次更新时不需修改java代码、!

思路: 1.当然是基于SQLiteOpenHelper.onCreate(第一次安装程序时调用).onUpdate(升级程序时调用) 2.用"脚本"(脚本制作具体方法问度娘)做数据库升级,文件名标识对应版本,java中根据"上一版本.当前版本"选择执行的脚本. 升级时,修改DB_VERSION(当前版本)即可. DBManager.java: package com.example.test; import java.io.ByteArrayOutputStream