图像处理算法和算子的区别

2019-08-31 18:03:23 simonyucsdy 阅读数 1067

Harris算子的一个缺点是不具有尺度不变性,所以引入了另一种检测算子:高斯拉普拉斯算子

LoG算子

       前面提到过高斯一阶导,其作用是检测边缘(边缘区域高斯一阶导数取得极值),那么对一阶导数继续求导得到二阶导数,可想而知,一阶导数的极值点就是二阶导数的0点。进而可以通过寻找二阶导数的0点(亦或是接近于0的点),去寻找图片的梯度(边缘)。

        拉普拉斯算子的表示式:

或者:

                                                                   

LoG曲线

对高斯函数进行二阶求导

                         

曲线图

尺度归一化

在LoG前面加变换率,就变成了LoG归一化

                           

非极大值抑制

LoG检测完一块区域后,为避免后续出现重复检测,一般采用非极大值抑制方式:非极大值点置为0。

LoG的处理流程

                                      

斑点检测

        LoG极值的点,就是要找的斑点。

        利用LoG进行滤波,得到的边缘检测图:

                           

                           

代码

(略)

 

2018-09-21 09:00:13 zqx951102 阅读数 1731

OpenCV-跟我一起学数字图像处理之拉普拉斯算子

Laplace算子和Sobel算子一样,属于空间锐化滤波操作。起本质与前面的Spatial Filter操作大同小异,下面就通过Laplace算子来介绍一下空间锐化滤波,并对OpenCV中提供的Laplacian函数进行一些说明。

  • 数学原理

离散函数导数

离散函数的导数退化成了差分,一维一阶差分公式和二阶差分公式分别为,

CodeCogsEqn

CodeCogsEqn(2)

Laplace算子的差分形式

分别对Laplace算子x,y两个方向的二阶导数进行差分就得到了离散函数的Laplace算子。

在一个二维函数f(x,y)中,x,y两个方向的二阶差分分别为,

CodeCogsEqn(3)

CodeCogsEqn(4)

所以Laplace算子的差分形式为,

CodeCogsEqn(5)

写成filter mask的形式如下,

0 1 0
1 -4 1
0 1 0
注意该mask的特点,mask在上下左右四个90度的方向上结果相同,也就是说在90度方向上无方向性。为了让该mask在45度的方向上也具有该性质,对该filter mask进行扩展定义为,
1 1 1
1 -8 1
1 1 1
 

注:

有时我们也会见到不同于上述结果的Laplace算子的filter mask,

0 -1 0
-1 4 -1
0 -1 0
 
-1 -1 -1
-1 8 -1
-1 -1 -1

其原因是在定义二阶导数的时候采用了相反的定义,这个无关紧要,但是要注意,当用Laplace算子滤波后的图像与原图叠加时,混合操作是加还是减因上述的定义而异。

图像的Laplace操作

如同本文开始时说的那样,将Laplace算子写成filter mask后,其操作大同小异于其他的空间滤波操作。将filter mask在原图上逐行移动,然后mask中数值与其重合的像素相乘后求和,赋给与mask中心重合的像素,对图像的第一,和最后的行和列无法做上述操作的像素赋值零,就得到了拉普拉斯操作结果。

拉普拉斯操作结果与原图的混合

因为Laplace算子是二阶导数操作,其在强调图像素中灰度不连续的部分的同时也不在强调灰度值连续的部分。这样会产生一个具有很明显的灰度边界,但是没有足够特征的黑色背景。背景特征可以通过原图像与Laplace算子操作后的图像混合恢复。用公式,

CodeCogsEqn(6)

其中的参数c的取值和上面的两种mask定义有关,当mask中心的数值取正时c=-1,相反c=1;

  • 基于OpenCV的Laplace算子的计算

OpenCV中Laplacian函数可以实现对图像的Laplace操作,具体用法如下,

Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );

参数意义为,

  1. src_gray,输入图像
  2. dst,Laplace操作结果
  3. ddepth,输出图像深度,因为输入图像一般为CV_8U,为了避免数据溢出,输出图像深度应该设置为CV_16S
  4. kernel_size,filter mask的规模,我们的mask时3x3的,所以这里应该设置为3
  5. scale,delta,BORDER_DEFAULT,默认设置就好

基于OpenCV的Laplace算子仿真代码段如下,

//load the Original Image and get some informations
Mat src = imread("012.jpg",0);
namedWindow("OriginalImage");
imshow("OriginalImage",src);
CV_Assert(src.depth() == CV_8U);

//OpenCV solution - Laplacian
Mat dst,abs_dst_laplace;
Laplacian(src,dst,CV_16S,
3);
convertScaleAbs(dst,abs_dst_laplace);

//show the result
namedWindow(result_laplacian);
imshow(
result_laplacian,abs_dst_laplace);

其中convertScaleAbs函数功能是将CV_16S型的输出图像转变成CV_8U型的图像。

仿真结果:

原图:

original

Laplace操作结果:

abs_dst_laplae

  • 基于mask operation原理仿真

Laplace算子滤波仿真

根据数学原理中介绍的算法,编写相应代码,进行相关仿真。其中对Laplace操作结果进行了图像拉伸显示,因为Laplace操作结果的像素值范围可能落在了[0,255]之外,而计算机在显示的时候将赋值全部置为0,大于255的像素全部显示成255。

代码段如下,

//get some informations of original image
int nr = src.rows;
int nc = src.cols;
int n = nr*nc;
int arr[9] = {0};

//scan the whole pixels of original image
//and do Laplacian Operation
int* table_lap = new int[n];
int* table_orig = new int[n];
int l;
for (int i=0;i<n;i++)
{
table_lap[i]
= 0;
table_orig[i]
= 0;
}
for (int i=1;i<nr-1;i++)
{
const uchar* previous = src.ptr<uchar>(i-1);
const uchar* current = src.ptr<uchar>(i);
const uchar* next = src.ptr<uchar>(i+1);
for (int j=1;j<nc-1;j++)
{
for (int k=0;k<3;k++)
{
arr[k]
= previous[j+k-1];
arr[k
+3] = current[j+k-1];
arr[k
+6] = next[j+k-1];
}
l
= nc*i+j; //calculate the location in the table of current pixel
Lmaskoperation(table_lap,arr,l);
table_orig[l]
= arr[4];
}
}

//pixels scale
uchar* La_scaled = new uchar[n];
table_scale(table_lap,La_scaled,n);

//padding values
Mat LaResult_own;
LaResult_own.create(src.size(),src.type());
uchar
* p = NULL;
for (int i=0;i<nr;i++)
{
p
= LaResult_own.ptr<uchar>(i);
for (int j=0;j<nc;j++)
{
l
= nc*i+j;
p[j]
= La_scaled[l];
}
}

//show results
namedWindow(LaResult_own);
imshow(
LaResult_own,LaResult_own);

其中Lmaskoperation是我写的mask为Laplace mask的mask operation操作函数,函数段如下,

//**********************//
//Laplacian mask operation
//**********************//
void Lmaskoperation(int* table,int* arr,int l)
{
    int tmp[9] = {-1,-1,-1,-1,8,-1,-1,-1,-1};
    for (int i=0;i<9;i++)
    {
        table[l] = table[l] + tmp[i]*arr[i];
    }
}

tabel_scale函数就是我写的图像拉伸函数,将Laplace操作结果拉伸到[0,255],具体函数段如下,

//*****************************//
//scale the pixels to [0 255]
//*****************************//
void table_scale(int* table,uchar* result,int n)
{
    int min = table[0];
    int max = table[0];
    for (int i=0;i<n;i++)
    {
        if(min>table[i])
        {
            min = table[i];
        }
        if(max<table[i])
        {
            max = table[i];
        }
    }
    for (int i=0;i<n;i++)
    {
        result[i] = (uchar)(255*(table[i]-min)/(max-min));
    }
}

仿真结果,拉伸后Laplace算子的操作结果

LaResult_own

以灰色为主色调的显示结果就是Laplace算子操作拉伸后显示的一大特点。

Laplace滤波图像与原图像的混合

我使用的mask中心值为正,所以混合操作需要原图减去Laplace滤波图像,代码段如下,

//blending with the original image using Eq g(x,y)=f(x,y)+c*Lap(x,y)
int* table_blend = new int[n];
for(int i=0;i<n;i++)
{
    table_blend[i] = table_orig[i] - table_lap[i];
    if(table_blend[i]<0)
    {
        table_blend[i] = 0;
    }
    else if (table_blend[i]>255)
    {
        table_blend[i] = 255;
    }
}

//padding values to blending result
Mat Blresult;
Blresult.create(src.size(),src.type());
for (int i=0;i<nr;i++)
{
p
= Blresult.ptr<uchar>(i);
for(int j=0;j<nc;j++)
{
l
= nc*i+j;
p[j]
= table_blend[l];
}
}

//show blending result
namedWindow(blending result_laplacian);
imshow(
blending result_laplacian,Blresult);

仿真结果:

blending result_laplacian

最后给出冈萨雷斯在介绍Laplacian时所给素材的仿真结果

blending result_laplacian

2017-09-30 08:57:04 GoodShot 阅读数 9027
数学中的映射,当映射的作用是把函数映成函数,或者函数映成数的时候,这个映射常常叫做算子.
比如微分算子D,把就是把函数f作用后,把f映成f的导函数.

拉普拉斯算子是一种二阶微分算子.



图像处理里都把图像看成R^2上的函数,每个像素只是这个函数的采样点。
在这个意义下,算子就是把一个R^2上的函数变化到另一个R^2上的函数的一个变换。

(表面上看,原图每个点到另一幅图的的映射,是通过矩阵(图像处理时也称为掩膜)用来与原图像做运算而实现的,算子这个表现的矩阵(映射)


2018-04-26 22:56:53 qq_31804159 阅读数 11199

我的程序效果:

 

边缘检测算法是图像处理中最为基本的问题。其目的是标志图像出亮度变化明显的点,从而反映出图像中重要变化。

 

先介绍一下Sobel算子

Sobel 算子是像素图像边缘检测中最重要的算子之一,该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如下图,Gx和Gy分别是在横向及纵向的灰度偏导的近似值。(:对于一个彩色图要先把它转换为灰度图)

关于卷积可以看一下这个博客:https://blog.csdn.net/chaipp0607/article/details/72236892?locationNum=9&fps=1

 

对于每一个点我们可以获得两个方向的梯度,我们可以通过下面这个公式算出梯度的估计值。

我们定义一个阈值Gmax(这里定义Gmax = 150),如果G比Gmax大可以认为该点是一个边界值.则设置这个点为白色否则该店为黑色。这样我们就得到了通过边缘检测的图像。

 

代码实现:

1.彩色图转为灰度图


QImage LeadToGrey(const QImage &source)
{

    int w = source.width();
    int h = source.height();
    QImage gray(w,h,QImage::Format_RGB32);

    for( int i = 0; i< h; i++){

        for(int j = 0; j < w; j++){
            QRgb pixel = source.pixel(j,i);

            int grey = qGray(pixel);
            QRgb graypixel = qRgb(grey,grey,grey);
            gray.setPixel(j,i,graypixel);

        }
    }
    return gray;
}


2.边缘检测代码:


Image LeadToEdge(QImage source)
{
    int w = source.width();
    int h = source.height();

    QImage Edge(w,h,QImage::Format_RGB32);

    for( int i = 0; i< h; i++){
   //卷积操作
        for(int j = 0; j < w; j++){
            double Gx =  (-1)* QColor(source.pixel(getIndex(j-1,w),getIndex(i-1,h))).red()
                        +(-2)*QColor(source.pixel(getIndex(j,w),getIndex(i-1,h))).red()
                        +(-1)*QColor(source.pixel(getIndex(j+1,w),getIndex(i-1,h))).red()
                        +QColor(source.pixel(getIndex(j-1,w),getIndex(i+1,h))).red()
                        +2*QColor(source.pixel(getIndex(j,w),getIndex(i+1,h))).red()
                        +QColor(source.pixel(getIndex(j+1,w),getIndex(i+1,h))).red();

            double Gy =  QColor(source.pixel(getIndex(j-1,w),getIndex(i-1,h))).red()
                    +(2)*QColor(source.pixel(getIndex(j-1,w),getIndex(i,h))).red()
                    +(1)*QColor(source.pixel(getIndex(j-1,w),getIndex(i+1,h))).red()
                    +(-1)*QColor(source.pixel(getIndex(j+1,w),getIndex(i-1,h))).red()
                    +(-2)*QColor(source.pixel(getIndex(j+1,w),getIndex(i,h))).red()
                    +(-1)*QColor(source.pixel(getIndex(j+1,w),getIndex(i+1,h))).red();

           double G = sqrt(Gx*Gx+Gy*Gy);

            QRgb pixel;
            if(G>Gmax)
              pixel = qRgb(255,255,255);
            else
              pixel = qRgb(0,0,0);
            Edge.setPixel(j,i,pixel);
        }
    }
    return Edge;
}

 

2018-01-28 23:47:45 fengye2two 阅读数 40774

首先感谢以下两位的渊博知识:

(1)爱鱼         https://www.cnblogs.com/mightycode/p/6394810.html

(2)mitutao   https://www.cnblogs.com/love6tao/p/5152020.html

图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波。我们知道微分运算是求信号的变化率,具有加强高频分量的作用。在空域运算中来说,对图像的锐化就是计算微分。由于数字图像的离散信号,微分运算就变成计算差分或梯度。图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Robert算子(交叉差分),Sobel算子等等,是基于寻找梯度强度。拉普拉斯算子(二阶差分)是基于过零点检测。通过计算梯度,设置阈值,得到边缘图像。

何为边缘?图象局部区域亮度变化显著的部分,对于灰度图像来说,也就是灰度值有一个明显变化,既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值。在提高对景物边缘的敏感性的同时,可以抑制噪声的方法才是好的边缘提取方法。

Canny算子求边缘点具体算法步骤如下:

1. 用高斯滤波器平滑图像.

2. 用一阶偏导有限差分计算梯度幅值和方向.

3. 对梯度幅值进行非极大值抑制.

4. 用双阈值算法检测和连接边缘.


第一步:灰度化 

把彩色图像变成灰度图像,该部分是按照Canny算法通常处理的图像为灰度图,如果获取的彩色图像,那首先就得进行灰度化。以RGB格式的彩图为例,通常灰度化采用的公式是:

Gray=0.299R+0.587G+0.114B;

第二步:高斯滤波 

对图像高斯滤波,图像高斯滤波的实现可以用两个一维高斯核分别两次加权实现,也就是先一维X方向卷积,得到的结果再一维Y方向卷积。当然也可以直接通过一个二维高斯核一次卷积实现。也就是二维卷积模板,由于水平有限,只说二维卷积模板怎么算。

       首先,一维高斯函数:

                          

          二维高斯函数:

                                

         模板中每一个点的高斯系数可以由上面的公式计算,这样得到的是不是最终的模板呢?答案不是,需要归一化,也即是每一个点的系数要除以所有系数之和,这样才是最终的二维高斯模板。

      这个里面有个小知识点,要想计算上面的系数,需要知道高斯函数的标准差σ (sigma),还需要知道选3*3还是5*5的模板,也就是模板要多大,实际应用的时候,这两者是有关系的,根据数理统计的知识,高斯分布的特点就是数值分布在(μ—3σ,μ+3σ)中的概率为0.9974,也就是模板的大小其实就是6σ这么大就OK了,但是6σ可能不是奇数,因为我们一定要保证有核心。所以模板窗口的大小一般采用1+2*ceil(3*nSigma) ceil是向上取整函数,例如ceil(0.6)=1。

       计算得到模板,那就是直接卷积就OK,卷积的意思就是图像中的点附近的模板大小区域乘以高斯模板区域,得到的结果就是该点卷积后的结果。卷积的核心意义就是获取原始图像中像模板特征的性质。

第三步:计算梯度值和方向 

图像的边缘可以指向不同方向,因此经典Canny算法用了四个梯度算子来分别计算水平,垂直和对角线方向的梯度。但是通常都不用四个梯度算子来分别计算四个方向。常用的边缘差分算子(如Rober,Prewitt,Sobel)计算水平和垂直方向的差分Gx和Gy。这样就可以如下计算梯度模和方向:

梯度角度θ范围从弧度-π到π,然后把它近似到四个方向,分别代表水平,垂直和两个对角线方向(0°,45°,90°,135°)。可以以±iπ/8(i=1,3,5,7)分割,落在每个区域的梯度角给一个特定值,代表四个方向之一。

这里选择Sobel算子计算梯度,相对于其他边缘算子,Sobel算子得出来的边缘粗大明亮。

 

 

 

下图是对上面半径2的高斯模糊图像L通道(HSL)应用Sobel算子的梯度模图,没有施加任何值。

           Sobel算子,无

第四步:非极大值抑制

非极大值抑制是进行边缘检测的一个重要步骤,通俗意义上是指寻找像素点局部最大值。沿着梯度方向,比较它前面和后面的梯度值进行了。见下图。

     

   上图中左右图:g1、g2、g3、g4都代表像素点,很明显它们是c的八领域中的4个,左图中c点是我们需要判断的点,蓝色的直线是它的梯度方向,也就是说c要是局部极大值,它的梯度幅值M需要大于直线与g1g2和g2g3的交点,dtmp1和dtmp2处的梯度幅值。但是dtmp1和dtmp2不是整像素,而是亚像素,也就是坐标是浮点的,那怎么求它们的梯度幅值呢?线性插值,例如dtmp1在g1、g2之间,g1、g2的幅值都知道,我们只要知道dtmp1在g1、g2之间的比例,就能得到它的梯度幅值,而比例是可以靠夹角计算出来的,夹角又是梯度的方向。

    写个线性插值的公式:设g1的幅值M(g1),g2的幅值M(g2),则dtmp1可以很得到:

        M(dtmp1)=w*M(g2)+(1-w)*M(g1)  

       其中w=distance(dtmp1,g2)/distance(g1,g2)      

    distance(g1,g2) 表示两点之间的距离。实际上w是一个比例系数,这个比例系数可以通过梯度方向(幅角的正切和余切)得到。

右边图中的4个直线就是4个不同的情况,情况不同,g1、g2、g3、g4代表c的八领域中的4个坐标会有所差异,但是线性插值的原理都是一致的。

下图是非最大值抑制的结果。可见边缘宽度已经大大减小。但是这个图像中因为没有应用任何值,还含有大量小梯度模值的点,也就是图中很暗的地方。下面,值要上场了。

             非最大值抑制结果


第五步:双阈值的选取

一般的边缘检测算法用一个值来滤除噪声或颜色变化引起的小的梯度值,而保留大的梯度值。Canny算法应用双值,即一个高值和一个低值来区分边缘像素。如果边缘像素点梯度值大于高值,则被认为是强边缘点。如果边缘梯度值小于高值,大于低值,则标记为弱边缘点。小于低值的点则被抑制掉。

第六步:滞后边界跟踪

强边缘点可以认为是真的边缘。弱边缘点则可能是真的边缘,也可能是噪声或颜色变化引起的。为得到精确的结果,后者引起的弱边缘点应该去掉。通常认为真实边缘引起的弱边缘点和强边缘点是连通的,而又噪声引起的弱边缘点则不会。所谓的滞后边界跟踪算法检查一个弱边缘点的8连通领域像素,只要有强边缘点存在,那么这个弱边缘点被认为是真是边缘保留下来。

这个算法搜索所有连通的弱边缘,如果一条连通的弱边缘的任何一个点和强边缘点连通,则保留这条弱边缘,否则抑制这条弱边缘。搜索时可以用广度优先或者深度优先算法,我在这里实现了应该是最容易的深度优先算法。一次连通一条边缘的深度优先算法如下:

  1. 准备一个栈s,一个队列q,设联通指示变量connected为假。从图像的第一个点开始,进入2。
  2. 如果这个点是弱边界点并且没有被标记,把它标记,并把它作为第一个元素放入栈s中,同时把它放入记录连通曲线的队列q,进入3。如果这个点不是弱边界或者已经被标记过,到图像的下一个点,重复2。
  3. 从栈s中取出一个元素,查找它的8像素领域。如果一个领域像素是弱边界并且没有被标记过,把这个领域像素标记,并加入栈s中,同时加入队列q。同时查找领域对应的强边界图,如果有一个像素是强边界,表示这条弱边界曲线和强边界联通,设置connected为真。重复3直到栈中没有元素了。如果connected为假,则依次从队列q中取出每个元素,清空标记。如果connected为真,保留标记。
  4. 清空队列q,设置connected为假,移动到图像的下一个点,到2。
第七步:结果查看

下面是对Lena图计算Canny边缘检测的梯度模图和二值化图,高斯半径2,高值100,低值50。

   

             Canny检测梯度模图                        Canny检测梯度二值图

作为对比,下面是用一阶差分和Sobel算子对原图计算的结果,阀值100。由于一阶差分的梯度值相对较小,我对一阶差分的梯度值放大了一定倍数,使得它和Sobel的梯度值保持同样的水平。

   

               一阶差分梯度模图                        一阶差分梯度二值图

   

                 Sobel梯度模图                          Sobel梯度二值图

很明显,Canny边缘检测的效果是很显著的。相比普通的梯度算法大大抑制了噪声引起的伪边缘,而且是边缘细化,易于后续处理。对于对比度较低的图像,通过调节参数,Canny算法也能有很好的效果。

原图   

Canny边缘梯度模,高斯半径2,低阀值30,高阀值100

   Canny边缘梯度二值化图,高斯半径2,低阀值30,高阀值100

从上往下依次为(a)(b)  (c)

(a)原图                       

(b)Canny梯度模,高斯半径2,低值30,高值100       

(c)Canny梯度二值化图,高斯半径2,低值30,高值100

原图

   Canny边缘梯度模,高斯半径1,低阀值40,高阀值80

   Canny边缘梯度二值化图,高斯半径1,低阀值40,高阀值80

从上往下依次为(d)(e)  (f)

(d)原图                       

(e)Canny梯度模,高斯半径1,低值40,高值80       

(f)Canny梯度二值化图,高斯半径1,低值40,高值80