Marr-Hildreth边缘检测器C++实现

1 Marr-Hildreth边缘检测器

1.1  Marr-Hildreth边缘检测器的原理

Marr-Hiddreth是基于以下两个事实的:

1 灰度变换与图像尺寸无关,因此边缘检测可以使用不同的尺寸算子

2 灰度的突然变换会在一阶导数中导致波峰或波谷,或在二阶导数中等效的引起零交叉

 

用于边缘检测的算子应该有两个明显的特点:

1 它应该是一个能计算图像中每一点处的一阶导数或二阶导数的数字近似的微分算子

2 它应该能被调整以便在任何期望的尺寸上起作用,因此大算子也可用于检测模糊边缘,小算子可用于检测锐度集中的精细细节

 

满足这些条件的最令人满意的算子是高斯拉普拉斯算子(LoG)

                    (1)

            (2)

                                                                             图1

图1(a)到(c)显示了一个LoG的负函数的三维图,图像和剖面,LoG的零交叉出现在x2+y2=处,图(d)显示的是5*5的模板,它近似于(a)的形状,该近似不唯一

任意尺寸的模板可以通过式(2)取样并标定系数以使系数之和为0来生成,生成LoG滤波器的的一种更有效的方式是以n*n对式(1)进行取样,然后将结果阵列与一个拉普拉斯模板进行卷积.

Marr-Hildreth算法是由LoG滤波器与一副图像的卷积组成

                 (3)

1.2 Marr-Hildreth边缘检测器的实现步骤

1 对式(1)进行取样得到n*n的高斯低通滤波器对输入图像滤波

2 计算第一步得到的图像的拉普拉斯

3 找到步骤二所得的图像的零交叉

1.3 对高斯函数进行取样的细节

二维高斯函数的公式:

                  (4)

       首先我们确定和高斯核矩阵的大小,一般我们取高斯核矩阵的大小为6+1, 比如,=4,size=25;

       之后我们对size*size的矩阵进行循环,对应的左边i,j作为x,y进行计算,不过要注意的是矩阵的中心位置是对应的x,y对应的是(0,0),所以我们需要适当的变换

       再计算出矩阵所有位置对应的f(x,y)之后,因为高斯值均为小数,为了规范,我们将其转换为整数,将(0,0)为值的值设为1,其他位置看与(0,0)位置的比值进行设置,转化为整数后,再对核矩阵进行均值化

//构造高斯核矩阵
void GaussianKernel(cv::Mat &ss,int size,double delta){
    int centerX=size/2;
    int centerY=size/2;
    int x=0;
    int y=0;
    cv::Mat gaussiankernel;
    gaussiankernel=cv::Mat_<double>(size,size);
    double cc=0.5/(3.1415926*delta*delta);
    for(int i=0;i<size;i++){
        for(int j=0;j<size;j++){
            x=std::abs(i-centerX);
            y=std::abs(j-centerY);
            double dd=cc*exp(-((pow(x,2)+pow(y,2))/(2*delta*delta)));
            gaussiankernel.at<double>(i,j)=dd;
        }
    }

    cv::Mat ss1;
    ss1=cv::Mat_<int>(size,size);
    int sum=0;
    //将高斯变换的小数类型转为整型
    double ff=gaussiankernel.at<double>(0,0);
    for(int i=0;i<size;i++){
        for(int j=0;j<size;j++){
            int cc=cvRound(gaussiankernel.at<double>(i,j)/ff);
            ss1.at<int>(i,j)=cc;
            sum+=cc;
        }
    }

    //均值化
    for(int i=0;i<size;i++){
        for(int j=0;j<size;j++){
            int cc=ss1.at<int>(i,j);
            ss.at<float>(i,j)=(float)(cc)/sum;
        }
    }
}

1.4 寻找零交叉的细节

       在滤波后的图像g(x,y)的任意像素p处,寻找零交叉的一种方法是:使用以p为中心的一个3*3邻域,p点处的零交叉意味着至少有两个相对的邻域像素符号不同,有四种测试的情况:左/右,上/下和两个对角,设置一个阈值,不仅相对邻域的符号不同,而且他们的差值的绝对值还必须超过这个阈值.

void linyu(cv::Mat &src,cv::Mat &result,double threshold){
    for (int y = 1; y < src.rows - 1; ++y)
    {
        for (int x = 1; x < src.cols; ++x)
        {
            // 邻域判定
            if ((src.at<float>(y - 1, x) *
                 src.at<float>(y + 1, x) < 0)&&(std::abs(src.at<float>(y - 1, x)-
                                                          src.at<float>(y + 1, x))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
            if ((src.at<float>(y, x - 1) *
                src.at<float>(y, x + 1) < 0)&&(std::abs(src.at<float>(y , x-1)-
                                                          src.at<float>(y , x+1))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
            if ((src.at<float>(y + 1, x - 1) *
                 src.at<float>(y - 1, x + 1) < 0)&&(std::abs(src.at<float>(y + 1, x-1)-
                                                              src.at<float>(y - 1, x+1))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
            if ((src.at<float>(y - 1, x - 1) *
                 src.at<float>(y + 1, x + 1) < 0)&&(std::abs(src.at<float>(y - 1, x-1)-
                                                              src.at<float>(y + 1, x+1))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
        }
    }
}

1.5 opencv实现步骤

1 将图片进行灰度化

2 将图片转换到像素范围[0,1]

3 生成高斯核矩阵并对图像进行平滑

4 对平滑后的图像进行拉普拉斯变换

5 进行零交叉查找

6 得到结果

1.6 完整代码

#include <iostream>
#include<opencv2/opencv.hpp>
using namespace cv;

//将图像的动态范围转到[0,1]
void ImageAdjust(cv::Mat &src,cv::Mat &dst){
    for(int i=0;i<src.rows;i++){
        for(int j=0;j<src.cols;j++){
            int g=src.at<uchar>(i,j);
            dst.at<float>(i,j)=g/255.;
        }
    }
}

//构造高斯核矩阵
void GaussianKernel(cv::Mat &ss,int size,double delta){
    int centerX=size/2;
    int centerY=size/2;
    int x=0;
    int y=0;
    cv::Mat gaussiankernel;
    gaussiankernel=cv::Mat_<double>(size,size);
    double cc=0.5/(3.1415926*delta*delta);
    for(int i=0;i<size;i++){
        for(int j=0;j<size;j++){
            x=std::abs(i-centerX);
            y=std::abs(j-centerY);
            double dd=cc*exp(-((pow(x,2)+pow(y,2))/(2*delta*delta)));
            gaussiankernel.at<double>(i,j)=dd;
        }
    }

    cv::Mat ss1;
    ss1=cv::Mat_<int>(size,size);
    int sum=0;
    //将高斯变换的小数类型转为整型
    double ff=gaussiankernel.at<double>(0,0);
    for(int i=0;i<size;i++){
        for(int j=0;j<size;j++){
            int cc=cvRound(gaussiankernel.at<double>(i,j)/ff);
            ss1.at<int>(i,j)=cc;
            sum+=cc;
        }
    }

    for(int i=0;i<size;i++){
        for(int j=0;j<size;j++){
            int cc=ss1.at<int>(i,j);
            ss.at<float>(i,j)=(float)(cc)/sum;
        }
    }
}

//拉普拉斯变换
void Laplacian(cv::Mat &src, cv::Mat &dst)
{
    cv::Mat element;
    element=(cv::Mat_<int>(3,3)<<-1,-1,-1,-1,8,-1,-1,-1,-1);
    for (int y = 1; y < src.rows-1; ++y) {
        for (int x =1; x < src.cols-1; ++x) {
            double value=0;
            int i=0;
            for (int m =y-1; m <= y+1; ++m) {
                for (int n = x-1; n <= x+1; ++n) {
                    int m1=i/3;
                    int n1=i%3;
                    int t=element.at<int>(m1,n1);
                    i++;
                    float b=src.at<float>(m,n);
                    value +=t*b;
                }
            }

            dst.at<float>(y,x)=value;
        }
    }
}

void linyu(cv::Mat &src,cv::Mat &result,double threshold){
    for (int y = 1; y < src.rows - 1; ++y)
    {
        for (int x = 1; x < src.cols; ++x)
        {
            // 邻域判定
            if ((src.at<float>(y - 1, x) *
                 src.at<float>(y + 1, x) < 0)&&(std::abs(src.at<float>(y - 1, x)-
                                                          src.at<float>(y + 1, x))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
            if ((src.at<float>(y, x - 1) *
                src.at<float>(y, x + 1) < 0)&&(std::abs(src.at<float>(y , x-1)-
                                                          src.at<float>(y , x+1))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
            if ((src.at<float>(y + 1, x - 1) *
                 src.at<float>(y - 1, x + 1) < 0)&&(std::abs(src.at<float>(y + 1, x-1)-
                                                              src.at<float>(y - 1, x+1))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
            if ((src.at<float>(y - 1, x - 1) *
                 src.at<float>(y + 1, x + 1) < 0)&&(std::abs(src.at<float>(y - 1, x-1)-
                                                              src.at<float>(y + 1, x+1))>threshold))
            {
                result.at<uchar>(y, x) = 255;
            }
        }
    }
}

int main() {
    cv::Mat src=cv::imread("../3.png",1);

    //图像转为灰度图
    cv::Mat gray(src.size(),CV_32FC1);
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::imshow("gray",gray);
    cv::imwrite("../gray.png",gray);

    //将图像的灰度范围转到[0.1]
    cv::Mat gray01;
    gray01=cv::Mat::zeros(gray.size(),CV_32FC1);
    ImageAdjust(gray,gray01);
    cv::imshow("gray01",gray01);

    //生成高斯核矩阵并对图像进行平滑
    int size=13;//高斯核矩阵的大小
    double delta=2;
    cv::Mat gaussiankernel;
    cv::Mat gaussianblur;
    gaussiankernel=cv::Mat_<float>(size,size);//(cv::Size(size,size),CV_64FC1);
    GaussianKernel(gaussiankernel,size,delta);
    cv::filter2D(gray01,gaussianblur,-1,gaussiankernel);

    //对进行平滑之后的图像进行拉普拉斯变换
    cv::Mat Laplace;
    Laplace=cv::Mat::zeros(gaussianblur.size(),CV_32FC1);
    Laplacian(gaussianblur,Laplace);
    cv::imshow("Laplace",Laplace);

    //找到最大值最小值,为设置阈值做参考
    double max=0;
    double min=0;
    cv::minMaxLoc(Laplace,&min,&max);

    //邻域判定,零交叉查找
    cv::Mat result;
    result=cv::Mat::zeros(Laplace.size(),CV_8U);
    linyu(Laplace,result,0.02);
    cv::imshow("result",result);
    cv::imwrite("../result.png",result);

    cv::waitKey(0);
    return 0;
}

1.7 实现效果

                                               原图

                                            灰度图像

                                   拉普拉斯变换图像

                                              result

猜你喜欢

转载自blog.csdn.net/u013263891/article/details/83864948
今日推荐