【數字影像處理】影像形態學演算法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
相關文章
- Python 影像處理 OpenCV (10):影像處理形態學之頂帽運算與黑帽運算PythonOpenCV
- OpenCV計算機視覺學習(5)——形態學處理(腐蝕膨脹,開閉運算,禮帽黑帽,邊緣檢測)OpenCV計算機視覺
- opencv 影像腐蝕、影像的膨脹OpenCV
- Python 影像處理 OpenCV (9):影像處理形態學開運算、閉運算以及梯度運算PythonOpenCV梯度
- 10.[機器視覺]Halcon形態學膨脹,腐蝕,開運算,閉運算視覺
- [Python影象處理] 十.形態學之影象頂帽運算和黑帽運算Python
- Python影像處理丨三種實現影像形態學轉化運算模式Python模式
- Python從零到壹丨影像增強的頂帽運算和底帽運算Python
- 數字影像處理實驗(四)影像銳化
- Python 影像處理 OpenCV (4):影像算數運算以及修改顏色空間PythonOpenCV
- [Python影象處理] 九.形態學之影象開運算、閉運算、梯度運算Python梯度
- 形態學影像處理
- [Python影象處理] 八.影象腐蝕與影象膨脹Python
- 數字影像處理(一)之灰度轉換和卷積python實現卷積Python
- 影像處理領域的加速運算元收集
- 影像處理中的valid卷積與same卷積卷積
- Numpy 加法運算,opencv 加法運算,影像的融合OpenCV
- 數字影像處理day_12 影像分割
- OpenCV計算機視覺學習(15)——淺談影像處理的飽和運算和取模運算OpenCV計算機視覺
- webgl 影像處理 加速計算Web
- 【傳統影像處理】1 數字影像基礎
- 數字影像處理--認識影像各種概念
- Python 影像處理 OpenCV (12): Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元邊緣檢測技術PythonOpenCV
- OpenCV計算機視覺學習(2)——影像算術運算 & 掩膜mask操作(數值計算,影像融合,邊界填充)OpenCV計算機視覺
- Python 影像處理 OpenCV (13): Scharr 運算元和 LOG 運算元邊緣檢測技術PythonOpenCV
- Win8 Metro(C#)數字影像處理--2.62影像對數增強C#
- verilog實現矩陣卷積運算矩陣卷積
- 複數的四則運算(C語言實現)C語言
- 影像處理--影像特效特效
- 影像處理第二篇之波段運算(軟體外掛篇)
- [00]數字影像處理-matlab速成Matlab
- opencv入門系列教學(六)影像上的算術運算(加法、融合、按位運算)OpenCV
- opencv影像處理學習隨筆:幫助文件運算公式中saturate的含義OpenCV公式
- 個人實驗程式碼記錄 | 數字影像處理實驗3·影像直方圖與均衡化處理直方圖
- 演算法 | 數字影像處理之「中值濾波」演算法
- 影像的卷積和池化操作卷積
- 逍遙自在學C語言 | 算數運算子C語言
- 高效能運算-NEON-影像旋轉