Python OpenCV 之圖像梯度埠通,Sobel 算子、Scharr 算子和 laplacian 算子

Python OpenCV 365 天學(xué)習(xí)計(jì)劃逛犹,與橡皮擦一起進(jìn)入圖像領(lǐng)域吧端辱。

@[toc](Python OpenCV)

基礎(chǔ)知識鋪墊

圖像梯度是計(jì)算圖像變化速度的方法,對于圖像邊緣部分虽画,灰度值如果變化幅度較大舞蔽,則其對應(yīng)梯度值也較大,反之码撰,圖像中比較平滑的部分渗柿,灰度值變化較小,相應(yīng)的梯度值變化也小脖岛。

有以上內(nèi)容就可以學(xué)習(xí)圖像梯度相關(guān)計(jì)算了朵栖,該知識后面會用到獲取圖像邊緣信息相關(guān)技術(shù)中。

OpenCV 提供三種不同的梯度濾波器柴梆,或者說高通濾波器:<kbd>Sobel</kbd>陨溅,<kbd>Scharr</kbd> 和 <kbd>Laplacian</kbd>。

在數(shù)學(xué)上绍在,<kbd>Sobel</kbd>门扇,<kbd>Scharr</kbd> 是求一階或二階導(dǎo)數(shù),<kbd>Scharr</kbd> 是對 <kbd>Sobel</kbd>(使用小的卷積核求解求解梯度角度時)的優(yōu)化偿渡,<kbd>Laplacian</kbd>是求二階導(dǎo)數(shù)臼寄。

數(shù)學(xué)部分我們依舊不展開,在第一遍通識學(xué)習(xí)之后卸察,從上帝視角去補(bǔ)充脯厨。

Sobel 算子和 Scharr 算子

Sobel 算子說明與使用

<kbd>Sobel</kbd>算子是高斯平滑與微分操作的結(jié)合體铅祸,所以它的抗噪聲能力很好(具體橡皮擦沒有學(xué)到精髓坑质,先用起來)。

在使用過程中可以設(shè)定求導(dǎo)的方向(xorderyorder)临梗,還可以設(shè)定使用的卷積核的大形卸蟆(ksize)。

函數(shù)原型如下:

dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

參數(shù)說明如下:

  • <kbd>src</kbd>:輸入圖像盟庞;
  • <kbd>ddepth</kbd>:圖像顏色深度吃沪,針對不同的輸入圖像,輸出圖像有不同的深度什猖,具體參加后文票彪,-1 表示圖像深度一致红淡;
  • <kbd>dx</kbd>:x 方向求導(dǎo)階數(shù);
  • <kbd>dy</kbd>:y 方向求導(dǎo)階數(shù)降铸;
  • <kbd>ksize</kbd>:內(nèi)核大小在旱,一般取奇數(shù),1,3,5,7等值推掸;
  • <kbd>scale</kbd>:縮放大小桶蝎,默認(rèn)值為 1;
  • <kbd>delta</kbd>:增量數(shù)值谅畅,默認(rèn)值為 0登渣;
  • <kbd>borderType</kbd>:邊界類型,默認(rèn)值為 <kbd>BORDER_DEFAULT</kbd>毡泻。

備注:如果 ksize=-1胜茧,會使用 3x3 的 <kbd>Scharr</kbd> 濾波器,該效果要比 3x3 的 <kbd>Sobel</kbd> 濾波器好(而且速度相同仇味,所以在使用 3x3 濾波器時應(yīng)該盡量使用 <kbd>Scharr</kbd> 濾波器)竹揍。

關(guān)于該函數(shù)的測試代碼如下(注意選擇自己的圖片,并放置到同一文件夾):

import cv2
import numpy as np

img = cv2.imread('./test.jpg')

dst = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
dst = cv2.convertScaleAbs(dst)
cv2.imshow("dst", dst)
cv2.waitKey()
cv2.destroyAllWindows()

以上代碼中有很多要說明和學(xué)習(xí)的邪铲,接下來我們逐步分析芬位。

<kbd>Sobel</kbd> 濾波器中的第二個參數(shù),設(shè)置為 <kbd>cv2.CV_64F</kbd>带到,在互聯(lián)網(wǎng)找了一下通用解釋

我們一般處理的都是 8 位圖像昧碉,當(dāng)計(jì)算的梯度小于零,會自動變成 0揽惹,造成邊界信息丟失被饿,關(guān)于為什么小于零,是由于從黑到白的邊界導(dǎo)數(shù)是整數(shù)搪搏,而從白到黑的邊界點(diǎn)導(dǎo)數(shù)是負(fù)數(shù)狭握。當(dāng)圖像深度是 np.uint8 的時候,負(fù)值就會變成 0疯溺,基于這個原因论颅,需要把輸出圖像的數(shù)據(jù)類型設(shè)置高一些,例如 <kbd>cv2.CV_16S</kbd>囱嫩,<kbd>cv2.CV_64F</kbd>等值恃疯。

當(dāng)然簡單記憶的方式,就是設(shè)置為 -1 即可墨闲。

處理完畢之后今妄,在通過 <kbd>cv2.convertScaleAbs</kbd>函數(shù)將其轉(zhuǎn)回原來的 uint8 格式,如果不轉(zhuǎn)換,獲取的圖像不正確盾鳞。

對比一下犬性,轉(zhuǎn)換與沒有轉(zhuǎn)換兩次運(yùn)行的效果,右側(cè)明顯失去邊界值腾仅。


![2021020213574489[1].png](https://upload-images.jianshu.io/upload_images/25899950-ac3aaf82057504bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

注意上文代碼中仔夺,還有一個細(xì)節(jié)需要說明一下

dst = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
  • <kbd>dx</kbd>:x 方向求導(dǎo)階數(shù);
  • <kbd>dy</kbd>:y 方向求導(dǎo)階數(shù)攒砖。

這其中 dx=1,dy=0 表示計(jì)算水平方向的缸兔,不計(jì)算垂直方向,說白了就是哪個方向等于 1吹艇,計(jì)算哪個方向惰蜜。

還可以對函數(shù)中的 <kbd>ddepth</kbd>參數(shù)進(jìn)行不同取值的比對,查看差異化內(nèi)容受神。下述代碼用到了圖像融合函數(shù) <kbd>cv.addWeighted</kbd>

import numpy as np
import cv2 as cv
def sobel_demo1(image):
    grad_x = cv.Sobel(image, cv.CV_16S, 1, 0)
    grad_y = cv.Sobel(image, cv.CV_16S, 0, 1)
    gradx = cv.convertScaleAbs(grad_x)
    grady = cv.convertScaleAbs(grad_y)
    cv.imshow('sobel_demo1_gradient_x', gradx)
    cv.imshow('sobel_demo1_gradient_y', grady)
    # 合并x, y兩個梯度
    add_image = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
    cv.imshow('sobel_demo1_addWeighted', add_image)

def sobel_demo2(image):
    grad_x = cv.Sobel(image, cv.CV_32F, 1, 0)
    grad_y = cv.Sobel(image, cv.CV_32F, 0, 1)
    gradx = cv.convertScaleAbs(grad_x)
    grady = cv.convertScaleAbs(grad_y)
    cv.imshow('sobel_demo2_gradient_x', gradx)
    cv.imshow('sobel_demo2_gradient_y', grady)
    # 合并x, y兩個梯度
    add_image = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
    cv.imshow('sobel_demo2_addWeighted', add_image)

src = cv.imread('./test.jpg', 1)
sobel_demo1(src)
sobel_demo2(src)
cv.waitKey(0)
cv.destroyAllWindows()

你可以不使用圖像融合函數(shù)抛猖,直接通過 <kbd>Sobel</kbd> 函數(shù)計(jì)算 x 方向和 y 方向的導(dǎo)數(shù),代碼如下:

def sobel_demo3(image):
    grad = cv.Sobel(image, cv.CV_16S, 1, 1)
    grad = cv.convertScaleAbs(grad)
    cv.imshow('sobel_demo1_gradient', grad)

得到的結(jié)果比圖像融合效果要差很多鼻听。


20210202134929911[1].png

這樣對比查閱不是很容易看清楚财著,可以結(jié)合下述代碼進(jìn)行改造,將圖片整合到一起展示撑碴。

def sobel_demo1(image):

    grad_x = cv.Sobel(image, cv.CV_16S, 1, 0)
    grad_y = cv.Sobel(image, cv.CV_16S, 0, 1)
    gradx = cv.convertScaleAbs(grad_x)
    grady = cv.convertScaleAbs(grad_y)

    # 合并x, y兩個梯度
    dst = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
    imgs1 = np.hstack([cv.cvtColor(image, cv.COLOR_BGR2RGB), gradx])
    imgs2 = np.hstack([grady, dst])
    img_all = np.vstack([imgs1, imgs2])
    plt.figure(figsize=(20, 10))
    plt.imshow(img_all, cmap=plt.cm.gray)
    plt.show()

上述代碼運(yùn)行效果如下撑教,非常容易比對效果。

<kbd>Sobel</kbd>算子算法的優(yōu)點(diǎn)是計(jì)算簡單醉拓,速度快伟姐。

但由于只采用了 2 個方向的模板,只能檢測水平和垂直方向的邊緣亿卤,因此這種算法對于紋理較為復(fù)雜的圖像愤兵,其邊緣檢測效果就不是很理想,該算法認(rèn)為:凡灰度新值大于或等于閾值的像素點(diǎn)時都是邊緣點(diǎn)排吴。這種判斷不是很合理秆乳,會造成邊緣點(diǎn)的誤判,因?yàn)樵S多噪聲點(diǎn)的灰度值也很大钻哩。

Scharr 算子說明與使用

在 <kbd>Sobel</kbd>算子算法函數(shù)中屹堰,如果設(shè)置 <kbd>ksize=-1</kbd> 就會使用 3x3 的 <kbd>Scharr</kbd>濾波器。

它的原理和<kbd>sobel</kbd>算子原理一樣憋槐,只是卷積核不一樣双藕,所以精度會更高一點(diǎn)。

該函數(shù)的原型如下:

# Sobel 算子算法
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
# Scharr 算子算法
dst = cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])

它的參數(shù)與 <kbd>Sobel</kbd> 基本一致阳仔。

測試代碼如下,可以比較兩種算法的差異:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

def scharr_demo(image):
    grad_x = cv.Scharr(image, cv.CV_16S, 1, 0)
    grad_y = cv.Scharr(image, cv.CV_16S, 0, 1)
    gradx = cv.convertScaleAbs(grad_x)
    grady = cv.convertScaleAbs(grad_y)

    # 合并x, y兩個梯度
    dst = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
    return dst

def sobel_demo1(image):
    dst1 = scharr_demo(image)
    grad_x = cv.Sobel(image, cv.CV_16S, 1, 0)
    grad_y = cv.Sobel(image, cv.CV_16S, 0, 1)
    gradx = cv.convertScaleAbs(grad_x)
    grady = cv.convertScaleAbs(grad_y)
    # 合并x, y兩個梯度
    dst = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
    imgs = np.hstack([dst, dst1])
    plt.figure(figsize=(20, 10))
    plt.imshow(imgs, cmap=plt.cm.gray)
    plt.show()


def sobel_demo3(image):
    grad = cv.Sobel(image, cv.CV_16S, 1, 1)
    grad = cv.convertScaleAbs(grad)
    cv.imshow('sobel_demo1_gradient', grad)

def sobel_demo2(image):
    grad_x = cv.Sobel(image, cv.CV_32F, 1, 0)
    grad_y = cv.Sobel(image, cv.CV_32F, 0, 1)
    gradx = cv.convertScaleAbs(grad_x)
    grady = cv.convertScaleAbs(grad_y)
    cv.imshow('sobel_demo2_gradient_x', gradx)
    cv.imshow('sobel_demo2_gradient_y', grady)
    # 合并x, y兩個梯度
    add_image = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
    cv.imshow('sobel_demo2_addWeighted', add_image)

src = cv.imread('./test.jpg', 1)
sobel_demo1(src)
# sobel_demo2(src)
# sobel_demo3(src)
cv.waitKey(0)
cv.destroyAllWindows()
20210202141045854[1].png

<kbd>sobel</kbd>算子和<kbd>scharr</kbd>算子差異

  • <kbd>sobel</kbd>算子系數(shù):[1 2 1] ; <kbd>scharr</kbd>算子[3 10 3] ;
  • <kbd>scharr</kbd>算子要比<kbd>sobel</kbd>算子擁有更高的精確度;
  • <kbd>scharr</kbd>算子可以把比較細(xì)小的邊界也檢測出來近范。

laplacian 算子

<kbd>Laplace</kbd>函數(shù)實(shí)現(xiàn)的方法:先用<kbd>Sobel</kbd> 算子計(jì)算二階 x 和 y 導(dǎo)數(shù)嘶摊,再求和。

應(yīng)用層面评矩,我們先看與一下該函數(shù)的原型:

dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])

無特殊參數(shù)叶堆,可以直接進(jìn)入測試代碼的學(xué)習(xí)。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

image = cv.imread('./test1.png',cv.IMREAD_GRAYSCALE)

laplacian = cv.Laplacian(image, cv.CV_64F)
laplacian = cv.convertScaleAbs(laplacian)

imgs = np.hstack([image, laplacian])

plt.figure(figsize=(20, 10))
plt.imshow(imgs, cmap=plt.cm.gray)
plt.show()

為了測試方便斥杜,我將圖片提前轉(zhuǎn)換為了灰度圖虱颗。

20210202142606578[1].png

拉普拉斯卷積核,也可以自行進(jìn)行定義蔗喂。默認(rèn)使用的是四鄰域 [[0, 1, 0], [1, -4, 1], [0, 1, 0]]忘渔,修改為八鄰域 [[1, 1, 1], [1, -8, 1], [1, 1, 1]]

自定義卷積核代碼如下缰儿,這里用到了我們之前學(xué)習(xí)的 2D 卷積操作:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

image = cv.imread('./test1.png',cv.IMREAD_GRAYSCALE)

# 定義卷積和
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]])
dst = cv.filter2D(image, cv.CV_32F, kernel=kernel)
lapalian = cv.convertScaleAbs(dst)

imgs = np.hstack([image, lapalian])

plt.figure(figsize=(20, 10))
plt.imshow(imgs, cmap=plt.cm.gray)
plt.show()
20210202143035726[1].png

這里還學(xué)習(xí)到一句話畦粮,拉普拉斯對噪聲敏感,會產(chǎn)生雙邊效果乖阵,但是不能檢測出邊的方向宣赔。并且它不直接用于邊的檢測,只起輔助的角色瞪浸,檢測一個像素是在邊的亮的一邊還是暗的一邊利用零跨越儒将,確定邊的位置

梯度簡單來說就是求導(dǎo),就是這關(guān)鍵的求導(dǎo)对蒲,要去學(xué)習(xí)數(shù)學(xué)知識了椅棺,本部分在第一遍學(xué)習(xí)完畢,將會展開學(xué)習(xí)齐蔽。梯度在圖像上表現(xiàn)出來的就是提取圖像的邊緣(無論是橫向的两疚、縱向的、斜方向的等等)含滴,所需要的是一個核模板诱渤,模板的不同結(jié)果也不同√缚觯基于此勺美,上文涉及的算子函數(shù),都能夠用函數(shù)<kbd>cv2.filter2D()</kbd>來表示碑韵,不同的方法給予不同的核模板赡茸,然后演化為不同的算子。

橡皮擦的小節(jié)

希望今天的 1 個小時(今天內(nèi)容有點(diǎn)多祝闻,不一定可以看完)占卧,你有所收獲,我們下篇博客見~

相關(guān)閱讀


技術(shù)專欄

  1. Python 爬蟲 100 例教程,超棒的爬蟲教程华蜒,立即訂閱吧
  2. Python 爬蟲小課辙纬,精彩 9 講

逗趣程序員

  1. 曝光,程序員的 10 個摸魚神器
  2. 5 個無聊 Python 程序叭喜,用 Python 整蠱你的朋友們吧
  3. 10 年老程序員教你甩鍋必殺技贺拣,論【如何優(yōu)雅的甩鍋】
  4. 你想被開除嗎?來看看程序員【離職小技巧】吧

今天是持續(xù)寫作的第 <font color="red">72</font> / 100 天捂蕴。
如果你有想要交流的想法譬涡、技術(shù),歡迎在評論區(qū)留言啥辨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涡匀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子委可,更是在濱河造成了極大的恐慌渊跋,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件着倾,死亡現(xiàn)場離奇詭異拾酝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卡者,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蒿囤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崇决,你說我怎么就攤上這事材诽。” “怎么了恒傻?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵脸侥,是天一觀的道長。 經(jīng)常有香客問我盈厘,道長睁枕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任沸手,我火速辦了婚禮外遇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘契吉。我一直安慰自己跳仿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布捐晶。 她就那樣靜靜地躺著菲语,像睡著了一般妄辩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谨究,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天恩袱,我揣著相機(jī)與錄音泣棋,去河邊找鬼胶哲。 笑死,一個胖子當(dāng)著我的面吹牛潭辈,可吹牛的內(nèi)容都是我干的鸯屿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼把敢,長吁一口氣:“原來是場噩夢啊……” “哼寄摆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起修赞,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤婶恼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后柏副,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勾邦,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年割择,在試婚紗的時候發(fā)現(xiàn)自己被綠了眷篇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡荔泳,死狀恐怖蕉饼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情玛歌,我是刑警寧澤昧港,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站支子,受9級特大地震影響创肥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜译荞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一瓤的、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吞歼,春花似錦圈膏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丈甸。三九已至,卻和暖如春尿褪,著一層夾襖步出監(jiān)牢的瞬間睦擂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工杖玲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顿仇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓摆马,卻偏偏與公主長得像臼闻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子囤采,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容