如何优化Python代码:减少Blot绘图线条数且保留细节
图片转Blot绘图代码的线条优化方案
问题背景
现有Python代码可将图片转换为Hack Club Blot支持的连线绘图代码,但生成的线条数高达80000条。调整detail参数会直接丢失关键细节,需要在保留细节的同时减少线条量。
核心优化措施
1. 修复轮廓简化的逻辑错误
原代码中max_tolerance和min_tolerance的计算错误地将布尔判断结果赋值给变量,导致容差值无法随detail_level合理调整。修正后让容差范围根据细节等级动态变化:
# 修正容差计算逻辑 max_tolerance = max(0.1, -15 * detail_level + 13) min_tolerance = max(0.01, -1.5 * detail_level + 1.3) tolerance = (1 - detail_level) * (max_tolerance - min_tolerance) + min_tolerance
2. 过滤微小噪声轮廓
大量细碎的噪声轮廓会生成无意义的短线,过滤掉长度小于阈值的轮廓:
# 在处理每个轮廓前添加过滤逻辑 contour_length = np.sum(np.sqrt(np.sum(np.diff(contour, axis=0)**2, axis=1))) if contour_length < 20: # 阈值可根据图片尺寸调整 continue
3. 合并相邻共线线段
对简化后的轮廓,合并连续的共线线段,减少冗余线条:
# 合并共线线段的辅助函数 def merge_collinear_points(points, angle_threshold=0.1): if len(points) <= 2: return points merged = [points[0]] for i in range(1, len(points)-1): prev = merged[-1] curr = points[i] next_p = points[i+1] # 计算前后线段的夹角 vec1 = curr - prev vec2 = next_p - curr angle = np.arccos(np.dot(vec1, vec2)/(np.linalg.norm(vec1)*np.linalg.norm(vec2))) if angle > angle_threshold: merged.append(curr) merged.append(points[-1]) return np.array(merged) # 在轮廓简化后调用合并函数 smoothed_contour = measure.approximate_polygon(contour, tolerance=tolerance) merged_contour = merge_collinear_points(smoothed_contour) if len(merged_contour) >= 2: # 后续处理逻辑
4. 优化边缘检测参数
调整高斯模糊和Canny阈值,减少细碎边缘的生成:
- 缩小高斯模糊的动态范围,避免高detail时过度模糊
- 调整Canny阈值的计算方式,让边缘检测更精准
5. 修正尺寸计算错误
原代码中错误地将y轴尺寸更新到maxDimension_x变量中,导致文档尺寸计算错误,修复后正确区分x/y轴的最大尺寸。
修改后的完整代码
from PIL import Image, ImageOps import numpy as np import cv2 from skimage import measure, transform # Set the detail level (1 for maximum detail, 0 for minimal detail) detail_level = 0.8 # Higher detail level for better quality # 合并共线线段的辅助函数 def merge_collinear_points(points, angle_threshold=0.1): if len(points) <= 2: return points merged = [points[0]] for i in range(1, len(points)-1): prev = merged[-1] curr = points[i] next_p = points[i+1] # 计算向量夹角(弧度) vec1 = curr - prev vec2 = next_p - curr norm1 = np.linalg.norm(vec1) norm2 = np.linalg.norm(vec2) if norm1 == 0 or norm2 == 0: continue cos_angle = np.dot(vec1, vec2) / (norm1 * norm2) cos_angle = np.clip(cos_angle, -1.0, 1.0) angle = np.arccos(cos_angle) if angle > angle_threshold: merged.append(curr) merged.append(points[-1]) return np.array(merged) # Function to process the image and extract edges with higher precision def process_image(image_path): # Load image and convert to grayscale image = Image.open(image_path).convert("L") image = ImageOps.mirror(image) # Mirror the image horizontally image = ImageOps.invert(image) # Invert to make the background black and foreground white image = image.rotate(180) # Rotate the image by 180 degrees image_array = np.array(image) # Calculate ksize (ensuring it's odd and positive) ksize_value = max(3, int(round(-2 * detail_level + 4))) # 缩小模糊范围,保留更多细节 if ksize_value % 2 == 0: ksize_value += 1 ksize = (ksize_value, ksize_value) # Apply a slight blur to reduce noise blurred = cv2.GaussianBlur(image_array, ksize, 0) # Use Canny edge detection with adjusted thresholds canny_threshold1 = int(round(-25 * detail_level + 45)) canny_threshold2 = int(round(-60 * detail_level + 120)) edges = cv2.Canny(blurred, canny_threshold1, canny_threshold2) # 减少膨胀操作,避免边缘过度加粗 edges = transform.rescale(edges, 1.0, anti_aliasing=True) edges = cv2.dilate(edges, np.ones((1,1),np.uint8), iterations=1) # Use contours to find connected components contours = measure.find_contours(edges, 0.8) return contours, image_array.shape # Function to generate the Blot code def generate_blot_code(contours, dimensions, detail_level=0.8): maxDimension_y = 0 maxDimension_x = 0 print("Generating Blot code...") lines = [] # 修正容差计算逻辑 max_tolerance = max(0.1, -15 * detail_level + 13) min_tolerance = max(0.01, -1.5 * detail_level + 1.3) tolerance = (1 - detail_level) * (max_tolerance - min_tolerance) + min_tolerance # Calculate bounding box of all contours all_points = np.concatenate(contours) min_y, min_x = np.min(all_points, axis=0) max_y, max_x = np.max(all_points, axis=0) # Calculate scale and translation to center the drawing scale_x = (dimensions[1] - 1) / (max_x - min_x) scale_y = (dimensions[0] - 1) / (max_y - min_y) scale = min(scale_x, scale_y) # Maintain aspect ratio by using the smallest scale factor translate_x = (dimensions[1] - (max_x - min_x) * scale) / 2 - min_x * scale translate_y = (dimensions[0] - (max_y - min_y) * scale) / 2 - min_y * scale for contour in contours: # 过滤微小轮廓 contour_length = np.sum(np.sqrt(np.sum(np.diff(contour, axis=0)**2, axis=1))) if contour_length < 20: # 可根据实际图片调整阈值 continue # Smooth the contour and simplify based on the detail level smoothed_contour = measure.approximate_polygon(contour, tolerance=tolerance) # 合并共线线段 merged_contour = merge_collinear_points(smoothed_contour) if len(merged_contour) >= 2: # Only consider meaningful contours for i in range(len(merged_contour) - 1): y1, x1 = merged_contour[i] y2, x2 = merged_contour[i + 1] # Scale and translate coordinates x1 = int(x1 * scale + translate_x) y1 = int(y1 * scale + translate_y) x2 = int(x2 * scale + translate_x) y2 = int(y2 * scale + translate_y) lines.append(f"finalLines.push([[{x1}, {y1}], [{x2}, {y2}]]);\n") # 修正maxDimension_y的计算 if x1 > maxDimension_x: maxDimension_x = x1 +5 if x2 > maxDimension_x: maxDimension_x = x2 +5 if y1 > maxDimension_y: maxDimension_y = y1 +5 if y2 > maxDimension_y: maxDimension_y = y2 +5 blot_code = [ "// Produced by Vivaan Shahani, based on Aditya Anand's Blotinator, not human-written\n", f"setDocDimensions({str(maxDimension_x)}, {str(maxDimension_y)});\n", "const finalLines = [];\n" ] blot_code.extend(lines) blot_code.append("drawLines(finalLines);") return blot_code # Main function if __name__ == "__main__": # Use the correct image path image_path = '/Users/vivaanshahani/Downloads/IMG_9654.png' # Process the image contours, dimensions = process_image(image_path) # Generate the Blot code with the specified detail level blot_code = generate_blot_code(contours, dimensions, detail_level) # Write the Blot code to a file output_path = "/Users/vivaanshahani/Downloads/Blotcode.js" with open(output_path, "w") as file: file.writelines(blot_code) print(f"Blot code generated and saved to {output_path}")
内容的提问来源于stack exchange,提问作者user27099259
相关产品推荐
相关产品推荐

