当前位置: 首页 > news >正文

【IP101】图像“瘦身魔法“详解:从基础细化到Zhang-Suen、Hilditch算法与中轴变换的完整代码实现

图像细化算法详解 🎨

欢迎来到图像处理的"瘦身工作室"!在这里,我们将学习如何像一位细心的雕刻家一样,将图像中的目标对象"瘦身"为单像素宽度的骨架。这个过程就像把一条粗线条逐渐"削瘦"成一条细线,同时保持其拓扑结构不变。让我们一起探索这个神奇的"瘦身魔法"吧!🚀

📚 目录

  • 1. 算法原理
  • 2. 应用场景
  • 3. 基本细化算法
  • 4. Hilditch细化算法
  • 5. Zhang-Suen细化算法
  • 6. 骨架提取
  • 7. 中轴变换
  • 8. 优化建议

1. 算法原理

图像细化的核心思想是通过迭代删除目标边缘像素来获得骨架。这个过程需要保证:

原则说明重要性
保持连通性不能破坏目标的连接关系⭐⭐⭐⭐⭐
适度细化不能过度腐蚀导致目标消失⭐⭐⭐⭐
中心定位骨架应该位于物体的中心位置⭐⭐⭐⭐

就像雕刻家精心雕琢木头一样,我们需要小心翼翼地"削掉"边缘像素,直到得到理想的骨架。🎯

2. 应用场景

图像细化算法在多个领域都有重要应用:🌟

应用领域具体应用技术要点
📝 字符识别手写字符细化特征提取和识别
👆 指纹识别指纹骨架提取指纹匹配和识别
🛣️ 道路提取道路网络提取地图制作和导航
🏥 医学图像血管网络分析疾病诊断辅助
🎯 模式识别目标形状简化特征匹配

3. 基本细化算法

3.1 基本原理

最基本的细化算法采用迭代的方式,每次迭代删除满足特定条件的边界点。判断一个点是否可以删除通常需要考虑其8邻域像素的分布情况。

💡 数学小贴士:像素邻域编号
P 9 P 2 P 3 P 8 P 1 P 4 P 7 P 6 P 5 \begin{matrix} P_9 & P_2 & P_3 \\ P_8 & P_1 & P_4 \\ P_7 & P_6 & P_5 \end{matrix} P9P8P7P2P1P6P3P4P5

每次迭代必须满足以下条件:

条件类型具体条件作用
边界点条件当前点是边界点确保只处理边缘
连通性条件2 ≤ B(P1) ≤ 6保持连通性
连续性条件A(P1) = 1避免过度细化
删除条件P2 × P4 × P6 = 0 且 P4 × P6 × P8 = 0保持结构完整

3.2 C++实现

void basic_thinning(const Mat& src, Mat& dst) {CV_Assert(!src.empty() && src.type() == CV_8UC1);src.copyTo(dst);bool has_changed;do {has_changed = false;Mat tmp = dst.clone();#pragma omp parallel for collapse(2)for (int y = 1; y < dst.rows - 1; y++) {for (int x = 1; x < dst.cols - 1; x++) {if (tmp.at<uchar>(y, x) == 0) continue;// 检查是否为边界点if (!is_boundary(tmp, y, x)) continue;// 计算P2到P9的值int p2 = tmp.at<uchar>(y-1, x) > 0;int p3 = tmp.at<uchar>(y-1, x+1) > 0;int p4 = tmp.at<uchar>(y, x+1) > 0;int p5 = tmp.at<uchar>(y+1, x+1) > 0;int p6 = tmp.at<uchar>(y+1, x) > 0;int p7 = tmp.at<uchar>(y+1, x-1) > 0;int p8 = tmp.at<uchar>(y, x-1) > 0;int p9 = tmp.at<uchar>(y-1, x-1) > 0;// 条件1:2 <= B(P1) <= 6int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;if (B < 2 || B > 6) continue;// 条件2:A(P1) = 1int A = count_transitions(tmp, y, x);if (A != 1) continue;// 条件3和4if ((p2 * p4 * p6 == 0) && (p4 * p6 * p8 == 0)) {dst.at<uchar>(y, x) = 0;has_changed = true;}}}} while (has_changed);
}

3.3 Python实现

def basic_thinning(img_path):"""使用基本细化算法进行图像细化"""# 读取图像img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)if img is None:raise ValueError(f"无法读取图像: {img_path}")# 二值化_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# 转换为0和1格式skeleton = binary.copy() // 255changing = Truedef is_boundary(img, y, x):if img[y, x] == 0:return Falsefor dy in [-1, 0, 1]:for dx in [-1, 0, 1]:if dy == 0 and dx == 0:continueny, nx = y + dy, x + dxif 0 <= ny < img.shape[0] and 0 <= nx < img.shape[1]:if img[ny, nx] == 0:return Truereturn Falsedef count_transitions(img, y, x):values = [img[y-1, x],   # P2img[y-1, x+1], # P3img[y, x+1],   # P4img[y+1, x+1], # P5img[y+1, x],   # P6img[y+1, x-1], # P7img[y, x-1],   # P8img[y-1, x-1], # P9img[y-1, x]    # P2]count = 0for i in range(len(values)-1):if values[i] == 0 and values[i+1] == 1:count += 1return countwhile changing:changing = Falsetemp = skeleton.copy()for y in range(1, skeleton.shape[0]-1):for x in range(1, skeleton.shape[1]-1):if temp[y, x] == 0:continueif not is_boundary(temp, y, x):continue# 计算P2到P9的值p2, p3, p4, p5, p6, p7, p8, p9 = (temp[y-1, x], temp[y-1, x+1], temp[y, x+1], temp[y+1, x+1],temp[y+1, x], temp[y+1, x-1], temp[y, x-1], temp[y-1, x-1])# 条件1:2 <= B(P1) <= 6B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9if B < 2 or B > 6:continue# 条件2:A(P1) = 1A = count_transitions(temp, y, x)if A != 1:continue# 条件3和4if (p2 * p4 * p6 == 0) and (p4 * p6 * p8 == 0):skeleton[y, x] = 0changing = True# 转换回0-255格式result = skeleton.astype(np.uint8) * 255return result

4. Hilditch细化算法

4.1 基本原理

Hilditch细化算法是一种改进的细化算法,根据以下条件判断是否删除当前点:

  1. 连通性条件:2 ≤ B(P1) ≤ 6
  2. 连续性条件:A(P1) = 1
  3. 端点条件:P2 + P4 + P6 + P8 ≥ 1
  4. 删除条件:P2 × P4 × P6 = 0 且 P4 × P6 × P8 = 0

4.2 C++实现

void hilditch_thinning(const Mat& src, Mat& dst) {CV_Assert(!src.empty() && src.type() == CV_8UC1);src.copyTo(dst);bool has_changed;do {has_changed = false;Mat tmp = dst.clone();#pragma omp parallel for collapse(2)for (int y = 1; y < dst.rows - 1; y++) {for (int x = 1; x < dst.cols - 1; x++) {if (tmp.at<uchar>(y, x) == 0) continue;// 计算Hilditch算法条件int B = count_nonzero_neighbors(tmp, y, x);if (B < 2 || B > 6) continue;int A = count_transitions(tmp, y, x);if (A != 1) continue;// 计算连通性int conn = 0;if (tmp.at<uchar>(y-1, x) > 0 && tmp.at<uchar>(y-1, x+1) > 0) conn++;if (tmp.at<uchar>(y-1, x+1) > 0 && tmp.at<uchar>(y, x+1) > 0) conn++;if (tmp.at<uchar>(y, x+1) > 0 && tmp.at<uchar>(y+1, x+1) > 0) conn++;if (tmp.at<uchar>(y+1, x+1) > 0 && tmp.at<uchar>(y+1, x) > 0) conn++;if (tmp.at<uchar>(y+1, x) > 0 && tmp.at<uchar>(y+1, x-1) > 0) conn++;if (tmp.at<uchar>(y+1, x-1) > 0 && tmp.at<uchar>(y, x-1) > 0) conn++;if (tmp.at<uchar>(y, x-1) > 0 && tmp.at<uchar>(y-1, x-1) > 0) conn++;if (tmp.at<uchar>(y-1, x-1) > 0 && tmp.at<uchar>(y-1, x) > 0) conn++;if (conn == 1) {dst.at<uchar>(y, x) = 0;has_changed = true;}}}} while (has_changed);
}

4.3 Python实现

def hilditch_thinning(img_path):"""使用Hilditch算法进行图像细化"""# 读取图像img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)if img is None:raise ValueError(f"无法读取图像: {img_path}")# 二值化_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# 转换为0和1格式skeleton = binary.copy() // 255changing = Truedef count_nonzero_neighbors(img, y, x):count = 0for dy in [-1, 0, 1]:for dx in [-1, 0, 1]:if dy == 0 and dx == 0:continueny, nx = y + dy, x + dxif 0 <= ny < img.shape[0] and 0 <= nx < img.shape[1]:if img[ny, nx] > 0:count += 1return countdef count_transitions(img, y, x):values = [img[y-1, x],   # P2img[y-1, x+1], # P3img[y, x+1],   # P4img[y+1, x+1], # P5img[y+1, x],   # P6img[y+1, x-1], # P7img[y, x-1],   # P8img[y-1, x-1], # P9img[y-1, x]    # P2]count = 0for i in range(len(values)-1):if values[i] == 0 and values[i+1] == 1:count += 1return countwhile changing:changing = Falsetemp = skeleton.copy()for y in range(1, skeleton.shape[0]-1):for x in range(1, skeleton.shape[1]-1):if temp[y, x] == 0:continue# 计算Hilditch算法条件B = count_nonzero_neighbors(temp, y, x)if B < 2 or B > 6:continueA = count_transitions(temp, y, x)if A != 1:continue# 计算连通性conn = 0if temp[y-1, x] > 0 and temp[y-1, x+1] > 0: conn += 1if temp[y-1, x+1] > 0 and temp[y, x+1] > 0: conn += 1if temp[y, x+1] > 0 and temp[y+1, x+1] > 0: conn += 1if temp[y+1, x+1] > 0 and temp[y+1, x] > 0: conn += 1if temp[y+1, x] > 0 and temp[y+1, x-1] > 0: conn += 1if temp[y+1, x-1] > 0 and temp[y, x-1] > 0: conn += 1if temp[y, x-1] > 0 and temp[y-1, x-1] > 0: conn += 1if temp[y-1, x-1] > 0 and temp[y-1, x] > 0: conn += 1if conn == 1:skeleton[y, x] = 0changing = True# 转换回0-255格式result = skeleton.astype(np.uint8) * 255return result

5. Zhang-Suen细化算法

5.1 基本原理

Zhang-Suen细化算法是一种改进的细化算法,通过两次迭代来细化图像:

第一次迭代条件:

  1. 2 ≤ B(P1) ≤ 6
  2. A(P1) = 1
  3. P2 × P4 × P6 = 0
  4. P4 × P6 × P8 = 0

第二次迭代条件:

  1. 2 ≤ B(P1) ≤ 6
  2. A(P1) = 1
  3. P2 × P4 × P8 = 0
  4. P2 × P6 × P8 = 0

5.2 C++实现

void zhang_suen_thinning(const Mat& src, Mat& dst) {CV_Assert(!src.empty() && src.type() == CV_8UC1);src.copyTo(dst);bool has_changed;do {has_changed = false;// 第一次迭代Mat tmp = dst.clone();#pragma omp parallel for collapse(2)for (int y = 1; y < dst.rows - 1; y++) {for (int x = 1; x < dst.cols - 1; x++) {if (tmp.at<uchar>(y, x) == 0) continue;int B = count_nonzero_neighbors(tmp, y, x);if (B < 2 || B > 6) continue;int A = count_transitions(tmp, y, x);if (A != 1) continue;// Zhang-Suen条件1if (tmp.at<uchar>(y-1, x) * tmp.at<uchar>(y, x+1) * tmp.at<uchar>(y+1, x) == 0 &&tmp.at<uchar>(y, x+1) * tmp.at<uchar>(y+1, x) * tmp.at<uchar>(y, x-1) == 0) {dst.at<uchar>(y, x) = 0;has_changed = true;}}}// 第二次迭代tmp = dst.clone();#pragma omp parallel for collapse(2)for (int y = 1; y < dst.rows - 1; y++) {for (int x = 1; x < dst.cols - 1; x++) {if (tmp.at<uchar>(y, x) == 0) continue;int B = count_nonzero_neighbors(tmp, y, x);if (B < 2 || B > 6) continue;int A = count_transitions(tmp, y, x);if (A != 1) continue;// Zhang-Suen条件2if (tmp.at<uchar>(y-1, x) * tmp.at<uchar>(y, x+1) * tmp.at<uchar>(y, x-1) == 0 &&tmp.at<uchar>(y-1, x) * tmp.at<uchar>(y+1, x) * tmp.at<uchar>(y, x-1) == 0) {dst.at<uchar>(y, x) = 0;has_changed = true;}}}} while (has_changed);
}

5.3 Python实现

def zhang_suen_thinning(img_path):"""使用Zhang-Suen算法进行图像细化"""# 读取图像img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)if img is None:raise ValueError(f"无法读取图像: {img_path}")# 二值化_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# 转换为0和1格式skeleton = binary.copy() // 255def zhang_suen_iteration(img, iter_type):changing = Falserows, cols = img.shape# 创建标记数组markers = np.zeros_like(img)for i in range(1, rows-1):for j in range(1, cols-1):if img[i,j] == 1:# 获取8邻域p2,p3,p4,p5,p6,p7,p8,p9 = (img[i-1,j], img[i-1,j+1], img[i,j+1],img[i+1,j+1], img[i+1,j], img[i+1,j-1],img[i,j-1], img[i-1,j-1])# 计算条件A = 0for k in range(len([p2,p3,p4,p5,p6,p7,p8,p9])-1):if [p2,p3,p4,p5,p6,p7,p8,p9][k] == 0 and [p2,p3,p4,p5,p6,p7,p8,p9][k+1] == 1:A += 1B = sum([p2,p3,p4,p5,p6,p7,p8,p9])m1 = p2 * p4 * p6 if iter_type == 0 else p2 * p4 * p8m2 = p4 * p6 * p8 if iter_type == 0 else p2 * p6 * p8if (A == 1 and B >= 2 and B <= 6 and m1 == 0 and m2 == 0):markers[i,j] = 1changing = Trueimg[markers == 1] = 0return img, changing# 迭代细化changing = Truewhile changing:skeleton, changing1 = zhang_suen_iteration(skeleton, 0)skeleton, changing2 = zhang_suen_iteration(skeleton, 1)changing = changing1 or changing2# 转换回0-255格式result = skeleton.astype(np.uint8) * 255return result

6. 骨架提取

6.1 基本原理

骨架提取是一种特殊的细化算法,它通过形态学操作或距离变换来提取图像的中心线。骨架具有以下特点:

  1. 保持原图像的拓扑结构
  2. 位于物体的中心位置
  3. 宽度为单像素
  4. 保持物体的连通性

6.2 C++实现

void skeleton_extraction(const Mat& src, Mat& dst) {CV_Assert(!src.empty() && src.type() == CV_8UC1);// 使用距离变换和局部最大值提取骨架Mat dist;distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE);dst = Mat::zeros(src.size(), CV_8UC1);#pragma omp parallel for collapse(2)for (int y = 1; y < src.rows - 1; y++) {for (int x = 1; x < src.cols - 1; x++) {if (src.at<uchar>(y, x) == 0) continue;// 检查是否为局部最大值float center = dist.at<float>(y, x);bool is_local_max = true;for (int dy = -1; dy <= 1 && is_local_max; dy++) {for (int dx = -1; dx <= 1; dx++) {if (dy == 0 && dx == 0) continue;if (dist.at<float>(y+dy, x+dx) > center) {is_local_max = false;break;}}}if (is_local_max) {dst.at<uchar>(y, x) = 255;}}}
}

6.3 Python实现

def skeleton_extraction(img_path):"""使用形态学操作提取图像骨架"""# 读取图像img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)if img is None:raise ValueError(f"无法读取图像: {img_path}")# 二值化_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# 创建结构元素kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3))# 初始化骨架图像skeleton = np.zeros_like(binary)# 迭代提取骨架while True:# 形态学开运算eroded = cv2.erode(binary, kernel)opened = cv2.dilate(eroded, kernel)# 提取骨架点temp = cv2.subtract(binary, opened)# 更新骨架和二值图像skeleton = cv2.bitwise_or(skeleton, temp)binary = eroded.copy()# 当图像为空时停止迭代if cv2.countNonZero(binary) == 0:breakreturn skeleton

7. 中轴变换

7.1 基本原理

中轴变换(Medial Axis Transform, MAT)是一种将二维形状转换为骨架的技术,其特点是:

  1. 骨架上的每个点都是到边界的最远点
  2. 保持了物体的拓扑结构
  3. 可以用于形状分析和描述
  4. 常用于计算机视觉和模式识别

7.2 C++实现

void medial_axis_transform(const Mat& src, Mat& dst, Mat& dist_transform) {CV_Assert(!src.empty() && src.type() == CV_8UC1);// 计算距离变换distanceTransform(src, dist_transform, DIST_L2, DIST_MASK_PRECISE);// 提取中轴dst = Mat::zeros(src.size(), CV_8UC1);#pragma omp parallel forfor (int y = 1; y < src.rows - 1; y++) {for (int x = 1; x < src.cols - 1; x++) {if (src.at<uchar>(y, x) == 0) continue;float center = dist_transform.at<float>(y, x);bool is_medial_axis = false;// 检查梯度方向for (int dy = -1; dy <= 1; dy++) {for (int dx = -1; dx <= 1; dx++) {if (dy == 0 && dx == 0) continue;float neighbor = dist_transform.at<float>(y+dy, x+dx);// 如果在相反方向上有相同的距离值,则为中轴点if (abs(center - neighbor) < 1e-5) {int opposite_y = y - dy;int opposite_x = x - dx;if (opposite_y >= 0 && opposite_y < src.rows &&opposite_x >= 0 && opposite_x < src.cols) {float opposite = dist_transform.at<float>(opposite_y, opposite_x);if (abs(center - opposite) < 1e-5) {is_medial_axis = true;break;}}}}if (is_medial_axis) break;}if (is_medial_axis) {dst.at<uchar>(y, x) = 255;}}}
}

7.3 Python实现

def medial_axis_transform(img_path):"""计算图像的中轴变换"""# 读取图像img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)if img is None:raise ValueError(f"无法读取图像: {img_path}")# 二值化_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# 计算距离变换dist_transform = cv2.distanceTransform(binary, cv2.DIST_L2, 5)# 归一化距离变换结果cv2.normalize(dist_transform, dist_transform, 0, 255, cv2.NORM_MINMAX)# 提取局部最大值作为中轴点kernel = np.ones((3,3), dtype=np.uint8)dilated = cv2.dilate(dist_transform, kernel)medial_axis = (dist_transform == dilated) & (dist_transform > 20)# 转换为uint8类型result = medial_axis.astype(np.uint8) * 255# 转换为彩色图像result = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR)return result

8. 优化建议

要提高细化算法的效果,可以考虑以下优化方向:🔧

8.1 预处理优化

  • 使用中值滤波去除噪声
  • 进行适当的二值化处理
  • 填充小孔洞

8.2 算法选择

  • 对于简单图像,使用基本细化算法
  • 对于复杂图像,使用Zhang-Suen算法
  • 需要保持拓扑结构时,选择Hilditch算法

8.3 后处理优化

  • 去除毛刺
  • 平滑骨架
  • 修复断点

8.4 并行化处理

  • 使用GPU加速
  • 图像分块处理
  • 多线程优化

🎯 总结

图像细化是一个既优雅又实用的算法,就像一位细心的雕刻家,它能够将复杂的图像简化为最本质的骨架结构。掌握这个算法,就像拥有了一把"瘦身魔法棒",能够帮助我们更好地理解和分析图像!🎨✨

记住,好的细化效果需要:

  1. 合适的预处理
  2. 正确的算法选择
  3. 细致的参数调优
  4. 适当的后处理

让我们一起探索图像细化的奥秘,创造更多精彩的应用!🚀

http://www.xdnf.cn/news/506917.html

相关文章:

  • 剖析智能指针shared_ptr实现原理
  • Devin 编程智能体
  • 2023 睿抗机器人开发者大赛CAIP-编程技能赛-专科组(国赛)解题报告 | 珂学家
  • Active Directory域环境信息收集实战指南
  • 摄影构图小节
  • [逆向工程]C++实现DLL注入:原理、实现与防御全解析(二十五)
  • Flowbite 和 daisyUI 那个好用?
  • AI Agent开发第69课-彻底消除RAG知识库幻觉(3)-手撕“重排序”
  • W5500使用ioLibrary库创建DNS客户端
  • 【人工智能】DeepSeek解码:揭秘AI大模型训练的创新密码
  • 从0到1:Python项目部署与运维全攻略(10/10)
  • 如何在Cursor中高效使用MCP协议
  • 桌面端进程通信
  • 第十一课 蜗牛爬树
  • 恢复因 oh-my-zsh 安装导致丢失的 zsh 环境变量
  • 【Docker 新手入门指南】第五章:Hello Word
  • JavaScript运算符
  • 人工智能-自然语言与语音产品实现
  • SpringBoot--自动配置原理详解
  • 2025.05.17淘天机考笔试真题第二题
  • vue使用axios实现拦截器
  • 体育比分数据服务避坑指南
  • 信息与信息化
  • 【高斯函数拟合】高斯-牛顿法与梯度下降法的 Python 实现
  • Python集合运算:从基础到进阶全解析
  • 无线信道的噪声与干扰
  • 长三角、珠三角、成渝、京津冀四大城市群的区域与分布
  • 生产者 - 消费者模式实现方法整理
  • Ubuntu 添加系统调用
  • 给你的matplotlib images添加scale Bar