在 Python 影像處理(OpenCV、skimage)專案中,二值圖像的像素值與資料型態常常讓人踩雷!
本文以「骨架端點偵測」為例,帶你認識這個常見問題、如何避免,以及正確的寫法。
1️⃣ 問題背景
我們常會用**骨架化(skeletonization)**來分析物件形狀,並想找出骨架的端點(endpoints)。端點偵測常見兩種方法:找到的結果是一致的
差別在效率


- filter2D 卷積法(適合 0/1 二值圖)
- for-loop 8鄰域法(適合 0/255 二值圖)
7x5「數字 1」骨架(端點明顯版)
0/1 格式(for filter2d)
0 0 0 0 0
0 0 1 0 0 ← 端點 (1,2)
0 0 1 0 0
0 0 1 0 0
0 0 1 0 0
0 0 1 0 0
0 0 0 0 0
0/255 格式(for loop)
0 0 0 0 0
0 0 255 0 0 ← 端點 (1,2)
0 0 255 0 0
0 0 255 0 0
0 0 255 0 0
0 0 255 0 0
0 0 0 0 0
端點位置
- (1,2) (第二列第三行)
- (5,2) (第六列第三行)
這兩個點就是骨架的端點,因為它們只連接一個骨架點。
視覺化標註
0 0 0 0 0
0 0 x 0 0 ← 端點 (1,2)
0 0 1 0 0
0 0 1 0 0
0 0 1 0 0
0 0 x 0 0 ← 端點 (5,2)
0 0 0 0 0
但如果圖像型態搞錯,就會發生「偵測不到端點」的慘劇!
2️⃣ 常見踩雷情境
範例一:filter2D 只適用 0/1 圖像
def find_skeleton_endpoints_filter2d(skeleton: np.ndarray) -> np.ndarray:
kernel = np.array([[1,1,1], [1,10,1], [1,1,1]], dtype=np.uint8)
filtered = cv2.filter2D(skeleton, -1, kernel)
endpoints = (filtered == 11).astype(np.uint8)
return endpoints
錯誤用法:
skeleton = skeletonize(binary > 0).astype(np.uint8) * 255 # 這時像素值是 0/255
endpoints = find_skeleton_endpoints_filter2d(skeleton) # ❌ 找不到端點
正確用法:
skeleton = skeletonize(binary > 0).astype(np.uint8) # 這時像素值是 0/1
endpoints = find_skeleton_endpoints_filter2d(skeleton) # ✅ 正常偵測
範例二:for-loop 8鄰域法適用 0/255
def find_skeleton_endpoints_loop(skeleton: np.ndarray) -> list:
h, w = skeleton.shape
endpoints = []
for y in range(1, h - 1):
for x in range(1, w - 1):
if skeleton[y, x] == 255:
neighbors = skeleton[y - 1:y + 2, x - 1:x + 2]
num_neighbors = np.sum(neighbors == 255) - 1
if num_neighbors == 1:
endpoints.append((y, x))
return endpoints
這裡預設骨架點是 255,如果你傳進去的是 0/1 圖像,會找不到端點!
3️⃣ 解決方法與最佳實踐
✅ 1. 明確轉換像素值
- filter2D 方法前,請轉成 0/1:
skeleton_01 = (skeleton > 0).astype(np.uint8)
- for-loop 方法前,請轉成 0/255:
skeleton_255 = (skeleton > 0).astype(np.uint8) * 255
✅ 2. 明確註記每個函式的輸入格式
- 在每個函式 docstring 註明「輸入需為 0/1 或 0/255」。
- 在主流程中,統一管理資料型態與像素值。
✅ 3. 善用 assert 檢查
- 在函式開頭加上 assert,避免型態錯誤:
assert np.max(skeleton) in (1, 255), "骨架像素值應為 1 或 255"
4️⃣ 完整範例:兩種端點偵測都正確
# 取得骨架
skeleton_01 = skeletonize(binary_block > 0).astype(np.uint8) # 0/1
skeleton_255 = skeleton_01 * 255 # 0/255
# filter2D 法
endpoints_mask = find_skeleton_endpoints_filter2d(skeleton_01)
# for-loop 法
endpoints_loop = find_skeleton_endpoints_loop(skeleton_255)
5️⃣ 實戰小結
- filter2D 法:只適用 0/1 圖像
- for-loop 法:只適用 0/255 圖像
- 型態不一致會導致偵測失敗!
- 養成習慣:每次處理二值圖像都明確轉型、註明型態
6️⃣ 常見問答
Q:為什麼 OpenCV 有時會自動把 0/1 轉成 0/255?
A:OpenCV 的大多數函式預設處理 8-bit 圖像(0~255),但 skimage 等科學套件常用 0/1。兩者混用時,務必手動轉型。
Q:有沒有一個「萬用端點偵測」?
A:建議根據資料型態選擇方法,或在函式內自動轉型。
7️⃣ 結語
資料型態與像素值的混用,是影像處理最常見的 bug 來源之一。
每次處理二值圖像,請務必檢查型態與像素值!
這樣就能避免「端點偵測不到」這種隱藏 bug,讓你的程式更穩定、易維護!