【数字图像处理】图像形态学算法C语言实现(图像卷积,膨胀,腐蚀,开运算,闭运算,顶帽,黑帽,雕版,锐化)

(一)图像卷积

1. 图像卷积

图像卷积是进行空间域滤波与梯度运算的基础。首先我们需要理解卷积的运算方式从而可以编程实现。

2. 数字信号处理中的卷积

卷积一词最开始出现在信号与线性系统中,信号与线性系统中讨论的就是信号经过一个线性系统以后发生的变化。由于现实情况中常常是一个信号前一时刻的输出影响着这一时刻的输出,所在一般利用系统的单位响应与系统的输入求卷积,以求得系统的输出信号(当然要求这个系统是线性时不变的)。
卷积的定义:
卷积是两个变量在某范围内相乘后求和的结果。如果卷积的变量是序列x(n)和h(n),则卷积的结果:
在这里插入图片描述

3 数字图像处理中的卷积

数字图像是一个二维的离散信号,对数字图像做卷积操作其实就是利用卷积核(卷积模板)在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完所有图像的过程。
在这里插入图片描述

图表 3
这张图可以清晰的表征出整个卷积过程中一次相乘后相加的结果:该图片选用3*3的卷积核,卷积核内共有九个数值,所以图片右上角公式中一共有九行,而每一行都是图像像素值与卷积核上数值相乘,最终结果-8代替了原图像中对应位置处的1。这样沿着图片一步长为1滑动,每一个滑动后都一次相乘再相加的工作,我们就可以得到最终的输出结果。除此之外,卷积核的选择有一些规则:
(1)卷积核的大小一般是奇数,这样的话它是按照中间的像素点中心对称的,所以卷积核一般都是3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2。
(2)卷积核所有的元素之和一般要等于1,这是为了原始图像的能量(亮度)守恒。其实也有卷积核元素相加不为1的情况,下面就会说到。
(3)如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
(4)对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。

(二)图像卷积实现各种形态学运算

腐蚀

极小值卷积,求局部极小值

膨胀

极大值卷积,求局部极大值

形态学梯度

开运算

闭运算

顶帽

黑帽

雕版

锐化

li_conv.c

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2020-11-10 21:59:39
 * @LastEditors: Yueyang
 * @LastEditTime: 2020-11-24 21:15:30
 */
#ifndef LI_CONV_C
#define LI_CONV_C



#include "cv.h"
#include "li_image_proc.h"
#include <stdio.h>


/**
 * @name: Li_GetKernel
 * @msg:  得到卷积核矩阵
 * @param {double* data 数据
 *         BYTE KernalKind 卷积核边长}
 * @return {Li_Kernel*}
 */
LI_API 
Li_Kernel* Li_GetKernel(double* data,BYTE KernalKind)
{
    Li_Kernel * kernel;
    kernel=(Li_Kernel*)li_malloc_arr(sizeof(Li_Kernel));
    kernel->arr=(double*)li_malloc_arr(KernalKind*KernalKind*sizeof(double));
    for(int i=0;i<KernalKind*KernalKind;i++)
    kernel->arr[i]=data[i];
    kernel->width=KernalKind;
    kernel->height=KernalKind;
    kernel->arrsize=KernalKind*KernalKind;
    return kernel;
}

/**
 * @name: Li_Convolute
 * @msg: 计算图像卷积
 * @param {Li_Image* img 卷积图像
 *         Li_Kernel* kernal 卷积核 }
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_Convolute(Li_Image* img,Li_Kernel* kernal)
{
  if(img->imgdepth==LI_DEP_8U){
    if(kernal->width!=3) return NULL;
    BYTE* ptr[9]={0};
    BYTE* ptro;
    Li_Image* out=Li_Copy_Image(img);
    for(int i=0;i<img->height;i++)
        for(int j=0;j<img->width;j++)
        {
             BYTE sum=0;
             if(j-1>=0&&i-1>=0)
             ptr[0]=(BYTE*)img->at(img,j-1,i-1);
             if(j>=0&&i-1>=0)
             ptr[1]=(BYTE*)img->at(img,j+0,i-1);
             if(j+1<=img->width&&i-1>=0)
             ptr[2]=(BYTE*)img->at(img,j+1,i-1);
             if(j-1>=0&&i>=0)
             ptr[3]=(BYTE*)img->at(img,j-1,i+0);
             if(j>=0&&i>=0)
             ptr[4]=(BYTE*)img->at(img,j+0,i+0);
             if(j+1<=img->width&&i>=0)
             ptr[5]=(BYTE*)img->at(img,j+1,i+0);
             if(j-1>=0&&i+1<=img->height)
             ptr[6]=(BYTE*)img->at(img,j-1,i+1);
             if(j>=0&&i+1<=img->height)
             ptr[7]=(BYTE*)img->at(img,j+0,i+1);
             if(j+1<=img->width&&i+1<=img->height)
             ptr[8]=(BYTE*)img->at(img,j+1,i+1);
            for(int k=0;k<9;k++)
            {
                double* ptr2=(double*)(kernal->arr+k);
                if(ptr[k]!=NULL)
                {
                    sum+= (BYTE)(*ptr[k] * (*ptr2));
                }
                else 
                {
                    sum+=0;
                }
            }
            ptro=(BYTE*)out->at(out,j+0,i+0);
            *ptro=sum;
        }
        return out;
  }else if (img->imgdepth==LI_DEP_24U||img->imgdepth==LI_DEP_32U)
  {
     Li_Image* imgH[img->imgdepth+1];
     Li_Image* imgL[img->imgdepth+1];
     Li_Split(img,imgH);
     for(int i=0;i<img->imgdepth+1;i++)
     imgL[i]= Li_Convolute(imgH[i],kernal);     
     Li_Image* out2=Li_Combine(imgL,img->imgdepth);
     return out2;
  }
}


/**
 * @name: Li_Sharp
 * @msg:  图像锐化
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_Sharp(Li_Image* img)
{
    double data[9]={1,1,1,1,-7,1,1,1,1}; 
    if(img==NULL)return NULL;
    if(img->imgdepth==LI_DEP_8U){
        Li_Image* out;
        Li_Kernel* kernel;

        kernel=Li_GetKernel(data,3);
        out= Li_Convolute(img,kernel);
        return out;
    }else if (img->imgdepth==LI_DEP_24U||img->imgdepth==LI_DEP_32U)
    {
        Li_Image* imgH[img->imgdepth+1];
        Li_Image* imgL[img->imgdepth+1];
        Li_Split(img,imgH);
        for(int i=0;i<img->imgdepth+1;i++)  
        {
            imgL[i]=Li_Sharp(imgH[i]);
        }
        Li_Image* out2=Li_Combine(imgL,img->imgdepth);
        return out2;
    }
}

/**
 * @name: Li_Emboss
 * @msg:  图像雕版
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_Emboss(Li_Image* img)
{
    double data[9]={0,0,-1,0,-1,0,2,0,0}; 
    if(img==NULL)return NULL;
    if(img->imgdepth==LI_DEP_8U){
        Li_Image* out;
        Li_Kernel* kernel;

        kernel=Li_GetKernel(data,3);
        out= Li_Convolute(img,kernel);
        return out;
    }else if (img->imgdepth==LI_DEP_24U||img->imgdepth==LI_DEP_32U)
    {
        Li_Image* imgH[img->imgdepth+1];
        Li_Image* imgL[img->imgdepth+1];
        Li_Split(img,imgH);
        for(int i=0;i<img->imgdepth+1;i++)  
        {
            imgL[i]=Li_Emboss(imgH[i]);
        }
        Li_Image* out2=Li_Combine(imgL,img->imgdepth);
        return out2;
    }
}


/**
 * @name: Li_Erode
 * @msg: 图像腐蚀(局部最小)
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_Erode(Li_Image* img)
{
  if(img->imgdepth==LI_DEP_8U){
    BYTE kek=0;
    BYTE* ptr[9]={0};
    BYTE* ptro;
    Li_Image* out=Li_Copy_Image(img);
    for(int i=0;i<img->height;i++)
        for(int j=0;j<img->width;j++)
        {
             BYTE sum=0;
             for(int k=0;k<9;k++)
             ptr[k]=&kek;
             
             if(j-1>=0&&i-1>=0)
             ptr[0]=(BYTE*)img->at(img,j-1,i-1);
             if(j>=0&&i-1>=0)
             ptr[1]=(BYTE*)img->at(img,j+0,i-1);
             if(j+1<=img->width&&i-1>=0)
             ptr[2]=(BYTE*)img->at(img,j+1,i-1);
             if(j-1>=0&&i>=0)
             ptr[3]=(BYTE*)img->at(img,j-1,i+0);
             if(j>=0&&i>=0)
             ptr[4]=(BYTE*)img->at(img,j+0,i+0);
             if(j+1<=img->width&&i>=0)
             ptr[5]=(BYTE*)img->at(img,j+1,i+0);
             if(j-1>=0&&i+1<=img->height)
             ptr[6]=(BYTE*)img->at(img,j-1,i+1);
             if(j>=0&&i+1<=img->height)
             ptr[7]=(BYTE*)img->at(img,j+0,i+1);
             if(j+1<=img->width&&i+1<=img->height)
             ptr[8]=(BYTE*)img->at(img,j+1,i+1);
            
            BYTE temp[9];
            for(int k=0;k<9;k++)
            {
                if(ptr[k]!=NULL)
                {
                    temp[k]= (BYTE)(*ptr[k]);
                }
            }
            for(int m=0;m<9-1;m++)
                for(int n=0;n<9-m-1;n++)
                {
                    if(temp[m]>temp[m+1])
                    {
                        BYTE x=temp[m];
                        temp[m]=temp[n+1];
                        temp[n+1]=x;
                    }
                }
           ptro=(BYTE*)out->at(out,j+0,i+0);
           *ptro=temp[0];
        }
        return out;
  }else if (img->imgdepth==LI_DEP_24U||img->imgdepth==LI_DEP_32U)
  {
     Li_Image* imgH[img->imgdepth+1];
     Li_Image* imgL[img->imgdepth+1];
     Li_Split(img,imgH);
     for(int i=0;i<img->imgdepth+1;i++)
     imgL[i]= Li_Medeum(imgH[i]);     
     Li_Image* out2=Li_Combine(imgL,img->imgdepth);
     return out2;
  }
}


/**
 * @name: Li_Dilate
 * @msg: 图像膨胀(局部最小)
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_Dilate(Li_Image* img)
{
  if(img->imgdepth==LI_DEP_8U){
    BYTE kek=0;
    BYTE* ptr[9]={0};
    BYTE* ptro;
    Li_Image* out=Li_Copy_Image(img);
    for(int i=0;i<img->height;i++)
        for(int j=0;j<img->width;j++)
        {
             BYTE sum=0;
             for(int k=0;k<9;k++)
             ptr[k]=&kek;
             
             if(j-1>=0&&i-1>=0)
             ptr[0]=(BYTE*)img->at(img,j-1,i-1);
             if(j>=0&&i-1>=0)
             ptr[1]=(BYTE*)img->at(img,j+0,i-1);
             if(j+1<=img->width&&i-1>=0)
             ptr[2]=(BYTE*)img->at(img,j+1,i-1);
             if(j-1>=0&&i>=0)
             ptr[3]=(BYTE*)img->at(img,j-1,i+0);
             if(j>=0&&i>=0)
             ptr[4]=(BYTE*)img->at(img,j+0,i+0);
             if(j+1<=img->width&&i>=0)
             ptr[5]=(BYTE*)img->at(img,j+1,i+0);
             if(j-1>=0&&i+1<=img->height)
             ptr[6]=(BYTE*)img->at(img,j-1,i+1);
             if(j>=0&&i+1<=img->height)
             ptr[7]=(BYTE*)img->at(img,j+0,i+1);
             if(j+1<=img->width&&i+1<=img->height)
             ptr[8]=(BYTE*)img->at(img,j+1,i+1);
            
            BYTE temp[9];
            for(int k=0;k<9;k++)
            {
                if(ptr[k]!=NULL)
                {
                    temp[k]= (BYTE)(*ptr[k]);
                }
            }
            for(int m=0;m<9-1;m++)
                for(int n=0;n<9-m-1;n++)
                {
                    if(temp[m]>temp[m+1])
                    {
                        BYTE x=temp[m];
                        temp[m]=temp[n+1];
                        temp[n+1]=x;
                    }
                }
           ptro=(BYTE*)out->at(out,j+0,i+0);
           *ptro=temp[8];
        }
        return out;
  }else if (img->imgdepth==LI_DEP_24U||img->imgdepth==LI_DEP_32U)
  {
     Li_Image* imgH[img->imgdepth+1];
     Li_Image* imgL[img->imgdepth+1];
     Li_Split(img,imgH);
     for(int i=0;i<img->imgdepth+1;i++)
     imgL[i]= Li_Medeum(imgH[i]);     
     Li_Image* out2=Li_Combine(imgL,img->imgdepth);
     return out2;
  }
}

/**
 * @name: Li_Grandient
 * @msg: 形态学梯度
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_Grandient(Li_Image* img)
{
    Li_Image* erode=Li_Erode(img);
    Li_Image* dilate=Li_Dilate(img);
    Li_Image* minus=Li_Minus(dilate,erode);
    Li_Destroy_Image(erode);
    Li_Destroy_Image(dilate);
    return minus;
}

/**
 * @name: Li_Mod_Open
 * @msg: 形态学开运算
 * @param {Li_Image* img}
 * @return {Li_Image* }
 */
LI_API
Li_Image* Li_Mod_Open(Li_Image* img)
{
    Li_Image* dilate=Li_Dilate(img);//先膨胀
    Li_Image* erode=Li_Erode(dilate);//先腐蚀
    Li_Destroy_Image(dilate);
    return erode;
}

/**
 * @name: Li_Mod_Close
 * @msg: 形态学闭运算
 * @param {Li_Image* img}
 * @return {Li_Image* }
 */
LI_API
Li_Image* Li_Mod_Close(Li_Image* img)
{
    Li_Image* erode=Li_Erode(img);//先腐蚀
    Li_Image* dilate=Li_Dilate(erode);//先膨胀
    Li_Destroy_Image(erode);
    return dilate;
}

/**
 * @name: Li_TopHat
 * @msg: 图像顶帽运算
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_TopHat(Li_Image* img)
{
    Li_Image* open=Li_Mod_Open(img);
    Li_Image* top=Li_Minus(img,open);
    Li_Destroy_Image(open);
    return top;
}

/**
 * @name: Li_BlackHat
 * @msg: 图像黑帽运算
 * @param {Li_Image* img}
 * @return {Li_Image*}
 */
LI_API
Li_Image* Li_BlackHat(Li_Image* img)
{
    Li_Image* close=Li_Mod_Close(img);
    Li_Image* black=Li_Minus(img,close);
    Li_Destroy_Image(close);
    return black;
}

#endif // !LI_CONV_C

main.c

/*
 * @Descripttion: 图像卷积常见操作
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2020-10-26 19:35:49
 * @LastEditors: Yueyang
 * @LastEditTime: 2020-11-24 21:12:04
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "bmp.h"
#include "cv.h"
#include "li_image.h"
#include "li_painter.h"
#include "li_image_proc.h"

int main()
{
     BYTE* ptr=NULL;
     Li_Image* out =Li_Load_Image("./picture/whu_rgb888.bmp",LI_BMP_888);
     Li_Image* sharp=Li_Sharp(out);//图像锐化
     Li_Image* emboss=Li_Emboss(out);//雕版
     
     Li_Image* gray=Li_Convert_Image(out,LI_BMP_888_2_LI_BMP_8);
     Li_Image* bw  =Li_Threshold(gray,127);
     Li_Image* erode=Li_Erode(gray);//图像腐蚀
     Li_Image* dilate=Li_Dilate(gray);//图像膨胀
     Li_Image* add   =Li_Add(out,out);
     Li_Image* minus =Li_Minus(out,out);
     Li_Image* grand =Li_Grandient(gray);
     Li_Image* open  =Li_Mod_Open(gray);
     Li_Image* close =Li_Mod_Close(gray);
     Li_Image* top   =Li_TopHat(gray);
     Li_Image* black =Li_BlackHat(gray);

     Li_Image* noise =Li_Salt_Noise(out,1000);//椒盐噪声
     Li_Image* med =Li_Smooth(noise,Li_MEDIUM);//中值滤波
     Li_Image* conv=Li_Smooth(noise,Li_GAUSS);//高斯滤波
     Li_Image* ave=Li_Smooth(noise,Li_AVERAGE);//均值滤波

     Li_Save_Image("conv.bmp",conv);
     Li_Save_Image("sharp.bmp",sharp);
     Li_Save_Image("emboss.bmp",emboss);   
     Li_Save_Image("erode.bmp",erode); 
     Li_Save_Image("dilate.bmp",dilate);
     Li_Save_Image("gray.bmp",gray);
     Li_Save_Image("med.bmp",med); 
     Li_Save_Image("ave.bmp",ave); 
     Li_Save_Image("noise.bmp",noise);
     Li_Save_Image("add.bmp",add);
     Li_Save_Image("minus.bmp",minus);
     Li_Save_Image("grand.bmp",grand);
     Li_Save_Image("open.bmp",open);
     Li_Save_Image("close.bmp",close);
     Li_Save_Image("top.bmp",top);
     Li_Save_Image("black.bmp",black);
     LILOG("over");
     return 0; 
}

 

(三)效果展示

原图

在这里插入图片描述

腐蚀

在这里插入图片描述

膨胀

在这里插入图片描述

形态学梯度

在这里插入图片描述

开运算

在这里插入图片描述

闭运算

在这里插入图片描述

顶帽

在这里插入图片描述

黑帽

在这里插入图片描述

雕版

在这里插入图片描述

锐化

在这里插入图片描述

(四)写在后面

因为LiteCV项目才刚刚写了一个开头,代码中有错误的地方还望指出。我已经将项目同步到了github,我会实时更新这个代码仓库。
项目github地址:
LiteCV

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页