602 lines
23 KiB
Python
602 lines
23 KiB
Python
'''
|
||
参考资料:
|
||
https://www.gnu.org/software/hp2xx/hp2xx.html
|
||
https://zhuanlan.zhihu.com/p/622090369
|
||
'''
|
||
|
||
import os
|
||
import typing
|
||
import string
|
||
import re
|
||
import warnings
|
||
from enum import Enum
|
||
import shapely
|
||
import shapely.ops
|
||
from shapely.geometry import LineString, Polygon, MultiPolygon, MultiLineString
|
||
import numpy as np
|
||
import cv2
|
||
import itertools
|
||
from shapely.validation import make_valid
|
||
|
||
|
||
def my_warning_handler(message, category, filename, lineno, file=None, line=None):
|
||
print(f"PLT警告: {message}")
|
||
warnings.showwarning = my_warning_handler
|
||
|
||
def assert_warning(condition, message):
|
||
if not condition:
|
||
warnings.warn(message, UserWarning)
|
||
|
||
class PltCommand(Enum):
|
||
IN = 0
|
||
PU = 1
|
||
PD = 2
|
||
PA = 3
|
||
|
||
AbstractSyntaxTree = typing.List[typing.Dict]
|
||
|
||
|
||
class VirtualMachineOutput(object):
|
||
'''虚拟机执行后的结果'''
|
||
MIN_FILTER_AREA_FACTOR = 0.0068 ** 2
|
||
MAX_FILTER_AREA_FACTOR = 0.8 ** 2
|
||
|
||
def __init__(self, polygons :typing.List[Polygon], dispensable: typing.List[LineString]) -> None:
|
||
for poly in polygons:
|
||
assert isinstance(poly, Polygon)
|
||
for line in dispensable:
|
||
assert isinstance(line, LineString)
|
||
|
||
# 过滤结果
|
||
|
||
# 计算画布大小
|
||
min_x, min_y, max_x, max_y = MultiPolygon(polygons).bounds
|
||
|
||
filter_polygons = []
|
||
for polygon in polygons:
|
||
if (max_x - min_x) * (max_y - min_y) * VirtualMachineOutput.MIN_FILTER_AREA_FACTOR < polygon.area < (max_x - min_x) * (max_y - min_y) * VirtualMachineOutput.MAX_FILTER_AREA_FACTOR:
|
||
filter_polygons.append(polygon)
|
||
else:
|
||
boundary = polygon.boundary
|
||
if isinstance(boundary, LineString):
|
||
dispensable.append(boundary)
|
||
elif isinstance(boundary, MultiLineString):
|
||
dispensable.extend(list(boundary.geoms))
|
||
|
||
print(f"过滤了{len(polygons) - len(filter_polygons)}个多边形")
|
||
polygons = filter_polygons
|
||
|
||
# 计算多边形的包含关系,构建多边形树
|
||
self.nodes = [{"data":polygon, "child":[], "parent":None, "index":index} for index, polygon in enumerate(polygons)]
|
||
self.dispensable = dispensable
|
||
|
||
# 找每个node的最佳parent
|
||
for node1 in self.nodes:
|
||
best_parent = None
|
||
for node2 in self.nodes:
|
||
if node1 == node2:
|
||
continue
|
||
|
||
if node1["index"] == 8 and node2["index"] == 9:
|
||
pass
|
||
|
||
# 判断是否包含
|
||
#if node2["data"].contains(node1["data"]):
|
||
if node2["data"].area > node1["data"].area:
|
||
intersection = node2["data"].intersection(node1["data"])
|
||
if intersection.area > node1["data"].area * 0.99:
|
||
if best_parent is None:
|
||
best_parent = node2
|
||
else:
|
||
if best_parent["data"].contains(node2["data"]):
|
||
best_parent = node2
|
||
|
||
node1["parent"] = best_parent
|
||
|
||
# 填充child
|
||
self.tree = []
|
||
for node in self.nodes:
|
||
if node["parent"] is not None:
|
||
node["parent"]["child"].append(node)
|
||
else:
|
||
self.tree.append(node)
|
||
|
||
# 构建广度优先遍历
|
||
self.bfs = self.tree.copy()
|
||
index = 0
|
||
while index < len(self.bfs):
|
||
self.bfs.extend(self.bfs[index]["child"])
|
||
index += 1
|
||
|
||
def _draw_nodes(self, nodes: typing.List, scale_factor: float, show_id: bool = True) -> np.ndarray:
|
||
# 计算画布大小
|
||
min_x, min_y, max_x, max_y = MultiPolygon([node["data"] for node in nodes]).bounds
|
||
|
||
max_x = int(np.ceil(max_x * scale_factor))
|
||
max_y = int(np.ceil(max_y * scale_factor))
|
||
min_x = int(np.floor(min_x * scale_factor))
|
||
min_y = int(np.floor(min_y * scale_factor))
|
||
|
||
max_x -= min_x
|
||
max_y -= min_y
|
||
|
||
#print("画布大小", max_x, max_y)
|
||
|
||
result_img = np.zeros((max_y, max_x, 4), dtype=np.uint8)
|
||
|
||
nodes = sorted(nodes, key=lambda node: self.bfs.index(node))
|
||
|
||
for node in nodes:
|
||
pts = (np.asarray(node["data"].exterior.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
|
||
pts[:,1] = max_y - pts[:,1]
|
||
cv2.fillPoly(result_img, [pts], (255,255,255,255))
|
||
|
||
# 最外圈(parent为None)用红色,内部用黑色
|
||
if node["parent"] is None:
|
||
cv2.polylines(result_img, [pts], True, (0,0,255,255), 2) # 红色 (BGR: 0,0,255)
|
||
else:
|
||
cv2.polylines(result_img, [pts], True, (0,0,0,255), 2) # 黑色
|
||
|
||
# 画无关紧要的东西(dispensable线段)
|
||
multiPolygon = MultiPolygon([node["data"] for node in nodes])
|
||
multiPolygon = make_valid(multiPolygon.buffer(0))
|
||
|
||
# 获取最外圈轮廓(parent为None的节点)
|
||
outer_contours = [node["data"].exterior for node in nodes if node["parent"] is None]
|
||
|
||
for shape in self.dispensable:
|
||
if multiPolygon.contains(shape):
|
||
pts = (np.asarray(shape.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
|
||
pts[:,1] = max_y - pts[:,1]
|
||
|
||
# 判断是否与最外圈轮廓粘连(距离非常近)
|
||
is_connected_to_outer = False
|
||
line_length = shape.length # 线段长度(PLT单位)
|
||
|
||
# 距离阈值(PLT单位),小于此值视为粘连
|
||
CONNECT_THRESHOLD = 1
|
||
# 线段长度限制(PLT单位)
|
||
MAX_LINE_LENGTH = 155
|
||
|
||
if line_length < MAX_LINE_LENGTH: # 只检查短线段
|
||
for outer in outer_contours:
|
||
distance = shape.distance(outer)
|
||
if distance < CONNECT_THRESHOLD:
|
||
is_connected_to_outer = True
|
||
break
|
||
|
||
if is_connected_to_outer:
|
||
# ========== 调试开关 ==========
|
||
# True = 蓝色标记模式(用于调试查看哪些线被检测到)
|
||
# False = 镂空模式(正式使用,挖空0.2cm宽度)
|
||
DEBUG_MODE = False
|
||
|
||
if DEBUG_MODE:
|
||
# 调试模式:蓝色标记,方便查看检测结果
|
||
cv2.polylines(result_img, [pts], False, (255,0,0,255), 2) # 蓝色加粗
|
||
else:
|
||
# 正式模式:镂空效果(方形端点)
|
||
# 用矩形多边形代替polylines,避免圆形端点
|
||
cutout_half_width = int(15 * scale_factor) # 半宽度
|
||
if cutout_half_width < 1:
|
||
cutout_half_width = 1
|
||
|
||
# 对每个线段创建矩形多边形
|
||
for i in range(len(pts) - 1):
|
||
p1 = pts[i]
|
||
p2 = pts[i + 1]
|
||
|
||
# 计算线段方向的垂直向量
|
||
dx = p2[0] - p1[0]
|
||
dy = p2[1] - p1[1]
|
||
length = np.sqrt(dx*dx + dy*dy)
|
||
if length < 0.001:
|
||
continue
|
||
|
||
# 垂直方向单位向量
|
||
nx = -dy / length * cutout_half_width
|
||
ny = dx / length * cutout_half_width
|
||
|
||
# 构建矩形的四个角点
|
||
rect_pts = np.array([
|
||
[p1[0] + nx, p1[1] + ny],
|
||
[p1[0] - nx, p1[1] - ny],
|
||
[p2[0] - nx, p2[1] - ny],
|
||
[p2[0] + nx, p2[1] + ny]
|
||
], dtype=np.int32)
|
||
|
||
# 用透明色填充矩形
|
||
cv2.fillPoly(result_img, [rect_pts], (0,0,0,0))
|
||
else:
|
||
cv2.polylines(result_img, [pts], False, (0,0,0,255), 1) # 黑色
|
||
|
||
# 画parent为None的id
|
||
if show_id:
|
||
for node in nodes:
|
||
if node["parent"] is None:
|
||
centroid = node["data"].centroid
|
||
text_pos = (int((centroid.x * scale_factor) - min_x), int(max_y - (centroid.y * scale_factor) - min_y))
|
||
cv2.putText(result_img, str(node["index"]), text_pos, cv2.FONT_HERSHEY_SIMPLEX, 3, (255,0,0,255), 5)
|
||
|
||
return result_img
|
||
|
||
def full_sheet(self, scale_factor: float = 0.1) -> np.ndarray:
|
||
'''整幅输出'''
|
||
return self._draw_nodes(self.bfs, scale_factor)
|
||
|
||
def debug_full_sheet(self, scale_factor: float = 0.1) -> np.ndarray:
|
||
"""
|
||
用随机颜色画出所有多边形和被过滤掉的线段
|
||
"""
|
||
# 计算画布大小,画布大小应该考虑dispensable
|
||
all_shapes = [node["data"] for node in self.nodes] + self.dispensable
|
||
temp = []
|
||
for shape in all_shapes:
|
||
if isinstance(shape, LineString):
|
||
convex_hull = shape.convex_hull
|
||
if isinstance(convex_hull, Polygon):
|
||
temp.append(convex_hull)
|
||
else:
|
||
temp.append(shape)
|
||
min_x, min_y, max_x, max_y = MultiPolygon(temp).bounds
|
||
max_x = int(np.ceil(max_x * scale_factor))
|
||
max_y = int(np.ceil(max_y * scale_factor))
|
||
min_x = int(np.floor(min_x * scale_factor))
|
||
min_y = int(np.floor(min_y * scale_factor))
|
||
max_x -= min_x
|
||
max_y -= min_y
|
||
|
||
result_img = np.zeros((max_y, max_x, 4), dtype=np.uint8)
|
||
|
||
# 画多边形
|
||
for node in self.nodes:
|
||
#overlay = result_img.copy()
|
||
color = tuple(np.random.randint(0,256,size=3).tolist() + [255])
|
||
pts = (np.asarray(node["data"].exterior.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
|
||
pts[:,1] = max_y - pts[:,1]
|
||
cv2.fillPoly(result_img, [pts], color)
|
||
if node["parent"] is None:
|
||
cv2.polylines(result_img, [pts], True, (0,0,255,255), 1)
|
||
else:
|
||
cv2.polylines(result_img, [pts], True, (0,0,0,255), 1)
|
||
#alpha = 0.5
|
||
#cv2.addWeighted(overlay, alpha, result_img, 1 - alpha, 0, result_img)
|
||
|
||
# 画被过滤掉的线段
|
||
for shape in self.dispensable:
|
||
#overlay = result_img.copy()
|
||
color = tuple(np.random.randint(0,256,size=3).tolist() + [255])
|
||
pts = (np.asarray(shape.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
|
||
pts[:,1] = max_y - pts[:,1]
|
||
cv2.polylines(result_img, [pts], False, color, 1)
|
||
#cv2.addWeighted(overlay, alpha, result_img, 1 - alpha, 0, result_img)
|
||
|
||
# 画parent为None的id
|
||
for node in self.nodes:
|
||
if node["parent"] is None:
|
||
centroid = node["data"].centroid
|
||
text_pos = (int((centroid.x * scale_factor) - min_x), int(max_y - (centroid.y * scale_factor) - min_y))
|
||
cv2.putText(result_img, str(node["index"]), text_pos, cv2.FONT_HERSHEY_SIMPLEX, 3, (0,0,255,255), 5)
|
||
|
||
|
||
return result_img
|
||
|
||
def _distance_between_cluster(self, cluster1, cluster2):
|
||
min_distance = float("inf")
|
||
for node1 in cluster1:
|
||
for node2 in cluster2:
|
||
assert isinstance(node1["data"], Polygon)
|
||
assert isinstance(node2["data"], Polygon)
|
||
|
||
d = node1["data"].distance(node2["data"])
|
||
if d < min_distance:
|
||
min_distance = d
|
||
return min_distance
|
||
|
||
def get_single_size_info(self, size_num: int) -> typing.List[typing.List]:
|
||
'''获取单码信息'''
|
||
# 距离聚类
|
||
clusters = [[node] for node in self.nodes]
|
||
|
||
while len(clusters) > size_num:
|
||
# 每个循环将合并距离最小的两个类
|
||
min_distance_clusters = (None, None)
|
||
min_distance = float("inf")
|
||
for cluster1, cluster2 in itertools.combinations(clusters, 2):
|
||
d = self._distance_between_cluster(cluster1, cluster2)
|
||
if d < min_distance:
|
||
min_distance = d
|
||
min_distance_clusters = (cluster1, cluster2)
|
||
|
||
clusters.remove(min_distance_clusters[0])
|
||
clusters.remove(min_distance_clusters[1])
|
||
clusters.append(min_distance_clusters[0] + min_distance_clusters[1])
|
||
|
||
# 排序
|
||
sorted_clusters = sorted(clusters, key=lambda x: min([node["index"] for node in x]))
|
||
|
||
return list(sorted_clusters)
|
||
|
||
def single_size(self, size_num: int, scale_factor: float = 0.1) -> typing.List[np.ndarray]:
|
||
'''单码输出'''
|
||
sorted_clusters = self.get_single_size_info(size_num)
|
||
|
||
result = []
|
||
for cluster in sorted_clusters:
|
||
result.append(self._draw_nodes(cluster, scale_factor))
|
||
return result
|
||
|
||
def single_piece(self, scale_factor: float = 0.1, show_id: bool = False) -> typing.List[typing.Tuple[int, np.ndarray]]:
|
||
'''单片输出
|
||
返回: [(裁片ID, 图像), ...]
|
||
'''
|
||
result = []
|
||
for node in self.nodes:
|
||
# 如果是顶层则新建一副图像
|
||
if node["parent"] is None:
|
||
nodes = [node] + node['child']
|
||
img = self._draw_nodes(nodes, scale_factor, show_id=show_id)
|
||
result.append((node["index"], img))
|
||
return result
|
||
|
||
|
||
class _VirtualMachine(object):
|
||
'''运行PLT文件的虚拟机'''
|
||
def __init__(self, tolerance) -> None:
|
||
self.tolerance = tolerance
|
||
|
||
def run(self, abstract_syntax_tree: AbstractSyntaxTree) -> VirtualMachineOutput:
|
||
context = abstract_syntax_tree.copy()
|
||
current_x = 0
|
||
current_y = 0
|
||
self.pen_state = 'UP'
|
||
lines :typing.List[typing.List[typing.Tuple]] = []
|
||
lines_flag :typing.List[int] = []
|
||
|
||
while len(context) != 0:
|
||
code = context[0]
|
||
|
||
try:
|
||
if code["cmd"] == PltCommand.IN:
|
||
print("初始化")
|
||
|
||
elif code["cmd"] == PltCommand.PA:
|
||
if self.pen_state == 'UP':
|
||
pass
|
||
elif self.pen_state == 'DOWN':
|
||
new_x = float(code["args"][0])
|
||
new_y = float(code["args"][1])
|
||
|
||
# 判断是否与已有的线相连接
|
||
for i in range(len(lines) - 1, -1, -1):
|
||
if lines_flag[i] == 1:
|
||
continue
|
||
if lines[i][0] == (current_x, current_y):
|
||
lines[i].insert(0, (new_x, new_y))
|
||
if lines[i][0] == lines[i][-1]:
|
||
lines_flag[i] = 1
|
||
break
|
||
elif lines[i][0] == (new_x, new_y):
|
||
lines[i].insert(0, (current_x, current_y))
|
||
if lines[i][0] == lines[i][-1]:
|
||
lines_flag[i] = 1
|
||
break
|
||
elif lines[i][-1] == (current_x, current_y):
|
||
lines[i].append((new_x, new_y))
|
||
if lines[i][0] == lines[i][-1]:
|
||
lines_flag[i] = 1
|
||
break
|
||
elif lines[i][-1] == (new_x, new_y):
|
||
lines[i].append((current_x, current_y))
|
||
if lines[i][0] == lines[i][-1]:
|
||
lines_flag[i] = 1
|
||
break
|
||
else:
|
||
# 不与已有的线相连接
|
||
lines.append([(current_x, current_y), (new_x, new_y)])
|
||
lines_flag.append(0)
|
||
|
||
current_x = float(code["args"][0])
|
||
current_y = float(code["args"][1])
|
||
|
||
elif code["cmd"] == PltCommand.PU:
|
||
self.pen_state = 'UP'
|
||
|
||
elif code["cmd"] == PltCommand.PD:
|
||
self.pen_state = 'DOWN'
|
||
|
||
except Exception as e:
|
||
assert_warning(False, f"[code {code}] 执行指令异常: {e}")
|
||
finally:
|
||
context = context[1:]
|
||
|
||
# 创建LineString :typing.List[LineString] = []
|
||
lineStrings :typing.List[LineString] = []
|
||
lineStrings = [LineString(line) for line in lines if len(set(line)) > 1]
|
||
|
||
'''
|
||
import matplotlib.pyplot as plt
|
||
for line in lineStrings:
|
||
x, y = line.xy
|
||
plt.plot(x, y)
|
||
plt.show()
|
||
'''
|
||
|
||
return self.__build_polygons_and_dispensable(lineStrings)
|
||
|
||
def __build_polygons_and_dispensable(self, lineStrings: typing.List[LineString]) -> VirtualMachineOutput:
|
||
|
||
# 为非常近的线搭桥
|
||
tree = shapely.STRtree(lineStrings)
|
||
|
||
bridges = []
|
||
# 遍历所有线段的端点,寻找需要搭桥的地方
|
||
for line in lineStrings:
|
||
endpoints = [shapely.Point(line.coords[0]), shapely.Point(line.coords[-1])]
|
||
|
||
for p in endpoints:
|
||
for idx in tree.query(p.buffer(self.tolerance)):
|
||
other_line = lineStrings[idx]
|
||
if other_line == line:
|
||
continue
|
||
|
||
dist = p.distance(other_line)
|
||
|
||
if 0 < dist <= self.tolerance:
|
||
_, p_target = shapely.ops.nearest_points(p, other_line)
|
||
bridge = LineString([p, p_target])
|
||
bridges.append(bridge)
|
||
|
||
lineStrings.extend(bridges)
|
||
|
||
# 节点化并提取闭合区域
|
||
all_shapes = shapely.ops.unary_union(lineStrings)
|
||
assert isinstance(all_shapes, MultiLineString)
|
||
polygons :typing.List[Polygon] = list(shapely.ops.polygonize(all_shapes))
|
||
|
||
# 合并重叠多边形
|
||
merged_polygons = shapely.unary_union(polygons)
|
||
assert isinstance(merged_polygons, MultiPolygon)
|
||
polygons = list(merged_polygons.geoms)
|
||
|
||
# 只保留外环
|
||
polygons = [Polygon(poly.exterior) for poly in polygons]
|
||
|
||
# 找到多余的线段
|
||
dispensable :typing.List[LineString] = []
|
||
closed_rings = [poly.exterior for poly in polygons]
|
||
if closed_rings:
|
||
leftovers = all_shapes.difference(shapely.ops.unary_union(closed_rings))
|
||
assert isinstance(leftovers, shapely.MultiLineString)
|
||
for shape in leftovers.geoms:
|
||
dispensable.append(shape)
|
||
else:
|
||
dispensable = list(all_shapes.geoms)
|
||
|
||
for i in range(len(polygons)):
|
||
polygons[i] = polygons[i].buffer(0)
|
||
|
||
print(f"共生成多边形{len(polygons)}个,线段{len(dispensable)}条")
|
||
|
||
output = VirtualMachineOutput(polygons, dispensable)
|
||
|
||
return output
|
||
|
||
class _Preprocessor(object):
|
||
'''PLT文件预处理器'''
|
||
def __init__(self) -> None:
|
||
pass
|
||
|
||
def get_abstract_syntax_tree(self, src_content: str) -> AbstractSyntaxTree:
|
||
content = src_content
|
||
|
||
# 换行符转分号 某些文件的语句末尾没有分号
|
||
content = content.replace("\n", ";")
|
||
content = content.replace(" ", ",")
|
||
|
||
# 删除所有空白字符
|
||
for space in string.whitespace:
|
||
content = content.replace(space, "")
|
||
|
||
result = []
|
||
|
||
# 遍历每一行命令
|
||
for line_index, line in enumerate(content.split(";")):
|
||
if line == "":
|
||
continue
|
||
|
||
# 提取开头的英文
|
||
pattern = r'^[a-zA-Z]+'
|
||
match = re.search(pattern, line)
|
||
|
||
if match is None:
|
||
assert_warning(False, f"[line {line_index}] 未找到命令: {line}")
|
||
continue
|
||
|
||
# 获取对应的指令
|
||
command_str = match.group().upper()
|
||
try:
|
||
command = PltCommand[command_str]
|
||
except KeyError:
|
||
assert_warning(False, f"[line {line_index}] 不支持的指令: {command_str}")
|
||
continue
|
||
|
||
# 获取参数
|
||
if len(command_str) == len(line):
|
||
stack = []
|
||
else:
|
||
stack = line[len(command_str):].split(",")
|
||
|
||
if command == PltCommand.IN:
|
||
result.append({'cmd': PltCommand.IN, 'args': []})
|
||
|
||
elif command == PltCommand.PA:
|
||
for i in range(len(stack) // 2):
|
||
result.append({'cmd': PltCommand.PA, 'args': [stack[0], stack[1]]})
|
||
stack = stack[2:]
|
||
|
||
elif command == PltCommand.PU:
|
||
result.append({'cmd': PltCommand.PU, 'args': []})
|
||
for i in range(len(stack) // 2):
|
||
result.append({'cmd': PltCommand.PA, 'args': [stack[0], stack[1]]})
|
||
stack = stack[2:]
|
||
|
||
elif command == PltCommand.PD:
|
||
result.append({'cmd': PltCommand.PD, 'args': []})
|
||
for i in range(len(stack) // 2):
|
||
result.append({'cmd': PltCommand.PA, 'args': [stack[0], stack[1]]})
|
||
stack = stack[2:]
|
||
|
||
assert_warning(len(stack) == 0, f"[line {line_index}] 栈非空: {stack}")
|
||
|
||
return result
|
||
|
||
|
||
class PltReader(object):
|
||
'''PLT文件读取器'''
|
||
|
||
def __init__(self, pltfile: typing.TextIO) -> None:
|
||
self.pltfile = pltfile
|
||
|
||
preprocessor = _Preprocessor()
|
||
self.abstract_syntax_tree = preprocessor.get_abstract_syntax_tree(self.pltfile.read())
|
||
|
||
def get_output(self, tolerance=5):
|
||
machine = _VirtualMachine(tolerance=tolerance)
|
||
output = machine.run(self.abstract_syntax_tree)
|
||
return output
|
||
|
||
|
||
if __name__ == "__main__":
|
||
filepath = "默认方案-POLO衫长袖-A料-标准.plt"
|
||
|
||
with open(filepath, 'r') as f:
|
||
reader = PltReader(f)
|
||
output = reader.get_output()
|
||
|
||
# 调试整幅输出(随机颜色)
|
||
debug_full_sheet_path = "debug_full_sheet.png"
|
||
cv2.imwrite(debug_full_sheet_path, output.debug_full_sheet(scale_factor=72/1016))
|
||
print(f"调试整幅输出已写入: {debug_full_sheet_path}")
|
||
|
||
# 整幅输出
|
||
full_sheet_path = "full_sheet.png"
|
||
cv2.imwrite(full_sheet_path, output.full_sheet(scale_factor=72/1016))
|
||
print(f"整幅输出已写入: {full_sheet_path}")
|
||
|
||
# 单码输出
|
||
single_size_path = "single_size"
|
||
os.makedirs(single_size_path, exist_ok=True)
|
||
for index, img in enumerate(output.single_size(size_num=5, scale_factor=72/1016)):
|
||
cv2.imwrite(os.path.join(single_size_path, f"size_{index}.png"), img)
|
||
print(f"单码输出已写入: {single_size_path}/ (共{len(os.listdir(single_size_path))}个文件)")
|
||
|
||
# 单片输出 - 每个裁片独立保存为PNG
|
||
single_piece_path = "single_piece"
|
||
os.makedirs(single_piece_path, exist_ok=True)
|
||
pieces = output.single_piece(scale_factor=72/1016, show_id=False)
|
||
for piece_id, img in pieces:
|
||
cv2.imwrite(os.path.join(single_piece_path, f"piece_{piece_id}.png"), img)
|
||
print(f"单片输出已写入: {single_piece_path}/ (共{len(pieces)}个裁片)")
|
||
|