1、簡介
圖像的細化主要是針對二值圖而言。
所謂骨架,可以理解為圖像的中軸,一個長方形的骨架,是它的長方向上的中軸線。
圓的骨架是它的圓心,直線的骨架是它自身,孤立點的骨架也是自身。
2、骨架的獲取
骨架的獲取主要有兩種方法:
(1)基於烈火模擬
設想在同一時刻,將目標的邊緣線都點燃,火的前沿以勻速向內部蔓延,當前沿相交時火焰熄滅,火焰熄滅點的結合就是骨架。
(2)基於最大圓盤
目標的骨架是由目標內所有內切圓盤的圓心組成。
我們來看看典型的圓形的骨架(用粗線表示)。
細化的算法有很多種,但比較常用的算法是查表法。
細化是從原來的圖中去掉一些點,但仍要保持原來的形狀。
實際上是保持原圖的骨架。
判斷一個點是否能去掉是以8個相鄰點(八連通)的情況來作為判斷依據的,具體判斷依據為:
1.內部點不能刪除
2.鼓勵點不能刪除
3.直線端點不能刪除
4.如果P是邊界點,去掉P后,如果連通分量不增加,則P可刪除
看看上面那些點,就是3*3矩陣中的中心點:
第一個點不能去除,因為它是內部點
第二個點不能去除,它也是內部點
第三個點不能去除,刪除后會使原來相連的部分斷開
第四個點可以去除,這個點是骨架
第五個點不可以去除,它是直線的端點
第六個點不可以去除,它是直線的端點
對於所有的這樣的點,我們可以做出一張表,來判斷這樣的點能不能刪除
我們對於黑色的像素點,對於它周圍的8個點,我們賦予不同的價值,若周圍是黑色,我們認為其價值為0,為白色則取九宮格中對應的價值。對於前面那幅圖中第一個點,也周圍的點都是黑色,所以它的總價值是0,對應於索引表的第一項,前面那幅圖中第二點,它周圍有三個白色點,它的總價值為1+4+32=37,對應於索引表中第三十八項。
我們用這種方法,把所有點的情況映射到0-255的索引表中
我們掃描原圖,對於黑色的像素點,根據周圍八點的情況計算它的價值,然后查看索引表中對應項來決定是否要保留這一點。
3、代碼實現
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : Ma Yi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2020-04-24 # Name : test02 # Software : PyCharm # Note : 骨架抽取 import cv2 import copy # 映射表 g_array = [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0] def thin(img): """ 細化函數,根據算法,運算出中心點的對應值 :param img: 需要細化的圖片(經過二值化處理的圖片) :return: """ h, w = img.shape i_thin = copy.deepcopy(img) for i in range(h): for j in range(w): if img[i, j] == 0: a = [1] * 9 for k in range(3): for l in range(3): if -1 < (i - 1 + k) < h and -1 < (j - 1 + l) < w and i_thin[i - 1 + k, j - 1 + l] == 0: a[k * 3 + l] = 0 i_sum = a[0] * 1 + a[1] * 2 + a[2] * 4 + a[3] * 8 + a[5] * 16 + a[6] * 32 + a[7] * 64 + a[8] * 128 i_thin[i, j] = g_array[i_sum] * 255 return i_thin def to_binary(img): """ 二值化函數,閾值根據圖片的昏暗程序自己設定 :param img: 需要二值化的圖片 :return: """ w, h = img.shape i_two = copy.deepcopy(img) for i in range(w): for j in range(h): if img[i, j] < 200: i_two[i, j] = 0 else: i_two[i, j] = 255 return i_two # 入口函數 if __name__ == '__main__': # 讀取圖片,並顯示 image = cv2.imread("1.jpg", 0) img_binary = to_binary(image) img_thin = thin(img_binary) cv2.imshow("image", image) cv2.imshow("img_binary", img_binary) cv2.imshow("img_thin", img_thin) cv2.waitKey(0)
效果不是很好,來看一個最簡單的事例:
按照前面的分析,我們應該得到一條豎着的線,但實際上我們得到了一條橫線。
我們在從上到下,從左到右掃描的時候,遇到第一個點,我們查表可以刪除,遇到第二個點,我們查表也可以刪除,整個第一行都可以刪除。
於是我們查看第二行時,和第一行一樣,它也被整個刪除了。這樣一直到最后一行,於是我們得到最后的結果是一條直線。
解決的辦法是:
在每行水平掃描的過程中,先判斷每一點的左右鄰居,如果都是黑點,則該點不做處理。另外,如果某個黑點被刪除了,則跳過它的右鄰居,處理下一點。對矩形這樣做完一遍,水平方向會減少兩像素。然后我們再改垂直方向掃描,方法一樣。
這樣做一次水平掃描和垂直掃描,原圖會“瘦”一圈,多次重復上面的步驟,直到圖形不在變化為止。
這一改進讓算法的復雜度的運行時間增大一個數量級:
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : Ma Yi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2020-04-24 # Name : test02 # Software : PyCharm # Note : 圖像抽取骨架 import cv2 import copy # 映射表 l_array = [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0] def v_thin(img): """ 細化函數,根據算法,運算出中心點的對應值 :param img: 需要細化的圖片(經過二值化處理的圖片) :param array: 映射矩陣array :return: """ h, w = img.shape i_next = 1 for i in range(h): for j in range(w): if i_next == 0: i_next = 1 else: i_m = int(img[i, j - 1]) + int(img[i, j]) + int(img[i, j + 1]) if 0 < j < w - 1 else 1 if img[i, j] == 0 and i_m != 0: a = [0] * 9 for k in range(3): for l in range(3): if -1 < (i - 1 + k) < h and -1 < (j - 1 + l) < w and img[i - 1 + k, j - 1 + l] == 255: a[k * 3 + l] = 1 i_sum = a[0] * 1 + a[1] * 2 + a[2] * 4 + a[3] * 8 + a[5] * 16 + a[6] * 32 + a[7] * 64 + a[8] * 128 img[i, j] = l_array[i_sum] * 255 if l_array[i_sum] == 1: i_next = 0 def h_thin(img): """ 細化函數,根據算法,運算出中心點的對應值 :param img: 需要細化的圖片(經過二值化處理的圖片) :param array: 映射矩陣array :return: """ h, w = img.shape i_next = 1 for j in range(w): for i in range(h): if i_next == 0: i_next = 1 else: i_m = int(img[i -1, j]) + int(img[i, j]) + int(img[i + 1, j]) if 0 < i < h - 1 else 1 if img[i, j] == 0 and i_m != 0: a = [0] * 9 for k in range(3): for l in range(3): if -1 < (i - 1 + k) < h and -1 < (j - 1 + l) < w and img[i - 1 + k, j - 1 + l] == 255: a[k * 3 + l] = 1 i_sum = a[0] * 1 + a[1] * 2 + a[2] * 4 + a[3] * 8 + a[5] * 16 + a[6] * 32 + a[7] * 64 + a[8] * 128 img[i, j] = l_array[i_sum] * 255 if l_array[i_sum] == 1: i_next = 0 def xi_hua(img, num=10): for i in range(num): v_thin(img) h_thin(img) return img def to_binary(img): """ 二值化函數,閾值根據圖片的昏暗程序自己設定 :param img: 需要二值化的圖片 :return: """ w, h = img.shape i_two = copy.deepcopy(img) for i in range(w): for j in range(h): if img[i, j] < 200: i_two[i, j] = 0 else: i_two[i, j] = 255 return i_two # 入口函數 if __name__ == '__main__': # 讀取圖片,並顯示 image = cv2.imread("1.jpg", 0) img_binary = to_binary(image) cv2.imshow("image", image) cv2.imshow("img_binary", img_binary) img_thin = xi_hua(img_binary) cv2.imshow("img_thin", img_thin) cv2.waitKey(0)
運行的效果: