Python - 圖像的細化(骨架抽取)


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)

運行的效果:

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM