You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:42:32