面向激光光栅雕刻的黑白图像无冗余空白簇分割算法求解
算法理论与定名
你要实现的这类算法属于激光雕刻光栅路径优化范畴下的轴对齐最小成本矩形划分,也常被称为「分块光栅扫描优化算法」,是目前商业激光雕刻设备内置的标配效率优化方案之一,已有成熟的工程化落地。
你之前用K-means效果差的核心原因是K-means的聚类目标是最小化像素到聚类中心的欧氏距离,和你需要的「最小化扫描总时间」目标完全不匹配,自然适配性差。
基于Python+Pillow的简易实现方案
这个实现采用递归二分的思路,每次判断横向/纵向切分是否能获得正收益,符合要求就切分,直到无法再切分为止,对边框、分散图形场景都能适配:
from PIL import Image # 配置参数(需根据你的实际雕刻参数换算,2.1cm对应像素需要结合图像DPI计算,示例取96DPI) DPI = 96 SCANLINE_OVERHEAD = 140 # 每条扫描线固定开销,单位ms def get_binary_mask(img_path): """获取二值图像的黑像素掩码,黑色标记为1,白色标记为0""" img = Image.open(img_path).convert("1") width, height = img.size mask = [] for y in range(height): row = [] for x in range(width): row.append(1 if img.getpixel((x,y)) == 0 else 0) mask.append(row) return mask, width, height def calculate_block_cost(mask, x1, y1, x2, y2): """计算单个矩形块的总扫描成本,返回成本值""" total_cost = 0 for y in range(y1, y2+1): # 查找当前行黑像素的左右边界 black_x = [x for x in range(x1, x2+1) if mask[y][x] == 1] if not black_x: continue left = min(black_x) right = max(black_x) scan_len_pixel = right - left + 1 # 扫描长度换算为时间:150mm/s=1.5mm/ms,像素转mm公式为 像素数*25.4/DPI scan_time = (scan_len_pixel * 25.4 / DPI) / 1.5 total_cost += SCANLINE_OVERHEAD + scan_time return total_cost def split_block(mask, x1, y1, x2, y2): """递归拆分块,返回最优拆分后的块坐标列表""" current_cost = calculate_block_cost(mask, x1, y1, x2, y2) best_gain = 0 best_split_pos = None split_dir = None # 0为横向切分,1为纵向切分 # 尝试所有纵向切分(左右拆分) for split_x in range(x1, x2): cost_left = calculate_block_cost(mask, x1, y1, split_x, y2) cost_right = calculate_block_cost(mask, split_x+1, y1, x2, y2) gain = current_cost - (cost_left + cost_right) if gain > best_gain and gain > 0: best_gain = gain best_split_pos = split_x split_dir = 1 # 尝试所有横向切分(上下拆分) for split_y in range(y1, y2): cost_top = calculate_block_cost(mask, x1, y1, x2, split_y) cost_bottom = calculate_block_cost(mask, x1, split_y+1, x2, y2) gain = current_cost - (cost_top + cost_bottom) if gain > best_gain and gain > 0: best_gain = gain best_split_pos = split_y split_dir = 0 # 有正收益则递归拆分两个子块,无收益则返回当前块 if best_gain > 0: if split_dir == 1: return split_block(mask, x1, y1, best_split_pos, y2) + split_block(mask, best_split_pos+1, y1, x2, y2) else: return split_block(mask, x1, y1, x2, best_split_pos) + split_block(mask, x1, best_split_pos+1, x2, y2) return [(x1, y1, x2, y2)] # 调用示例 if __name__ == "__main__": mask, w, h = get_binary_mask("test.png") # 先剔除全图边缘空白 all_black_x = [x for y in range(h) for x in range(w) if mask[y][x] == 1] all_black_y = [y for y in range(h) for x in range(w) if mask[y][x] == 1] if not all_black_x: print("无雕刻像素") exit() min_x, max_x = min(all_black_x), max(all_black_x) min_y, max_y = min(all_black_y), max(all_black_y) # 执行拆分 blocks = split_block(mask, min_x, min_y, max_x, max_y) print(f"拆分得到{len(blocks)}个雕刻块,坐标(x1,y1,x2,y2)分别为:") for block in blocks: print(block)
方案说明
- 代码内置的成本计算逻辑完全匹配你给出的约束条件,只会在拆分收益为正时执行切分,不会出现无效拆分
- 处理大图像时可以提前缓存每行的黑像素区间,避免重复计算,大幅提升运行速度
- 对你提到的两个示例场景都能输出最优结果:黑色边框会被拆分为上下左右4个独立块,三个横向排列的圆形会被拆分为3个对应单圆的独立块
- 输出的所有块不会重复包含黑像素,符合你的分配规则
内容的提问来源于stack exchange,提问作者Tatarize
相关产品推荐
相关产品推荐

