什么是骨干提取(教案)
代码总体框架
- 骨干提取Thinning:
- 骨干提取主函数:SequentialThinning。
- 自研骨干提取算法:solve,配合三个检查函数“CheckEdge,CheckConnectivity和CheckEndPoint”。
- Zhang-suen骨干提取算法:zhangsuen_solve,配合两个检查函数“UpDownCheck和LeftRightCheck”。
- 特征点提取Feature Extraction:
- FeatureExtraction:配合四个检查函数。新增Check_3BranchPoint和Check_4BranchPoint。
- 提取后图像优化Optimization:
- removeLinesUseFeatureExtraction: 利用特征点进行优化,配合ColorPixelDetect。
1 | class LWCThinning{ |
时间复杂度上的考虑
本代码中除了对原始图像的遍历使用了双重for循环,在其他方法中对3x3的卷积核进行处理时,均没有使用for循环。这是出于时间复杂度上的考量。3x3的卷积核很小,一共也才9个元素,如果再用一个常规的双重for循环进行处理,时间复杂度将会增加O(n^2),与外部遍历结合起来就是O(n^4),处理大图片将会非常缓慢;但相对地,如果将其展开书写,将时间复杂度降低为O(1)的情况下代码也不会变得过于冗杂。
三次判断
总体思路:三个check方法返回0或1。在solve()中调用这些check,三次判断全1才会执行像素的删除。
- return 0: 该项check不通过,不可以删除。
- return 1: 该项check通过,可以删除。
连续性判断 CheckConnectivity
先说结论:判断中心注目像素周围一圈的八个像素,从一个节点开始顺时针旋转遍历。如果像素值“从黑转变为白”的次数超过1次,那么就是不连续的。(另外,前提是周围一圈有两个及以上的黑色像素。如果只有一个黑像素,那么这个中心像素就是端点end-point)
我们可以通过画图来探索这个规律:


- ①很好地展示了斜线连续性被破坏的情况。
- ③很好地展示了直线连续性被破坏的情况。
- ⑤展示了两条直线交点处连续性被破坏的情况。
- ②中即便删除了中心像素,其连续性没有被破坏。
观察上述所有破快连续性的情况,我们发现他们周围一圈像素从黑变白的转变次数都大于一。(图中红色箭头和数字)
1 | int LWCThinning::CheckConnectivity(Mat& image3x3){ |
端点判断 CheckEndPoint
不难发现,如果中心注目像素的周围只存在一个黑色像素,那么它就是端点end-point。我们不能删除端点像素。

1 | int LWCThinning::CheckEndPoint(Mat& image3x3){ |
孤立点判断 CheckEdge
这个是最简单的,只需要查看周围一圈是否有黑色像素即可。它的深层含义是:画面上孤立的黑色像素点不能删去,因为他完整地表征着一部分的画面信息。
1 | int LWCThinning::CheckEdge(Mat& image3x3){ |
slove()主方法 和 SequentialThinning()接口方法
- 使用3x3的卷积核遍历原始图像。具体而言,当检测到3x3的中心是黑色像素时,从原图中抽取这个3x3的正方形像素区域构成一个新图像kernel_nowscanning,然后进行三次check。如果check通过,则将中心的黑色像素删除(即变为白色)。
1 | Mat LWCThinning::solve(Mat& Image, int iter_time){ |
- SequentialThinning()接口方法可以被用户(主函数)调用,对给定的图样进行N次骨干提取,将结果保存在result路径下。
1 | void LWCThinning::SequentialThinning(Mat& Image, int num_iterations){ |
初步测试结果
ABCD英文字符
- 字母A:

- 字母C:

- 字母E:

复杂的彩色图样
首先将彩色图片读取为灰度图,然后进行二值化处理,最后再调用SequentialThinning。
1 | int main(){ |
测试原图与处理后图像如下:

- 原图:

- 转化为二值化图像:

- 我们把处理后的图像放大观察,可以看到骨干提取的一些细节。比如衣服的领带和眼睛等等:

问题:对于复杂图像,处理后的图像中存在很多垂直水平面的直线(噪声),如何去除?
优化一:“Zhang-Suen算法”
问题就出在我们对原始图像的处理顺序上。我们总是从上往下、从左往右地进行逐行扫描,这种顺序性可能导致水平或垂直方向的像素被优先移除或保留,从而产生水平或垂直的直线。这直接导致了很多倾斜的骨干被强制地转化为了垂直的直线。
从上方测试图“字母A”可以看出这种状况,并且在彩色测试图中也很明显能发现许多垂直直线。
Zhang-Suen算法
与先前自己实现的算法相比,其实就是check部分的逻辑不同。Zhang-Suen算法会先后进行两次遍历,一次是UpDown一次是LeftRight。
1 | //zhangsuen ----------------------------------------------------- |
优化结果
貌似没有解决垂直直线较多的问题…
优化二:利用“特性点提取”消除直线
刚刚在做“特征点提取”的工作时,突然来了灵感:我发现这些多余的直线都是由非特征点组成的,亦或是原理特征点的。换言之,我们只要提取了骨架图的特征点,然后去除远离特征点的点即可!
特征点提取

这是特征点提取后的图片,不同的特征点标上了不同的颜色:
- 孤立点 isolated point:红色。
- 端点 end point:绿色。
- 3分歧点:蓝色。图片中的阶梯状斜线属于这一类。
- 4分歧点:黄色。十字路口。

我们可以放大图片观察一下细节:(由于4分歧点的黄色标注看的不明显,我在边上放了一双眼睛。)

特征点提取的核心代码:
1 | int LWCThinning::Check_3BranchPoint(Mat& image3x3){ |
利用“特性点提取”消除直线
仍然使用3x3的扫描核kernel(中心像素为注目像素),如果kernel内不存在彩色像素,即不存在特征点,那么就删去这个中心像素。
1 | Mat LWCThinning::removeLinesUseFeatureExtraction(Mat& Image){ |
优化结果
动漫人物图片:ShinonomeEnana
- 原图:

- 初步骨架提取:

- 特征点提取:

- 利用特征点去直线:

环境景色图片:ひびきの南
- 原图:

- 初步骨架提取:

- 特征点提取:

- 利用特征点去直线:

包含大量文字的图片:日本年号对照表
- 原图:

- 初步骨架提取:

- 特征点提取:

我们发现对文字进行处理后效果已经很好了,因此无需再继续优化。