如何将cv2.findContours多轮廓结果转换为NumPy数组并生成正常GCode
问题背景
处理黑白曼陀罗图片时,需用Python结合OpenCV、NumPy提取轮廓的X/Y坐标并生成GCode文件。但cv2.findContours返回多个长度不一的三维数组(格式如array([[[179, 50]], ... [[131, 85]]], dtype=int32)),期望转换为np.array points[X][Y]的二维数组结构。
当前轮廓提取代码:
img = cv2.imread(image_path, 0) assert img is not None, "file could not be read, check with os.path.exists()" ret, thresh = cv2.threshold(img, 127, 255, 0) contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
最初尝试收集点的代码:
points = [] for contour in contours: points.extend(contour) return points
需转换为NumPy数组以生成整洁CSV,尝试填充轮廓至相同长度时触发报错:
maxlength = max(len(contour) for contour in contours) cnts = [] for contour in contours: padded_contour = np.pad(contour, ((0, maxlength - len(contour)), (0, 0)), mode='constant') cnts.append(padded_contour) cnts_array = np.array(cnts)
报错信息:ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (2,2) and requested shape (3,2)
编辑后可运行但生成图片出现奇怪线条的代码:
all_points = [] for contour in contours: for point in contour: x, y = point[0] all_points.append([x, y]) contours_array = all_points # np.array(all_points) return contours_array
问题分析与解决方案
1. 填充轮廓报错的原因与修正
cv2.findContours返回的每个轮廓是三维数组(形状为(N,1,2)),原填充代码仅处理了第一和第三维度,忽略了中间的1维度。正确填充需针对三个维度分别设置:
maxlength = max(len(contour) for contour in contours) cnts = [] for contour in contours: # 填充参数:(N维度填充量, 中间1维度填充量, 坐标维度填充量) padded_contour = np.pad(contour, ((0, maxlength - len(contour)), (0, 0), (0, 0)), mode='constant') cnts.append(padded_contour) cnts_array = np.array(cnts) # 转换为(M, maxlength, 2)的二维轮廓数组(M为轮廓数量) cnts_2d = cnts_array.reshape(-1, maxlength, 2)
注:这种填充方式会引入无效的0值点,对后续GCode/CSV生成无实际意义,不推荐使用。
2. 正确提取二维坐标数组
无需强行统一轮廓长度,直接将每个轮廓的三维数组转换为二维后合并,即可得到符合需求的(total_points, 2)结构数组:
all_points = [] for contour in contours: # 将三维轮廓数组(N,1,2)转成二维(N,2)后添加到列表 all_points.extend(contour.reshape(-1, 2)) # 转换为NumPy二维数组 contours_array = np.array(all_points)
3. 解决“奇怪线条”问题
奇怪线条源于所有轮廓点连续绘制,但不同轮廓是独立闭合曲线,绘制时需在轮廓间抬笔(GCode中用G0指令)。正确的GCode生成逻辑如下:
def generate_gcode(contours, output_path): with open(output_path, 'w') as f: # GCode初始化设置 f.write("G21 ; 单位设置为毫米\n") f.write("G90 ; 使用绝对坐标\n") f.write("M3 S1000 ; 开启主轴\n") for contour in contours: if len(contour) == 0: continue # 提取当前轮廓的二维坐标 pts = contour.reshape(-1, 2) # 抬笔移动到轮廓起点 f.write(f"G0 X{pts[0][0]} Y{pts[0][1]}\n") # 落笔准备绘制 f.write("G1 Z-1.0 F1000 ; 落笔\n") # 依次绘制轮廓上的点 for x, y in pts[1:]: f.write(f"G1 X{x} Y{y} F1000\n") # 闭合轮廓(可选) f.write(f"G1 X{pts[0][0]} Y{pts[0][1]}\n") # 抬笔离开工件 f.write("G0 Z5.0 ; 抬笔\n") # GCode结束指令 f.write("M5 ; 关闭主轴\n") f.write("M2 ; 程序结束\n")
4. 保存坐标为CSV文件
使用NumPy内置方法直接保存二维坐标数组为CSV:
np.savetxt("contour_points.csv", contours_array, delimiter=",", header="X,Y", comments="")
内容的提问来源于stack exchange,提问作者ALMR00

