[Python] How can I accurately split compounded manga/manhwa panels in an image using OpenCV?

Discussão em 'Python' iniciado por Stack, Junho 29, 2025.

  1. Stack

    Stack Membro Participativo

    I'm working on a project where I download folders of manga/manhwa pages. Some of the images contain multiple scene panels compounded into a single large image. I want to split these panels into separate images, but my current code doesn't do it accurately—it sometimes misses panels or incorrectly merges them.

    I'm using OpenCV to detect and extract the panels. Below is the core part of my extract_panels function that does the splitting:

    img = cv2.imread(temp_path, cv2.IMREAD_COLOR)
    if img is None:
    with open(log_file, 'a') as f:
    f.write(f"Failed to load preprocessed image {temp_path} at {datetime.now()}\n")
    os.remove(temp_path) if os.path.exists(temp_path) else None
    return

    try:
    # Get image dimensions
    img_height, img_width = img.shape[:2]

    # Convert to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Blur to reduce noise
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)

    # Edge detection with Canny
    edges = cv2.Canny(blurred, 50, 150)

    # Dilate to connect panel borders
    kernel = np.ones((15, 15), np.uint8)
    dilated = cv2.dilate(edges, kernel, iterations=1)

    # Find contours
    contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    if not contours:
    with open(log_file, 'a') as f:
    f.write(f"No panels detected in {image_path} at {datetime.now()}\n")
    os.remove(temp_path) if os.path.exists(temp_path) else None
    return

    # Filter and collect panels
    panels = []
    min_area = img_width * img_height * 0.02 # At least 2% of image area
    min_width = img_width * 0.1 # At least 10% of image width
    min_height = img_height * 0.1 # At least 10% of image height

    for contour in contours:
    x, y, w, h = cv2.boundingRect(contour)
    area = w * h
    aspect_ratio = w / h if h > 0 else 0

    if (area > min_area and
    w >= min_width and
    h >= min_height and
    0.2 < aspect_ratio < 5.0):
    panels.append((x, y, w, h))

    # Sort panels top-to-bottom, left-to-right
    panels.sort(key=lambda p: (p[1], p[0]))

    # Merge or discard adjacent panels
    merged_panels = []
    i = 0
    while i < len(panels):
    x, y, w, h = panels
    if i + 1 < len(panels):
    next_x, next_y, next_w, next_h = panels[i + 1]
    gap = next_y - (y + h)
    if abs(x - next_x) < 50 and 0 <= gap <= 50:
    if h < img_height * 0.15:
    i += 1
    continue
    elif next_h < img_height * 0.15:
    i += 2
    merged_panels.append((x, y, w, h))
    continue
    else:
    merged_h = h + gap + next_h
    merged_panels.append((x, y, w, merged_h))
    i += 2
    continue
    merged_panels.append((x, y, w, h))
    i += 1

    # Extract and save panels
    panel_count = 0
    output_folder = os.path.normpath(output_folder)
    for i, (x, y, w, h) in enumerate(merged_panels):
    padding = 50
    x = max(0, x - padding)
    y = max(0, y - padding)
    w = min(w + 2 * padding, img.shape[1] - x)
    h = min(h + 2 * padding, img.shape[0] - y)

    panel = img[y:y + h, x:x + w]
    output_path = os.path.normpath(os.path.join(output_folder, f"page_{page_num:03d}_panel_{i:02d}.png"))
    cv2.imwrite(output_path, panel)
    panel_count += 1


    I'm struggling with accurately detecting each panel. In some cases, overlapping or vertically stacked panels get merged incorrectly, or small but valid panels are missed altogether.

    Any advice on improving the contour detection, filtering, or merging logic would be appreciated. Ideally, I'd like more consistent and clean cuts between the panels, even when they are closely packed or artistically bordered.

    Continue reading...

Compartilhe esta Página