
在工业视觉检测中,测量弯曲物体(绳索、金属条、软管)的宽度是常见需求。medial_axis 是 scikit-image 的核心算法——从二值图像中提取物体的中心骨架,同时返回每个骨架点到边界的距离。
目标:在 Java 项目中使用 medial_axis,但 JavaCV 没有这个函数。解决方案——一比一复刻算法。
本文展示如何用 Python 验证算法原理,再用 Java 完整复刻,最终实现跨语言一致的骨架分析能力。
medial_axis 的核心思想:骨架 = 距离变换的脊线。

medial_axis 算法流程

查找表核心:512 种 3x3 邻域配置的预计算结果
scikit-image 的 medial_axis 只需几行:
from skimage.morphology import medial_axis
import numpy as np, cv2
mask = cv2.imread('test_mask.png', cv2.IMREAD_GRAYSCALE)
_, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
skel, dist = medial_axis(mask > 0, return_distance=True)
widths = dist[skel] * 2 # 宽度 = 2 x 距离测试三种典型物体:

scikit-image 的 medial_axis 是 Cython 实现,JavaCV 没有等价函数。但算法是纯逻辑——距离变换 + 查找表 + 排序删除,完全可用 Java 复刻。
用 Python 预计算查找表,导入 Java:
from scipy import ndimage as ndi
def _pattern_of(index):
return np.array([[index & 2**i for i in range(3)],
[index & 2**(i+3) for i in range(3)],
[index & 2**(i+6) for i in range(3)]], bool)
cif = (np.arange(512) & 2**4).astype(bool)
table = cif & (ndi_label_changed | fewer_than_3_neighbors)
# 生成 Java boolean 数组// 512 项查找表(从 Python 导入,完全一致)// 512 项查找表(从 Python 导入,完全一致)
static finalboolean[] TABLE = {
false,false,false,false,false,false,false,false,
false,false,false,false,false,false,false,false,
true,true,true,false,true,true,false,false,
true,false,false,false,true,true,false,false,
// ... 共 512 项
};// 对每个前景像素用查找表判断保留/删除
for (int i = 0; i < fgCount; i++) {
int y = py[order[i]], x = px[order[i]];
if (maskData[y*cols+x] == 0) continue;
int idx = computeIndex(maskData, y, x, cols, rows);
if (!TABLE[idx]) {
maskData[y * cols + x] = 0; // 删除
}
}验证通过:Java 512 项查找表与 Python 0 不匹配(完全一致)。MIN=2.0px 是端点处的自然值,与 Python medial_axis 输出一致。

scikit-image 的 medial_axis 虽然只有几行 Python 代码,但背后是精心设计的 512 项查找表 + 距离变换 + 排序删除。通过一比一复刻,我们在 Java 中实现了完全一致的输出。(仔细看的话其实还是有些差距的)