Spaces:
Running
Running
added dial rem
Browse files
comic_panel_extractor/border_panel_extractor.py
CHANGED
|
@@ -138,14 +138,16 @@ class BorderPanelExtractor:
|
|
| 138 |
# Draw bounding rectangles
|
| 139 |
img_h, img_w = original_image.shape[:2]
|
| 140 |
image_area = img_h * img_w
|
| 141 |
-
max_ratio = 0.7 # Max box area must be less than 70% of image
|
| 142 |
for cnt in contours:
|
| 143 |
x, y, w, h = cv2.boundingRect(cnt)
|
| 144 |
box_area = w * h
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
orig_pil = Image.fromarray(original_image)
|
| 151 |
self._create_visualization(orig_pil, accepted_boxes, "extract_with_contours.jpg")
|
|
@@ -311,24 +313,33 @@ class BorderPanelExtractor:
|
|
| 311 |
|
| 312 |
return all_paths, output_path
|
| 313 |
|
| 314 |
-
def draw_black(self, original_image, accepted_boxes) ->
|
| 315 |
orig_pil = Image.fromarray(original_image.copy())
|
| 316 |
-
|
| 317 |
|
|
|
|
|
|
|
|
|
|
| 318 |
stripe_height = 10
|
| 319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
for x1, y1, x2, y2 in accepted_boxes:
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
# Save the result
|
| 327 |
output_path = os.path.join(self.config.output_folder, "00_original_with_panels_removed.jpg")
|
| 328 |
orig_pil.save(output_path)
|
| 329 |
-
|
| 330 |
return output_path
|
| 331 |
|
|
|
|
| 332 |
def get_black_white_ratio(self, image_path: str, threshold: int = 128) -> dict:
|
| 333 |
"""
|
| 334 |
Calculate the ratio of black and white pixels in a binary image.
|
|
@@ -518,83 +529,85 @@ class BorderPanelExtractor:
|
|
| 518 |
|
| 519 |
def extend_to_nearby_boxes(self, boxes, image_shape):
|
| 520 |
"""
|
| 521 |
-
|
|
|
|
| 522 |
|
| 523 |
A box is represented by (x1, y1, x2, y2).
|
| 524 |
"""
|
| 525 |
if not boxes:
|
| 526 |
return boxes
|
| 527 |
-
extended_boxes = [list(box) for box in boxes]
|
| 528 |
-
height, width = image_shape[:2]
|
| 529 |
-
|
| 530 |
-
width_threshold = min(x2 - x1 for x1, y1, x2, y2 in extended_boxes)
|
| 531 |
-
height_threshold = min(y2 - y1 for x1, y1, x2, y2 in extended_boxes)
|
| 532 |
|
| 533 |
-
|
| 534 |
-
#
|
| 535 |
-
|
| 536 |
-
#
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
if i == j:
|
| 541 |
continue
|
| 542 |
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
#
|
| 558 |
-
|
| 559 |
-
if
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
if 0 < gap_top <= height_threshold:
|
| 589 |
-
# print(f" [INFO] Extending top of Box {i}. Gap ({gap_top:.2f}) <= Threshold ({height_threshold:.2f})")
|
| 590 |
-
extended_boxes[i][1] = y2_2
|
| 591 |
-
# elif gap_top > height_threshold:
|
| 592 |
-
# print(f" [DEBUG] Did not extend top: Gap ({gap_top:.2f}) > Threshold ({height_threshold:.2f})")
|
| 593 |
-
# else:
|
| 594 |
-
# print(f" [DEBUG] Not horizontally aligned for vertical extension.")
|
| 595 |
-
# print("-" * 20)
|
| 596 |
-
|
| 597 |
-
return [tuple(box) for box in extended_boxes]
|
| 598 |
|
| 599 |
def threshold_based_filter(self, boxes, image_shape):
|
| 600 |
img_h, img_w = image_shape[:2]
|
|
@@ -615,20 +628,32 @@ class BorderPanelExtractor:
|
|
| 615 |
def _apply_additional_processing(self, segmentation_mask_path: str) -> np.ndarray:
|
| 616 |
"""Apply additional image processing steps when needed."""
|
| 617 |
image_processor = ImageProcessor()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
|
| 619 |
# Step 5: Thicken black lines
|
| 620 |
processed_path = image_processor.thick_black(
|
| 621 |
-
|
| 622 |
-
file_name="
|
| 623 |
output_folder=f"{self.output_folder}"
|
| 624 |
)
|
| 625 |
|
| 626 |
# Step 6: Connect gaps
|
| 627 |
-
processed_path = image_processor.connect_horizontal_vertical_gaps(
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
)
|
| 632 |
|
| 633 |
# Check if more processing is needed
|
| 634 |
pixel_ratios = self.get_black_white_ratio(processed_path)
|
|
@@ -636,19 +661,19 @@ class BorderPanelExtractor:
|
|
| 636 |
# Additional processing steps
|
| 637 |
processed_path = image_processor.thin_image_borders(
|
| 638 |
processed_path,
|
| 639 |
-
file_name="
|
| 640 |
output_folder=f"{self.output_folder}"
|
| 641 |
)
|
| 642 |
|
| 643 |
processed_path = image_processor.remove_dangling_lines(
|
| 644 |
processed_path,
|
| 645 |
-
file_name="
|
| 646 |
output_folder=f"{self.output_folder}"
|
| 647 |
)
|
| 648 |
|
| 649 |
processed_path = image_processor.thick_black(
|
| 650 |
processed_path,
|
| 651 |
-
file_name="
|
| 652 |
output_folder=f"{self.output_folder}"
|
| 653 |
)
|
| 654 |
|
|
|
|
| 138 |
# Draw bounding rectangles
|
| 139 |
img_h, img_w = original_image.shape[:2]
|
| 140 |
image_area = img_h * img_w
|
|
|
|
| 141 |
for cnt in contours:
|
| 142 |
x, y, w, h = cv2.boundingRect(cnt)
|
| 143 |
box_area = w * h
|
| 144 |
+
# Check size thresholds
|
| 145 |
+
if box_area > image_area * 0.8 or not self._meets_size_requirements(box_area, w, h, image_area, img_w, img_h):
|
| 146 |
+
continue
|
| 147 |
+
|
| 148 |
+
minc, minr = x, y
|
| 149 |
+
maxc, maxr = x + w, y + h
|
| 150 |
+
accepted_boxes.append((minc, minr, maxc, maxr))
|
| 151 |
|
| 152 |
orig_pil = Image.fromarray(original_image)
|
| 153 |
self._create_visualization(orig_pil, accepted_boxes, "extract_with_contours.jpg")
|
|
|
|
| 313 |
|
| 314 |
return all_paths, output_path
|
| 315 |
|
| 316 |
+
def draw_black(self, original_image, accepted_boxes) -> str:
|
| 317 |
orig_pil = Image.fromarray(original_image.copy())
|
| 318 |
+
width, height = orig_pil.size
|
| 319 |
|
| 320 |
+
# Create a global stripe pattern (black and white horizontal stripes)
|
| 321 |
+
stripe_img = Image.new("RGB", (width, height), (255, 255, 255))
|
| 322 |
+
draw = ImageDraw.Draw(stripe_img)
|
| 323 |
stripe_height = 10
|
| 324 |
|
| 325 |
+
for y in range(0, height, stripe_height):
|
| 326 |
+
if (y // stripe_height) % 2 == 0:
|
| 327 |
+
draw.rectangle([0, y, width, min(y + stripe_height, height)], fill=(0, 0, 0))
|
| 328 |
+
|
| 329 |
+
# Create a mask where accepted boxes will be applied
|
| 330 |
+
mask = Image.new("L", (width, height), 0)
|
| 331 |
+
mask_draw = ImageDraw.Draw(mask)
|
| 332 |
for x1, y1, x2, y2 in accepted_boxes:
|
| 333 |
+
mask_draw.rectangle([x1, y1, x2, y2], fill=255)
|
| 334 |
+
|
| 335 |
+
# Paste the striped image only where mask is white (inside accepted boxes)
|
| 336 |
+
orig_pil.paste(stripe_img, (0, 0), mask)
|
| 337 |
+
|
|
|
|
| 338 |
output_path = os.path.join(self.config.output_folder, "00_original_with_panels_removed.jpg")
|
| 339 |
orig_pil.save(output_path)
|
|
|
|
| 340 |
return output_path
|
| 341 |
|
| 342 |
+
|
| 343 |
def get_black_white_ratio(self, image_path: str, threshold: int = 128) -> dict:
|
| 344 |
"""
|
| 345 |
Calculate the ratio of black and white pixels in a binary image.
|
|
|
|
| 529 |
|
| 530 |
def extend_to_nearby_boxes(self, boxes, image_shape):
|
| 531 |
"""
|
| 532 |
+
Extends boxes to the edge of any close neighboring box without causing
|
| 533 |
+
unintended merging by using an atomic update approach.
|
| 534 |
|
| 535 |
A box is represented by (x1, y1, x2, y2).
|
| 536 |
"""
|
| 537 |
if not boxes:
|
| 538 |
return boxes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
|
| 540 |
+
height, width = image_shape[:2]
|
| 541 |
+
# Ensure you have self.config.min_width_ratio and self.config.min_height_ratio defined
|
| 542 |
+
# For example:
|
| 543 |
+
# self.config.min_width_ratio = 0.05
|
| 544 |
+
# self.config.min_height_ratio = 0.05
|
| 545 |
+
width_threshold = width * self.config.min_width_ratio
|
| 546 |
+
height_threshold = height * self.config.min_height_ratio
|
| 547 |
+
|
| 548 |
+
final_boxes = []
|
| 549 |
+
# For each box, calculate its new coordinates based on the original list
|
| 550 |
+
for i in range(len(boxes)):
|
| 551 |
+
# Start with the original coordinates for the box we're currently processing
|
| 552 |
+
x1, y1, x2, y2 = boxes[i]
|
| 553 |
+
|
| 554 |
+
# These will store the closest boundaries we can extend to,
|
| 555 |
+
# initialized to the image edges.
|
| 556 |
+
closest_left_boundary = 0
|
| 557 |
+
closest_right_boundary = width
|
| 558 |
+
closest_top_boundary = 0
|
| 559 |
+
closest_bottom_boundary = height
|
| 560 |
+
|
| 561 |
+
# Find the closest neighbor on each of the four sides by checking against ALL other boxes
|
| 562 |
+
for j in range(len(boxes)):
|
| 563 |
if i == j:
|
| 564 |
continue
|
| 565 |
|
| 566 |
+
x1_j, y1_j, x2_j, y2_j = boxes[j]
|
| 567 |
+
|
| 568 |
+
# Check for neighbors to the RIGHT of box `i`
|
| 569 |
+
is_vert_overlap = (y1 < y2_j and y2 > y1_j) # Do they overlap vertically?
|
| 570 |
+
is_right_neighbor = (x1_j >= x2) # Is box `j` to the right of `i`?
|
| 571 |
+
if is_vert_overlap and is_right_neighbor:
|
| 572 |
+
closest_right_boundary = min(closest_right_boundary, x1_j)
|
| 573 |
+
|
| 574 |
+
# Check for neighbors to the LEFT of box `i`
|
| 575 |
+
is_left_neighbor = (x2_j <= x1) # Is box `j` to the left of `i`?
|
| 576 |
+
if is_vert_overlap and is_left_neighbor:
|
| 577 |
+
closest_left_boundary = max(closest_left_boundary, x2_j)
|
| 578 |
+
|
| 579 |
+
# Check for neighbors BELOW box `i`
|
| 580 |
+
is_horiz_overlap = (x1 < x2_j and x2 > x1_j) # Do they overlap horizontally?
|
| 581 |
+
is_bottom_neighbor = (y1_j >= y2) # Is box `j` below `i`?
|
| 582 |
+
if is_horiz_overlap and is_bottom_neighbor:
|
| 583 |
+
closest_bottom_boundary = min(closest_bottom_boundary, y1_j)
|
| 584 |
+
|
| 585 |
+
# Check for neighbors ABOVE box `i`
|
| 586 |
+
is_top_neighbor = (y2_j <= y1) # Is box `j` above `i`?
|
| 587 |
+
if is_horiz_overlap and is_top_neighbor:
|
| 588 |
+
closest_top_boundary = max(closest_top_boundary, y2_j)
|
| 589 |
+
|
| 590 |
+
# --- Apply the calculated extensions ---
|
| 591 |
+
|
| 592 |
+
# Extend right if the closest gap on the right is within the threshold
|
| 593 |
+
if 0 < (closest_right_boundary - x2) <= width_threshold:
|
| 594 |
+
x2 = closest_right_boundary
|
| 595 |
+
|
| 596 |
+
# Extend left
|
| 597 |
+
if 0 < (x1 - closest_left_boundary) <= width_threshold:
|
| 598 |
+
x1 = closest_left_boundary
|
| 599 |
+
|
| 600 |
+
# Extend down
|
| 601 |
+
if 0 < (closest_bottom_boundary - y2) <= height_threshold:
|
| 602 |
+
y2 = closest_bottom_boundary
|
| 603 |
+
|
| 604 |
+
# Extend up
|
| 605 |
+
if 0 < (y1 - closest_top_boundary) <= height_threshold:
|
| 606 |
+
y1 = closest_top_boundary
|
| 607 |
+
|
| 608 |
+
final_boxes.append(tuple(map(int, (x1, y1, x2, y2))))
|
| 609 |
+
|
| 610 |
+
return final_boxes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
|
| 612 |
def threshold_based_filter(self, boxes, image_shape):
|
| 613 |
img_h, img_w = image_shape[:2]
|
|
|
|
| 628 |
def _apply_additional_processing(self, segmentation_mask_path: str) -> np.ndarray:
|
| 629 |
"""Apply additional image processing steps when needed."""
|
| 630 |
image_processor = ImageProcessor()
|
| 631 |
+
|
| 632 |
+
processed_path = image_processor.remove_diagonal_lines_and_set_white(segmentation_mask_path,
|
| 633 |
+
file_name="04_remove_diagonal_lines_and_set_white.jpg",
|
| 634 |
+
output_folder=f"{self.output_folder}")
|
| 635 |
+
|
| 636 |
+
processed_path = image_processor.remove_dangling_lines(processed_path,
|
| 637 |
+
file_name="05_remove_dangling_lines.jpg",
|
| 638 |
+
output_folder=f"{self.output_folder}")
|
| 639 |
+
|
| 640 |
+
processed_path = image_processor.remove_diagonal_only_cells(processed_path,
|
| 641 |
+
file_name="06_remove_diagonal_only_cells.jpg",
|
| 642 |
+
output_folder=f"{self.output_folder}")
|
| 643 |
|
| 644 |
# Step 5: Thicken black lines
|
| 645 |
processed_path = image_processor.thick_black(
|
| 646 |
+
processed_path,
|
| 647 |
+
file_name="07_thick.jpg",
|
| 648 |
output_folder=f"{self.output_folder}"
|
| 649 |
)
|
| 650 |
|
| 651 |
# Step 6: Connect gaps
|
| 652 |
+
# processed_path = image_processor.connect_horizontal_vertical_gaps(
|
| 653 |
+
# processed_path,
|
| 654 |
+
# file_name="05_continuity.jpg",
|
| 655 |
+
# output_folder=f"{self.output_folder}"
|
| 656 |
+
# )
|
| 657 |
|
| 658 |
# Check if more processing is needed
|
| 659 |
pixel_ratios = self.get_black_white_ratio(processed_path)
|
|
|
|
| 661 |
# Additional processing steps
|
| 662 |
processed_path = image_processor.thin_image_borders(
|
| 663 |
processed_path,
|
| 664 |
+
file_name="08_thin.jpg",
|
| 665 |
output_folder=f"{self.output_folder}"
|
| 666 |
)
|
| 667 |
|
| 668 |
processed_path = image_processor.remove_dangling_lines(
|
| 669 |
processed_path,
|
| 670 |
+
file_name="09_remove_dangling_lines.jpg",
|
| 671 |
output_folder=f"{self.output_folder}"
|
| 672 |
)
|
| 673 |
|
| 674 |
processed_path = image_processor.thick_black(
|
| 675 |
processed_path,
|
| 676 |
+
file_name="010_thick.jpg",
|
| 677 |
output_folder=f"{self.output_folder}"
|
| 678 |
)
|
| 679 |
|