【數字影像處理】影像形態學演算法C語言實現(影像卷積,膨脹,腐蝕,開運算,閉運算,頂帽,黑帽,雕版,銳化)

與光同程發表於2020-11-25

(一)影像卷積

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

相關文章