专栏首页音视频技术学习笔记图像处理基础-图像边缘检测

图像处理基础-图像边缘检测

一、插曲

图像处理经常会用到这张赫赫有名的图片,这位lena女士的照片原本用在“花花公子”的杂志封面上,机缘巧合被当做测试素材,广泛用在图形处理领域。

原图是一张半裸的全身照,截取了头像部分,有兴趣可以去网上找找

参考:Lena.jpg

IEEE图像处理汇刊的主编David C.Munson总结了两点原因: 1.该图适度的混合了细节、平滑区域、阴影和纹理,从而能很好的测试各种图像处理算法。 2.Lenna是个美女,对于图象处理界的研究者来说,美女图可以有效的吸引他们来做研究

图像边缘检测的算法有很多,包括传统的模板算⼦(Sobel、Roberts、Prewitt、Laplace)、形态学边缘检测、经典的 Canny 边缘检测及基于深度学习的边缘检测算法等。这篇文章讲两个有代表性的算子:sobel边缘检测和canny边缘检测

二、sobel边缘检测

2.1算法原理

基于梯度是最基本的边缘检测算法,存在较大误差和不稳定性。

Sobel 模板算⼦是 Irwin Sobel 在 1968 年发表的论⽂ An Isotropic 3×3 Image Gradient Operator中提出的⼀种⼀阶导数模板算⼦,⽤来计算图像灰度函数的近似梯度。

其中,Gx表⽰⽔平⽅向的卷积模板,Gy表⽰垂直⽅向的卷积模板

2.2 实现效果:

不同阈值

实现代码见文末

三、canny边缘检测

Canny边缘检测是 John Canny在 1986年⾸次提出的⼀种改进的边缘检测⽅法。该⽅法主要通过图像信号函数的极⼤值来判断图像的边缘像素点,与基本的 Sobel 模板算⼦等相⽐,其具有低错误率、⾼定位性等优点,因⽽被⼴泛应⽤。 算法实现步骤:

1. 降噪-高斯滤波平滑处理

2. 梯度计算

使⽤⼀阶导数算⼦(⼀般⽤ Sobel 模板算⼦)计算灰度图像中每个像素点在⽔平和垂直⽅向上的导数GX、GY,得出梯度向量(GX,GY),最后得到该像素点的梯度幅度G和相位⾓D 相位角后面用来根据梯度方向取临近点

3.非极大值抑制

将当前像素的梯度值与其在梯度⽅向上的邻域像素的梯度值做对⽐,如果当前像素的梯度值为最⼤值,则保留该点的梯度信息,否则将该点删除或将像素值置为0

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

设置(min, max)范围,抑高于max视为边缘,低于min过滤掉。居于中间,进入第4步过滤,取临近点对比

实现效果:

范围(5, 50)

完整代码见文末

完整代码

sobel边缘检测

#include"f_Sobel.h"
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<stdio.h>
#include"Commen.h"

int f_Sobel(unsigned char *srcData, int width, int height,int stride, int threshold)
{
    int ret = 0;
    unsigned char *dstData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
    memset(dstData, 255, sizeof(unsigned char) * height * stride);
    int x, y, i, k, pos;
    int hValue, vValue, value;
    unsigned char *pSrcL0;
    unsigned char *pSrcL1;
    unsigned char *pSrcL2;
    unsigned char *pDstL;
    unsigned char SqrtValue[65026];
    pSrcL0 = srcData;
    pSrcL1 = srcData + stride;
    pSrcL2 = srcData + stride * 2;
    pDstL  = dstData + stride;
    for (i = 0; i < 65026; i++)
    {
      SqrtValue[i] = (unsigned char)(sqrt((float)i) < threshold ? 0 : 255);
         // ps中这一行改成
         // SqrtValue[i] = (unsigned char)(255 - (int)sqrt((float)i));
        // 如果颜色相近sqrt算出来接近0,255-0得255,接近白色
        // sqrt算出来很大,255 - 255接近0, 显示成黑色,识别为边缘
        // 猜测人眼对白底黑字比较敏感,所以用255 - ?取反计算显示
    } 
    for (y = 1; y < height - 1; y++)
    {        
        for (x = 1; x < width - 1; x++)
        {
            pos = x * 4;
            hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
            vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
            k = hValue * hValue + vValue * vValue;
            k = MIN2(k, 65025);
            pDstL[pos] = SqrtValue[k];
            pos++;
            hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
            vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
            k = hValue * hValue + vValue * vValue;
            k = MIN2(k, 65025);
            pDstL[pos] = SqrtValue[k];
            pos++;
            hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
            vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
            k = hValue * hValue + vValue * vValue;
            k = MIN2(k, 65025);
            pDstL[pos] = SqrtValue[k];
        }
        pSrcL0 += stride;
        pSrcL1 += stride;
        pSrcL2 += stride;
        pDstL  += stride;
    }
    memcpy(srcData, dstData, sizeof(unsigned char) * height * stride);
    free(dstData);
    return ret;
}

canny边缘检测

#include"f_Canny.h"
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<stdio.h>
#include"Commen.h"


//单通道灰度化
static int f_GrayOneChannel(unsigned char* srcData, unsigned char* grayData, int width, int height, int stride)
{
    int ret = 0;
    int i, j, gray, offset;
    offset = stride - (width * 4);
    unsigned char* pSrc = srcData;
    unsigned char* pGray = grayData;
    for(j = 0; j < height; j++)
    {
        for(i = 0; i < width; i++)
        {
            gray = (pSrc[2] + pSrc[1] + pSrc[0]) / 3;
            *pGray = gray;
            pSrc += 4;
            pGray ++;
        }
        pSrc += offset;
    }
    return ret;
};
//梯度相位角获取  
static void GetGradientDegree(unsigned char* srcBytes, int width, int height, float gradient[], unsigned char degree[], float* GradientMax)  
{  
    float gx, gy;  
    int temp, pos;  
    float div;  
    float PI = 3.1415926f;
    float t = 180.0f/PI;
    for (int j = 1; j < height - 1; j++)  
    {  
        for (int i = 1; i < width - 1; i++)  
        {  
            pos = i + j * width;
            gx = srcBytes[pos + 1 - width] + srcBytes[pos + 1] + srcBytes[pos + 1] + srcBytes[pos + 1 + width] - srcBytes[pos - 1 - width] - (srcBytes[pos - 1] + srcBytes[pos - 1]) - srcBytes[pos - 1 + width];  
            gy = srcBytes[pos - 1 - width] + srcBytes[pos - width] + srcBytes[pos - width] + srcBytes[pos + 1 - width] - srcBytes[pos - 1 + width] - (srcBytes[pos + width] + srcBytes[pos + width]) - srcBytes[pos + 1 + width];  
            // 求出每个点的梯度值幅度
            gradient[pos] = (float)sqrt((float)(gx * gx + gy * gy));  
            if (*GradientMax < gradient[pos])  
            {  
                *GradientMax = gradient[pos];  
            }  
            if (gx == 0)  
            {  
                temp = (gy == 0) ? 0 : 90;  
            }  
            else  
            {  
                div = gy / gx;  
                if (div < 0)  
                {  
                    temp = (int)(180 - atan(-div) * t);  
                }  
                else  
                {  
                    temp = (int)(atan(div) * t);  
                }  
                if (temp < 22.5f)  
                {  
                    temp = 0;  
                }  
                else if (temp < 67.5f)  
                {  
                    temp = 45;  
                }  
                else if (temp < 112.5f)  
                {  
                    temp = 90;  
                }  
                else if (temp < 157.5f)  
                {  
                    temp = 135;  
                }  
                else  
                    temp = 0;  
            }  
            degree[pos] = temp;  
        }  
    }  
    
} ; 
//非极大值抑制  
static void NonMaxMini(unsigned char* srcBytes, int width, int height, float gradient[], float GradientMax, unsigned char degree[])  
{  
    float leftPixel = 0, rightPixel = 0;
    int pos;
    for (int j = 1; j < height - 1; j++)  
    {  
        for (int i = 1; i < width - 1; i++)  
        {  
            pos = i + j * width;
            switch (degree[pos])  
            {  
                case 0:  
                    leftPixel = gradient[pos - 1];  
                    rightPixel = gradient[pos + 1];  
                    break;  
                case 45:  
                    leftPixel = gradient[pos - 1 + width];  
                    rightPixel = gradient[pos + 1 - width];  
                    break;  
                case 90:  
                    leftPixel = gradient[pos + width];  
                    rightPixel = gradient[pos - width];  
                    break;  
                case 135:  
                    leftPixel = gradient[pos + 1 + width];  
                    rightPixel = gradient[pos - 1 - width];  
                    break;  
                default:  
                    break;  
            }  
            if ((gradient[pos] < leftPixel) || (gradient[pos] < rightPixel))  
            {  
                srcBytes[pos] = 0;  
            }  
            else  
            {  
                srcBytes[pos] = (int)(255.0f * gradient[pos] / GradientMax);  
            }  
        }  
    }  
};  
//双阈值边缘判断  
static void TwoThreshouldJudge(unsigned char* srcBytes, int width, int height, int highThreshold, int lowThreshould)  
{  
    int pos = 0;
    for (int j = 1; j < height - 1; j++)  
    {  
        for (int i = 1; i < width - 1; i++)  
        {  
            pos = i + j * width;
            if (srcBytes[pos] > highThreshold)  
            {  
                srcBytes[pos] = 255;  
            }  
            else if (srcBytes[pos] < lowThreshould)  
            {  
                srcBytes[pos] = 0;  
            }  
            else  
            {  
                // 8领域都小于高阈值,视为噪点,剔除掉
                if (srcBytes[pos - 1 - width] < highThreshold && srcBytes[pos - width] < highThreshold && srcBytes[pos + 1 - width] < highThreshold && srcBytes[pos - 1] < highThreshold  
                    && srcBytes[pos + 1] < highThreshold && srcBytes[pos - 1 + width] < highThreshold && srcBytes[pos + width] < highThreshold && srcBytes[pos + 1 + width] < highThreshold)  
                {  
                    srcBytes[pos] = 0;  
                }  
                else  
                    srcBytes[pos] = 255;  
            }  
        }  
    }  
}; 
/************************************************************
*Function:  Canny edge detection
*Description: Canny edge detection
*Params:    
*srcData:  image bgr data     
*width  :image width
*height :image height
*stride :image stride
*highThreshold:[0,255]
*lowThreshold: [0,255],default 0.4 * highThreshold
*Return :0-OK,or failed    
************************************************************/
int f_CannyEdgedetection(unsigned char* srcData, int width ,int height, int stride, int highThreshold,int lowThreshold)
{
    int ret = 0;
    int i, j, offset, pos, temp, size;
    unsigned char* pSrc = srcData;
    size = width * height;
    unsigned char* grayData = (unsigned char*)malloc(sizeof(unsigned char) * size);
    memset(grayData, 0, sizeof(unsigned char) * size);
    offset = stride - width * 4;
    //gray
    f_GrayOneChannel(srcData, grayData, width, height, stride);
    //gauss fiter
    for(j = 0; j < height; j++)
    {
        for(i = 0; i < width; i++)
        {
            pos = i + j * width;
            if(i == 0 || j == 0 || i == width - 1 || j == height - 1)
            {
                grayData[pos] = 0;
            }
            else
            {
                temp = ((grayData[pos] << 2) + grayData[pos - width - 1] + grayData[pos + 1 - width] + grayData[pos - 1 + width] + grayData[pos + 1 + width] + grayData[pos - width] + grayData[pos - width] + grayData[pos - 1] + grayData[pos - 1] + grayData[pos + width] + grayData[pos + width] + grayData[pos + 1] + grayData[pos + 1]) >> 4;  
                grayData[pos] = temp;
            }
        }
    }
    //gradient
    float* gradient = (float*)malloc(sizeof(float) * size);
    memset(gradient, 0, sizeof(float) * size);
    unsigned char* degree = (unsigned char*)malloc(sizeof(unsigned char) * size);
    memset(degree, 0, sizeof(unsigned char) * size);
    float GradientMax = 0;
    GetGradientDegree(grayData, width, height, gradient, degree, &GradientMax);
    //none max value 
    NonMaxMini(grayData, width, height, gradient,GradientMax,degree);
    //two threshold judgement
    TwoThreshouldJudge(grayData,width, height,highThreshold,lowThreshold);
    //recovery
    for(j = 0; j < height; j++)
    {
        for(i = 0; i < width; i++)
        {
            pSrc[0] = pSrc[1] = pSrc[2] = grayData[i + j * width];
            pSrc += 4;
        }
        pSrc += offset;
    }
    free(grayData);
    free(gradient);
    free(degree);
    return ret;
};

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 图像处理基础-拉普拉斯锐化

    拉普拉斯算法,数学描述上是求二阶导数,如果忘了高数里的二阶导,可以理解为简单的模板算子:

    用户1068165
  • 15.opengl高级-混合

    纹理缓和的计算也不复杂,根据alpha通道值做叠加或减除融合,详细可参考opengl-混合

    用户1068165
  • 照片处理-阿宝色滤镜

    阿宝色是一位摄影师名为阿宝(网名:aibao),原名董立竑,由他在2008年左右所创的一种特别的色彩。这种色彩主要是,橘色的肤色和偏青色的背景色调为主,整体的视...

    用户1068165
  • python表白神器你值得拥有

    上一期云舔狗的效果好像反响不错,大家纷纷摆脱双手的束缚python云舔狗自动给微信好友发送早安晚安和播报天气预报,然后纷纷获得她(们)的芳心,就此一行君建议大家...

    行哥玩Python
  • hihoCoder #1078 : 线段树的区间修改(线段树区间更新板子题)

    #1078 : 线段树的区间修改 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 对于小Ho表现出的对线段树的理解,小Hi表示挺满...

    Angel_Kitty
  • 在R里面对坐标进行基因组区域注释

    这里可以使用大名鼎鼎的Y书开发的ChIPseeker包,加上人类的注释信息包TxDb.Hsapiens.UCSC.hg38.knownGene来进行注释,示例代...

    生信技能树
  • 学以致用 | Python不白学!情人节送给女神的套路神器

    AiTechYun
  • 模拟入栈操作

    开源519
  • 基于HMM的中文词性标注 POSTagging

    给定 标注文本corpus4pos_tagging.txt,训练一个模型,用模型预测给定文本的词性

    Michael阿明
  • Letter Case Permutation

    用户1147447

扫码关注云+社区

领取腾讯云代金券