【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)
目錄
?
前言
一、邊緣檢測算法
1、一階算子
2、二階算子
二、一階算子
原圖像lena
1、Roberts算子
不同方向的算子模板
梯度的計算
系統(tǒng)代碼:
自定義函數(shù)代碼
結(jié)果
2、Prewitt
不同方向的算子
梯度計算
系統(tǒng)自帶代碼
自定義函數(shù)代碼
結(jié)果
3、Sobel
不同方向的算子
梯度計算
系統(tǒng)自帶代碼(opencv和skimage)
自定義函數(shù)代碼
結(jié)果
4、 Kirsch
不同方向的算子
梯度計算
自定義函數(shù)代碼
結(jié)果
5、Canny
canny主要計算步驟:
不同方向的算子
梯度計算
系統(tǒng)自帶代碼
自定義函數(shù)代碼
結(jié)果
三、填充補零操作(究竟補多少行)
1、why&how
2、cv2.copyMakeBorder()
3、創(chuàng)建零圖像矩陣
四、不同一階算子的比較
前言
耐心看完一定會有收獲的,大部分內(nèi)容也會在代碼中體現(xiàn),結(jié)合理論知識和代碼進行理解會更有效
一、邊緣檢測算法
邊緣檢測算法是指利用灰度值的不連續(xù)性質(zhì),以灰度突變?yōu)榛A(chǔ)分割出目標區(qū)域。對鋁鑄件表面進行成像后會產(chǎn)生一些帶缺陷的區(qū)域,這些區(qū)域的灰度值比較低,與背景圖像相比在灰度上會有突變,這是由于這些區(qū)域?qū)饩€產(chǎn)生散射所引起的。因此邊緣檢測算子可以用來對特征的提取。
1、一階算子
一種是基于一階微分的算子,也稱基于搜索的算子,首先通過一階導數(shù)計算邊緣強度,然后采用梯度的方向來對邊緣的局部方向進行尋找,同時根據(jù)該方向來尋找出局部梯度模的最大值,由此定位邊緣,如Roberts Cross算子,Prewitt算子Sobel算子,Kirsch算子,Canny算子,羅盤算子等;
圖像中的邊緣區(qū)域,像素值會發(fā)生“跳躍”,對這些像素求導,在其一階導數(shù)在邊緣位置為極值,這就是Sobel算子使用的原理——極值處就是邊緣。
2、二階算子
另一種是基于二階微分的算子,也稱基于零交叉的算子,通過尋找由圖像得到的二階導數(shù)的過零點來定位檢測邊緣,如Marr-Hildreth算子,Laplacian算子,LOG算子等
二階算子可見:《【圖像處理】——Python圖像分割邊緣檢測算法之二階梯度算子(laplace、log、dog算子)》
如果對像素值求二階導數(shù),會發(fā)現(xiàn)邊緣處的導數(shù)值為0
二、一階算子
參考:《一階算子opencv中函數(shù)的參數(shù)解讀與舉例》
一階微分算子進行邊緣檢測的思路大致就是通過指定大小的核(kernal)(也稱為算子)與圖像進行卷積,將得到的梯度進行平方和或者最大值作為新的梯度賦值給對應(yīng)的像素點,不同的一階微分算子主要的不同在于其算子即核的元素不同以及核的大小不一樣
以下是連續(xù)函數(shù)的一階導數(shù)求導公式:因為圖像是一個面,就相當于是灰度值關(guān)于x,y兩個方向的函數(shù),要求某一點的導數(shù),則是各個方向的偏導數(shù)的平方和再進行開方運算
離散函數(shù)的一階導數(shù)公式:
y'=[y(x0+h)-y(x0-h)]/(2h);這是一維函數(shù)的一階求導,h是步長,在圖像處理中一般為1
首先復習一下什么是卷積?
卷積就是對應(yīng)的元素相乘再進行累加的過程
原圖像lena
1、Roberts算子
Robert算子是用于求解對角線方向的梯度,因為根據(jù)算子GX和GY的元素設(shè)置可以看到,只有對角線上的元素非零
不同方向的算子模板
梯度的計算
系統(tǒng)代碼:
def sys_robert(img):'''直接調(diào)用系統(tǒng)skimage模塊給的函數(shù):param img: 待處理圖片:return: 返回的是邊緣圖像矩陣'''gray = cv2.imread(img,0)#讀取的時候直接將其讀取成灰色的圖片,變成一個二維灰度值矩陣edge_img = filters.roberts(gray)#進行邊緣檢測,得到邊緣圖像return edge_img自定義函數(shù)代碼
其中對于下列函數(shù)def_col可以直接利用cv2.filter2D(灰度圖,-1表示與原圖一樣的size,核)
def def_col(gray,kernal):'''對指定的圖像用指定的核對齊其進行卷積,得到卷積后的圖像:param gray: 補零操作后的灰度圖像:param kernal: 卷積核:return: 返回卷積后的圖像,即邊緣圖像'''edge_img = cv2.filter2D(gray, -1, kernal)#上面這句和下面的代碼有一樣的效果# h,w = gray.shape# edge_img = np.zeros((h-1,w-1))#用于存放卷積后的邊緣圖像矩陣# for i in range(h-1):# for j in range(w-1):# #用卷積核與圖像上對應(yīng)大小的塊進行卷積,這里是以左上角的格子為基準進行的卷積# edge_img[i,j]=gray[i,j]*kernal[0,0]+gray[i,j+1]*kernal[0,1]+gray[i+1,j]*kernal[1,0]+gray[i,j]*kernal[1,1]return edge_imgdef def_robert(img):'''對指定的圖像進行邊緣檢測:param img: 要檢測的圖像:return: 返回邊緣圖像'''gray = cv2.imread(img,0)# np.savetxt("result2.txt", gray, fmt="%d")h = gray.shape[0]w = gray.shape[1]#定義Robert算子的卷積核,這個是對角線方向x_kernal = np.array([[1,0],[0,-1]])y_kernal = np.array([[0,1],[-1,0]])#由于卷積核和圖像進行卷積是以右下角的像素進行定位卷積核和目標像素點的位置關(guān)系,因此為了能夠遍歷整個圖像,#需要在圖像的第一行和第一列進行補零操作gray_zero = np.zeros((h+1,w+1))#先生成一個(h+1,w+1)的零圖像矩陣#將原始圖像去填充零矩陣,第一行和第一列不填充for i in range(1,h+1):for j in range(1,w+1):gray_zero[i,j]=gray[i-1,j-1]gray = gray_zero#將填充后的矩陣復制給gray#通過卷積,得到x和y兩個方向的邊緣檢測圖像x_edge = def_col(gray,x_kernal)y_edge = def_col(gray,y_kernal)#創(chuàng)建一個與原始圖像大小一致的空圖像,用于存放經(jīng)過Robert算子的邊緣圖像矩陣edge_img = np.zeros((h,w),np.uint8)#根據(jù)計算公式得到最終的像素點灰度值for i in range(h):for j in range(w):edge_img[i,j] = (np.sqrt(x_edge[i,j]**2+y_edge[i,j]**2))return edge_imgif __name__ == '__main__':img = "test1.png"edge = def_robert(img)edge1 = sys_robert(img)#以下是將看一下兩種方法圖像各點的像素值的比值,用c存放# h,w = edge1.shape# c = np.zeros((h,w))# c[0:h,0:w] = edge1[0:h,0:w]/edge1[0:h,0:w]# for i in range(h):# for j in range(w):# if edge1[i,j] != 0:# c[i,j] = edge[i,j]/edge1[i,j]# else:# c[i,j] = edge[i,j]# print(max(c.ravel()))#進行numpy保存到TXT中的操作# np.savetxt("result.txt", edge, fmt="%d")# np.savetxt("c.txt", c)# np.savetxt("result1.txt", edge1)cv2.imshow('',edge)cv2.imshow('222',edge1)cv2.waitKey(0)結(jié)果
2、Prewitt
Prewitt算子利用像素點上下、左右鄰點的灰度差,在邊緣處達到極值檢測邊緣,去掉部分偽邊緣,對噪聲具有平滑作用 。其原理是在圖像空間利用兩個方向模板與圖像進行鄰域卷積來完成的,這兩個方向模板一個檢測水平邊緣,一個檢測垂直邊緣。
不同方向的算子
梯度計算
用某點的x向梯度和y向梯度的最大值代表該點梯度;2.用x向梯度和y向梯度的和代替該點梯度;3.用x向梯度和y向梯度的平方根代替該點的梯度(同python自帶的運算);4.用x向梯度和y向梯度絕對值的和代替該點的梯度
系統(tǒng)自帶代碼
from skimage import data,color,filters import matplotlib.pyplot as plt import numpy as np import cv2def sys_prewitt(img):'''prewitt系統(tǒng)自帶:param img: 原始圖像:return: 返回邊緣圖像'''img = cv2.imread(img,0)edge_img=filters.prewitt(img)return edge_img自定義函數(shù)代碼
def def_col(img,kernal):'''對指定的圖像用指定的核對齊其進行卷積,得到卷積后的圖像:param img: 補零操作后的灰度圖像:param kernal: 卷積核:return: 返回卷積后的圖像,即邊緣圖像'''edge_img = cv2.filter2D(img,-1,kernal)# h,w = img.shape# edge_img=np.zeros([h-2,w-2])# for i in range(h-2):# for j in range(w-2):# edge_img[i,j]=img[i,j]*kernal[0,0]+img[i,j+1]*kernal[0,1]+img[i,j+2]*kernal[0,2]+\# img[i+1,j]*kernal[1,0]+img[i+1,j+1]*kernal[1,1]+img[i+1,j+2]*kernal[1,2]+\# img[i+2,j]*kernal[2,0]+img[i+2,j+1]*kernal[2,1]+img[i+2,j+2]*kernal[2,2]return edge_imgdef def_prewitt(img,type_flags):gray = cv2.imread(img,0)h = gray.shape[0]w = gray.shape[1]x_prewitt=np.array([[1,0,-1],[1,0,-1],[1,0,-1]])y_prewitt=np.array([[1,1,1],[0,0,0],[-1,-1,-1]])img=np.zeros([h+2,w+2])img[2:h+2,2:w+2]=gray[0:h,0:w]edge_x_img=def_col(img,x_prewitt)edge_y_img=def_col(img,y_prewitt)#p(i,j)=max[edge_x_img,edge_y_img]這里是將x,y中最大的梯度來代替該點的梯度edge_img_max=np.zeros([h,w],np.uint8)for i in range(h):for j in range(w):if edge_x_img[i][j]>edge_y_img[i][j]:edge_img_max=edge_x_img[i][j]else:edge_img_max=edge_y_img#p(i,j)=edge_x_img+edge_y_img#將梯度和替代該點梯度edge_img_sum=np.zeros([h,w],np.uint8)for i in range(h):for j in range(w):edge_img_sum[i][j]=edge_x_img[i][j]+edge_y_img[i][j]# p(i,j)=|edge_x_img|+|edge_y_img|將絕對值的和作為梯度edge_img_abs = np.zeros([h, w],np.uint8)for i in range(h):for j in range(w):edge_img_abs[i][j] = abs(edge_x_img[i][j]) + abs(edge_y_img[i][j])#p(i,j)=sqrt(edge_x_img**2+edge_y_img**2)將平方和根作為梯度edge_img_sqrt=np.zeros([h,w],np.uint8)for i in range(h):for j in range(w):edge_img_sqrt[i][j]=np.sqrt((edge_x_img[i][j])**2+(edge_y_img[i][j])**2)type = [edge_img_max,edge_img_sum,edge_img_abs,edge_img_sqrt]return type[type_flags]if __name__ == '__main__':img = 'colorful_lena.jpg'edge = sys_prewitt(img)edge1 = def_prewitt(img,3)cv2.imshow('',edge)cv2.imshow('1',edge1)cv2.waitKey(0)結(jié)果
3、Sobel
sobel和prewitt算子很像,都是檢測水平和垂直邊緣,但是其最終梯度的計算和算子的元素值不一樣
不同方向的算子
梯度計算
系統(tǒng)自帶代碼(opencv和skimage)
import cv2 from skimage import filters,color import numpy as npdef cv2_sobel(img):'''利用cv2自帶的函數(shù)進行sobel邊緣檢測:param img: 待檢測的圖像:return: 返回不同方向梯度的邊緣圖像矩陣,通過控制不同方向的導數(shù)為1或者0來選擇卷積核'''img = cv2.imread(img)#讀取圖片gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#灰度化sobel_x = cv2.Sobel(gray, cv2.CV_8U, 1, 0)#通過控制dx,dy的值來控制梯度的方向,這里是求x方向的梯度sobel_y = cv2.Sobel(gray, cv2.CV_8U, 0, 1)#這里是求y方向的梯度sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 1)#這里是求x,y方向綜合的梯度return [sobel_x,sobel_y,sobel]def skimage_sobel(img):'''利用skimage模塊自帶的函數(shù)進行圖像的邊緣檢測:param img: 待檢測圖像:return: 返回的是邊緣圖像矩陣'''gray = cv2.imread(img,0)edge_img = filters.sobel(gray)#這里求的是x,y方向兩個梯度的綜合return edge_img自定義函數(shù)代碼
def sobel_cal(img, kernal):'''自定義sobel卷積函數(shù):param img: 已經(jīng)補零了的圖像:param kernal: 指定方向的梯度算子,核:return: 返回梯度矩陣'''edge_img = cv2.filter2D(img, -1, kernal)# h, w = img.shape# img_filter = np.zeros([h, w])# #機芯卷積操作# for i in range(h - 2):# for j in range(w - 2):# img_filter[i][j] = img[i][j] *kernal[0][0] + img[i][j + 1] * kernal[0][1] + img[i][j + 2] * kernal[0][2] + \# img[i + 1][j] * kernal[1][0] + img[i + 1][j + 1] *kernal[1][1] + img[i + 1][j + 2] * kernal[1][2] + \# img[i + 2][j] * kernal[2][0] + img[i + 2][j + 1] * kernal[2][1] + img[i + 2][j + 2] * kernal[2][2]return edge_imgdef def_sobel(img):#定義不同方向的梯度算子x_sobel = np.array([[-1, 0, 1],[-2, 0, 2],[-1, 0, 1]])y_sobel = np.array([[-1, -2, -1],[0, 0, 0],[1, 2, 1]])gray_img = cv2.imread(img,0)h, w = gray_img.shapeimg = np.zeros([h + 2, w + 2],np.uint8)img[2:h + 2, 2:w + 2] = gray_img[0:h, 0:w]x_edge_img = sobel_cal(img, x_sobel)y_edge_img = sobel_cal(img, y_sobel)edge_img = np.zeros([h, w])for i in range(h):for j in range(w):edge_img[i][j] = np.sqrt(x_edge_img[i][j] ** 2 + y_edge_img[i][j] ** 2) / (np.sqrt(2))return edge_imgif __name__ == '__main__':img = "test1.png"sobel_x,sobel_y,sobel = cv2_sobel(img)sk_sobel = skimage_sobel(img)def_sobelimg = def_sobel(img)cv2.imshow("src", cv2.imread(img))cv2.imshow("Sobel_x", sobel_x)cv2.imshow("Sobel_y", sobel_y)cv2.imshow("Sobel", sobel)cv2.imshow("sk_Sobel", sk_sobel)cv2.imshow("def_Sobel", def_sobelimg)cv2.waitKey(0)cv2.destroyAllWindows()結(jié)果
4、 Kirsch
不同方向的算子
梯度計算
Kirsch邊緣算子由八個方向的卷積核構(gòu)成,這8個模板代表8個方向,對圖像上的8個特定邊緣方向作出最大響應(yīng),運算中取最大值作為圖像的邊緣輸出
自定義函數(shù)代碼
from skimage import data,color import matplotlib.pyplot as plt import numpy as np import cv2def conv_cal(img,kernal):edge_img = cv2.filter2D(img, -1, kernal)# h,w=img.shape# img_filter=np.zeros([h,w])# for i in range(h-2):# for j in range(w-2):# img_filter[i][j]=img[i][j]*filter[0][0]+img[i][j+1]*filter[0][1]+img[i][j+2]*filter[0][2]+\# img[i+1][j]*filter[1][0]+img[i+1][j+1]*filter[1][1]+img[i+1][j+2]*filter[1][2]+\# img[i+2][j]*filter[2][0]+img[i+2][j+1]*filter[2][1]+img[i+2][j+2]*filter[2][2]return edge_imgdef def_krisch(img):#定義算子krisch1=np.array([[5,5,5],[-3,0,-3],[-3,-3,-3]])krisch2=np.array([[-3,-3,-3],[-3,0,-3],[5,5,5]])krisch3=np.array([[5,-3,-3],[5,0,-3],[5,-3,-3]])krisch4=np.array([[-3,-3,5],[-3,0,5],[-3,-3,5]])krisch5=np.array([[-3,-3,-3],[-3,0,5],[-3,5,5]])krisch6=np.array([[-3,-3,-3],[5,0,-3],[5,5,-3]])krisch7=np.array([[-3,5,5],[-3,0,5],[-3,-3,-3]])krisch8=np.array([[5,5,-3],[5,0,-3],[-3,-3,-3]])gray_img = cv2.imread(img,0)w,h=gray_img.shapeimg=np.zeros([w+2,h+2])img[2:w+2,2:h+2]=gray_img[0:w,0:h]edge1=conv_cal(img,krisch1)edge2=conv_cal(img,krisch2)edge3=conv_cal(img,krisch3)edge4=conv_cal(img,krisch4)edge5=conv_cal(img,krisch5)edge6=conv_cal(img,krisch6)edge7=conv_cal(img,krisch7)edge8=conv_cal(img,krisch8)edge_img=np.zeros([w,h],np.uint8)for i in range(w):for j in range(h):edge_img[i][j]=max(list([edge1[i][j],edge2[i][j],edge3[i][j],edge4[i][j],edge5[i][j],edge6[i][j],edge7[i][j],edge8[i][j]]))return [edge1,edge2,edge3,edge4,edge5,edge6,edge7,edge8,edge_img]if __name__ == '__main__':img = 'colorful_lena.jpg'edge1, edge2, edge3, edge4, edge5, edge6, edge7, edge8,edge_img = def_krisch(img)cv2.imshow('1',edge1)cv2.imshow('2',edge2)cv2.imshow('3',edge3)cv2.imshow('4',edge4)cv2.imshow('5',edge5)cv2.imshow('6',edge6)cv2.imshow('7',edge7)cv2.imshow('8',edge8)cv2.imshow('9',edge_img)cv2.waitKey(0)結(jié)果
這個結(jié)果我也看不懂。。。。
5、Canny
?????? 可以說,Canny 邊緣檢測算法是被業(yè)界公認的性能最為優(yōu)良的邊緣檢測算法之一。Canny算法不是像Roberts、Prewitt、Sobel等這樣簡單梯度算子或銳化模板,它是在梯度算子基礎(chǔ)上,引入了一種能獲得抗噪性能好、定位精度高的單像素邊緣的計算策略。
?????? Canny把邊緣檢測問題轉(zhuǎn)化為檢測單位函數(shù)極大值問題。在高斯噪聲假設(shè)中,一個典型的邊緣代表一個階躍的強度變化。
?????? 根據(jù)這個模型,一個好的邊緣檢測算子應(yīng)滿足以下3個指標:
(1) 低失誤概率;(2) 高位置精度;(3) 對每個邊緣有唯一的響應(yīng)。
換句話說邊緣檢測問題就是要做到: 抑制噪聲,精確定位邊緣
canny主要計算步驟:
Canny 邊緣檢測算法的主要計算步驟包括四個方面:
(1) 利用高斯濾波器對原始圖像進行平滑濾波,以提高算法的抗噪性。
(2) 用一階有限差分近似代替偏導數(shù),計算圖像梯度強度和方向。計算梯度可以利用先前的Roberts、 Prewitt、Sobel等算子(文中用的是Prewitt 算子)。其中,方向信息是為了下一步計算需要。
(3) 利用第(2)步梯度方向劃分(劃分為四個方向 0\ -45\ 90 \45 )進行梯度強度的非極大抑制,獲取單像素邊緣點。
個人認為比較難懂的就是第三步,實際上做的就是檢測梯度方向是否垂直于切線方向,那么該怎么來確定這個切線方向呢?因為每個像素點的梯度方向在第二部中已經(jīng)確定了,因此我們只需要取與梯度方向垂直的三個像素點即可,若該點的像素值是這三個中最大的,則說明這是梯度的切線方向處的極大值點,這就說明了這就是我們要找的邊緣(邊緣的灰度值突變性大,因此梯度大),下面用圖表示了一下,可以結(jié)合代碼進行查看
(4) 雙(或滯后)閾值進行邊緣的二值化。
基本思路:通過梯度值和梯度方向劃分后,得到一個只含有鄰域為最大梯度的像素矩陣,這些點設(shè)為0,其余為255,然后再根據(jù)閾值對前面的像素矩陣進行修正設(shè)置,這樣就得到了比較好的邊緣圖像
參考:https://blog.csdn.net/weixin_44403952/article/details/90375013
不同方向的算子
這里的算子是使用的是prewitt算子,也可以自定義一個合理的算子進行使用梯度計算
梯度計算
canny算法中的梯度在計算過程中會變化好多次:一個是由計算公式得到的梯度,類似于krewitt算子;一種是通過對第一種梯度進行梯度方向劃分以及非極大值梯度抑制后得到的梯度,另一種就是梯度方向
系統(tǒng)自帶代碼
# coding=utf-8 import cv2 import numpy as npdef cv2_canny(img):'''由于Canny只能處理灰度圖,所以將讀取的圖像轉(zhuǎn)成灰度圖。用高斯平滑處理原圖像降噪。調(diào)用Canny函數(shù),指定最大和最小閾值,其中apertureSize默認為3。:param img: :return: '''img = cv2.imread(img, 0)img = cv2.GaussianBlur(img, (3, 3), 2)edge_img = cv2.Canny(img, 50,100)return edge_img自定義函數(shù)代碼
def def_canny(img,top=1,buttom=1,left=1,right=1):'''自定義canny算子,這里可以修改的參數(shù)有填充的方式和值、高斯濾波是核的大小以及sigmaX的值:param img: 待測圖片:param top: 圖像上方填充的像素行數(shù):param buttom: 圖像下方填充的像素行數(shù):param left: 圖像左方填充的像素行數(shù):param right: 圖像右方填充的像素行數(shù):return: [img1,img2,img3,theta]img1:梯度幅值圖;img2:非極大值抑制梯度灰度圖;img3:最終邊緣圖像;theta:梯度方向灰度圖'''#算子,這里的梯度算子可以使用sobel、sobel等算子,這里用的是prewitt梯度算子m1 = np.array([[-1,0,1],[-1,0,1],[-1,0,1]])m2 = np.array([[-1,-1,-1],[0,0,0],[1,1,1]])# 第一步:完成高斯平滑濾波img = cv2.imread(img,0)#將彩色讀成灰色圖片img = cv2.GaussianBlur(img,(3,3),2)#高斯濾波# 第二步:完成一階有限差分計算,計算每一點的梯度幅值與方向img1 = np.zeros(img.shape,dtype="uint8") # 與原圖大小相同,用于存放梯度值theta = np.zeros(img.shape,dtype="float") # 方向矩陣原圖像大小,用于存放梯度方向img = cv2.copyMakeBorder(img,top,buttom,left,right,borderType=cv2.BORDER_CONSTANT,value=0)#表示增加的像素數(shù),類似于補零操作,這里上下左右均增加了一行填充方式為cv2.BORDER_REPLICATErows,cols = img.shape#以下是進行了卷積,以核與圖像的卷積來代替核中間對應(yīng)的那個值#dst = cv2.filter2D(img, -1, kernel)這個函數(shù)雖然可以直接進行卷積,但是無法返回梯度方向for i in range(1,rows-1):for j in range(1,cols-1):# Gy,對應(yīng)元素相乘再累加Gy = (np.dot(np.array([1, 1, 1]), (m1 * img[i - 1:i + 2, j - 1:j + 2]))).dot(np.array([[1], [1], [1]]))# Gx,對應(yīng)元素相乘再累加Gx = (np.dot(np.array([1, 1, 1]), (m2 * img[i - 1:i + 2, j - 1:j + 2]))).dot(np.array([[1], [1], [1]]))#將所有的角度轉(zhuǎn)換到-180到180之間if Gx[0] == 0:theta[i-1,j-1] = 90continueelse:temp = (np.arctan(Gy[0] / Gx[0]) ) * 180 / np.pi#求梯度方向if Gx[0]*Gy[0] > 0:if Gx[0] > 0:theta[i-1,j-1] = np.abs(temp)else:theta[i-1,j-1] = (np.abs(temp) - 180)if Gx[0] * Gy[0] < 0:if Gx[0] > 0:theta[i-1,j-1] = (-1) * np.abs(temp)else:theta[i-1,j-1] = 180 - np.abs(temp)img1[i-1,j-1] = (np.sqrt(Gx**2 + Gy**2))#求兩個方向梯度的平方和根#以下對梯度方向進行了劃分,將梯度全部劃分為0,90,45,-45四個角度for i in range(1,rows - 2):for j in range(1, cols - 2):if ( ( (theta[i,j] >= -22.5) and (theta[i,j]< 22.5) ) or( (theta[i,j] <= -157.5) and (theta[i,j] >= -180) ) or( (theta[i,j] >= 157.5) and (theta[i,j] < 180) ) ):theta[i,j] = 0.0elif ( ( (theta[i,j] >= 22.5) and (theta[i,j]< 67.5) ) or( (theta[i,j] <= -112.5) and (theta[i,j] >= -157.5) ) ):theta[i,j] = 45.0elif ( ( (theta[i,j] >= 67.5) and (theta[i,j]< 112.5) ) or( (theta[i,j] <= -67.5) and (theta[i,j] >= -112.5) ) ):theta[i,j] = 90.0elif ( ( (theta[i,j] >= 112.5) and (theta[i,j]< 157.5) ) or( (theta[i,j] <= -22.5) and (theta[i,j] >= -67.5) ) ):theta[i,j] = -45.0# 第三步:進行 非極大值抑制計算#這里做的事情其實就是在尋找極值點,像素點的值是與該像素點的方向垂直方向上相鄰三個像素最大的,則將該像素點定義為極值點,即邊緣像素img2 = np.zeros(img1.shape) # 非極大值抑制圖像矩陣for i in range(1,img2.shape[0]-1):for j in range(1,img2.shape[1]-1):if (theta[i,j] == 0.0) and (img1[i,j] == np.max([img1[i,j],img1[i+1,j],img1[i-1,j]]) ):img2[i,j] = img1[i,j]if (theta[i,j] == -45.0) and img1[i,j] == np.max([img1[i,j],img1[i-1,j-1],img1[i+1,j+1]]):img2[i,j] = img1[i,j]if (theta[i,j] == 90.0) and img1[i,j] == np.max([img1[i,j],img1[i,j+1],img1[i,j-1]]):img2[i,j] = img1[i,j]if (theta[i,j] == 45.0) and img1[i,j] == np.max([img1[i,j],img1[i-1,j+1],img1[i+1,j-1]]):img2[i,j] = img1[i,j]# 第四步:雙閾值檢測和邊緣連接img3 = np.zeros(img2.shape) #定義雙閾值圖像# TL = 0.4*np.max(img2)# TH = 0.5*np.max(img2)TL = 50#低閾值TH = 100#高閾值#關(guān)鍵在這兩個閾值的選擇#小于低閾值的則設(shè)置為0,大于高閾值的則設(shè)置為255for i in range(1,img3.shape[0]-1):for j in range(1,img3.shape[1]-1):#將比較明確的灰度級的像素點繪制出來if img2[i,j] < TL:img3[i,j] = 0elif img2[i,j] > TH:img3[i,j] = 255#如果一個像素點的值在二者之間,且這個像素點的鄰近的八個像素點有一個的像素值是小于高閾值的,則將該點設(shè)置為255#實際上下面這段代碼就是將剩余的像素點都設(shè)置成了255,這樣就實現(xiàn)了邊緣連接elif (( img2[i+1,j] < TH) or (img2[i-1,j] < TH )or( img2[i,j+1] < TH )or(img2[i,j-1] < TH) or (img2[i-1, j-1] < TH )or ( img2[i-1, j+1] < TH) or( img2[i+1, j+1] < TH ) or ( img2[i+1, j-1] < TH) ):img3[i,j] = 255return [img1,img2,img3,theta]if __name__ == '__main__':img = 'test1.png'img1, img2, img3, theta = def_canny(img)cv2.imshow("1", cv2.imread(img, 0)) # 原始圖像cv2.imshow("2", img1) # 梯度幅值圖cv2.imshow("3", img2) # 非極大值抑制灰度圖cv2.imshow("4", img3) # 最終效果圖cv2.imshow("theta", theta) # 角度值灰度圖edge_img = cv2_canny(img)cv2.imshow("orign", cv2.imread(img))cv2.imshow('Canny', edge_img)cv2.waitKey(0)cv2.destroyAllWindows()結(jié)果
?
三、填充補零操作(究竟補多少行)
1、why&how
這里我們要和卷積神經(jīng)網(wǎng)絡(luò)的卷積區(qū)分開來,卷積神經(jīng)網(wǎng)絡(luò)卷積的主要目的是將圖片的信息進行壓縮,因此經(jīng)過卷積圖片的尺寸會變小,而在我們這里邊緣檢測中,我們要保證圖像的大小不發(fā)生改變,這時候我們就需要進行補零操作,具體補幾行幾列,還得看情況而定,一般是和核的大小有關(guān),一般情況下假設(shè)核是nXn的,那么就需要補充n-1行和n-1列
注意這里不一定是補零,也可以是其他的填充方法,如復制邊緣的像素值進行填充,出來的效果就是拖拉的感覺,
2、cv2.copyMakeBorder()
在opencv中提供了函數(shù)cv2.copyMakeBorder()進行填充
cv2.copyMakeBorder(src, top, bottom, left, right, borderType, dst=None, value=None) cv2.copyMakeBorder(img, 1, 1, 1, 1, cv2.BORDER_DEFAULT)上面代碼就表示按照默認的填充方式在上下左右各填充一行像素
可參考:https://blog.csdn.net/zfjBIT/article/details/86655182
3、創(chuàng)建零圖像矩陣
記住一定要將其類型設(shè)置為dtype = np.uint8,否則不太好,多次創(chuàng)建0矩陣圖像時,只需要將最后一個進行設(shè)置即可
img3 = np.zeros(img2.shape,np.uint8)四、不同一階算子的比較
??????? Roberts、Prewitt、Sobel等梯度算子不能獲取單像素邊緣,并進行必要的計算結(jié)果對比展示。實際上,以上簡單的梯度算子與圖像進行卷積濾波,僅僅是對圖像的一個銳化過程,得到的僅是原始圖像的梯度圖,而不是最終的邊緣結(jié)果,當然不會是單像素邊緣。
?????? 梯度算子Prewitt和Sobel它們的計算過程比較簡單,所以計算出來的結(jié)果精度也就較低,利用它們進行檢測出來的圖像輪 廓比較簡單,對于那些細的邊緣以及裂紋缺陷就有可能檢測不出來;相比于梯度算子,在檢測效果方面,LOG濾波算子以及Canny算子會更好,對于圖像的那些細節(jié)的邊緣特征它們也可以檢測得出來。研究表明,與LOG算子相比,采用Canny算子出來的邊緣檢測效果比較好,同時噪聲干擾也較少
總結(jié)
以上是生活随笔為你收集整理的【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uds 诊断协议的bootloader开
- 下一篇: bt种子简介与magnet磁力介绍