avans06 commited on
Commit
39d3d2f
·
1 Parent(s): 8135b6a

Add an option to attempt border removal in the input parameters.

Browse files
Files changed (3) hide show
  1. app.py +130 -7
  2. requirements.txt +1 -1
  3. webui.bat +1 -1
app.py CHANGED
@@ -25,12 +25,124 @@ import os
25
  import tempfile
26
  import shutil
27
  import numpy as np
28
- import cv2 as cv
29
 
30
  # Import Kumiko's core library and its page module dependency
31
  import kumikolib
32
  import lib.page
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  # ----------------------------------------------------------------------
35
  # Core functions to solve the non-English path issue
36
  # ----------------------------------------------------------------------
@@ -84,7 +196,7 @@ kumikolib.cv.imwrite = imwrite_unicode
84
  # Gradio Processing Function
85
  # ----------------------------------------------------------------------
86
 
87
- def process_manga_images(files, output_structure, use_rtl, progress=gr.Progress(track_tqdm=True)):
88
  """
89
  The main processing logic for the Gradio interface.
90
  Receives uploaded files and settings, processes them, and returns a path to a ZIP file.
@@ -133,6 +245,10 @@ def process_manga_images(files, output_structure, use_rtl, progress=gr.Progress(
133
  x, y, width, height = panel.to_xywh()
134
  panel_img = page.img[y:y + height, x:x + width]
135
 
 
 
 
 
136
  output_filepath = ""
137
  # Check user's choice for the output structure
138
  if output_structure == "Group panels in folders":
@@ -196,8 +312,8 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
196
  file_types=["image"],
197
  )
198
 
199
- with gr.Accordion("Advanced Settings", open=False):
200
- # Add the new Radio button for selecting the output structure
201
  output_structure_choice = gr.Radio(
202
  label="ZIP File Structure",
203
  choices=["Group panels in folders", "Create a flat directory"],
@@ -205,12 +321,19 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
205
  info="Choose how to organize panels in the output ZIP file."
206
  )
207
 
208
- # Add the new Checkbox for RTL setting
209
  rtl_checkbox = gr.Checkbox(
210
  label="Right-to-Left (RTL) Reading Order",
211
- value=False, # Default to False
212
  info="Check this for manga that is read from right to left."
213
  )
 
 
 
 
 
 
 
214
 
215
  process_button = gr.Button("Start Analysis & Cropping", variant="primary")
216
 
@@ -221,7 +344,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
221
 
222
  process_button.click(
223
  fn=process_manga_images,
224
- inputs=[image_input, output_structure_choice, rtl_checkbox],
225
  outputs=output_zip,
226
  api_name="process"
227
  )
 
25
  import tempfile
26
  import shutil
27
  import numpy as np
28
+ import cv2 as cv # The project uses 'cv' as an alias for cv2
29
 
30
  # Import Kumiko's core library and its page module dependency
31
  import kumikolib
32
  import lib.page
33
 
34
+ # ----------------------------------------------------------------------
35
+ # Border Removal Function and Dependencies
36
+ # ----------------------------------------------------------------------
37
+
38
+ # Ensure the thinning function is available
39
+ try:
40
+ # Attempt to import the thinning function from the contrib module
41
+ from cv2.ximgproc import thinning
42
+ except ImportError:
43
+ # If opencv-contrib-python is not installed, print a warning and provide a dummy function
44
+ print("Warning: cv2.ximgproc.thinning not found. Border removal might be less effective.")
45
+ print("Please install 'opencv-contrib-python' via 'pip install opencv-contrib-python'")
46
+ def thinning(src, thinningType=None): # Dummy function to prevent crashes
47
+ return src
48
+
49
+
50
+ def _find_best_border_line(roi_mask: np.ndarray, axis: int, scan_range: range) -> int:
51
+ """
52
+ A helper function to find the best border line along a single axis.
53
+ It scans from the inside-out and returns the index of the line with the highest score.
54
+ """
55
+ best_index, max_score = scan_range.start, -1
56
+
57
+ total_span = abs(scan_range.stop - scan_range.start)
58
+ if total_span == 0:
59
+ return best_index
60
+
61
+ for i in scan_range:
62
+ if axis == 1: # Horizontal scan (for top/bottom borders)
63
+ continuity_score = np.count_nonzero(roi_mask[i, :])
64
+ else: # Vertical scan (for left/right borders)
65
+ continuity_score = np.count_nonzero(roi_mask[:, i])
66
+
67
+ progress = abs(i - scan_range.start)
68
+ position_weight = progress / total_span
69
+
70
+ score = continuity_score * (1 + position_weight)
71
+
72
+ if score >= max_score:
73
+ max_score, best_index = score, i
74
+
75
+ return best_index
76
+
77
+
78
+ def remove_border(panel_image: np.ndarray,
79
+ search_zone_ratio: float = 0.25,
80
+ padding: int = 5) -> np.ndarray:
81
+ """
82
+ Removes borders using skeletonization and weighted projection analysis.
83
+ """
84
+ if panel_image is None or panel_image.shape[0] < 30 or panel_image.shape[1] < 30:
85
+ return panel_image
86
+
87
+ pad_size = 15
88
+ # Use 'cv' which is the alias for cv2 in this project
89
+ padded_image = cv.copyMakeBorder(
90
+ panel_image, pad_size, pad_size, pad_size, pad_size,
91
+ cv.BORDER_CONSTANT, value=[255, 255, 255]
92
+ )
93
+
94
+ gray = cv.cvtColor(padded_image, cv.COLOR_BGR2GRAY)
95
+ _, thresh = cv.threshold(gray, 240, 255, cv.THRESH_BINARY_INV)
96
+
97
+ contours, _ = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
98
+
99
+ if not contours:
100
+ return panel_image
101
+
102
+ largest_contour = max(contours, key=cv.contourArea)
103
+ x, y, w, h = cv.boundingRect(largest_contour)
104
+
105
+ filled_mask = np.zeros_like(gray)
106
+ cv.drawContours(filled_mask, [largest_contour], -1, 255, cv.FILLED)
107
+
108
+ erosion_iterations = 5
109
+ hollow_contour = cv.subtract(filled_mask, cv.erode(filled_mask, np.ones((3,3), np.uint8), iterations=erosion_iterations))
110
+
111
+ skeleton = thinning(hollow_contour)
112
+
113
+ roi_mask = skeleton[y:y+h, x:x+w]
114
+
115
+ top_search_end = int(h * search_zone_ratio)
116
+ bottom_search_start = h - top_search_end
117
+ left_search_end = int(w * search_zone_ratio)
118
+ right_search_start = w - left_search_end
119
+
120
+ top_range = range(top_search_end, -1, -1)
121
+ bottom_range = range(bottom_search_start, h)
122
+ left_range = range(left_search_end, -1, -1)
123
+ right_range = range(right_search_start, w)
124
+
125
+ best_top_y = _find_best_border_line(roi_mask, axis=1, scan_range=top_range)
126
+ best_bottom_y = _find_best_border_line(roi_mask, axis=1, scan_range=bottom_range)
127
+ best_left_x = _find_best_border_line(roi_mask, axis=0, scan_range=left_range)
128
+ best_right_x = _find_best_border_line(roi_mask, axis=0, scan_range=right_range)
129
+
130
+ final_x1 = x + best_left_x + padding
131
+ final_y1 = y + best_top_y + padding
132
+ final_x2 = x + best_right_x - padding
133
+ final_y2 = y + best_bottom_y - padding
134
+
135
+ if final_x1 >= final_x2 or final_y1 >= final_y2:
136
+ return panel_image
137
+
138
+ cropped = padded_image[final_y1:final_y2, final_x1:final_x2]
139
+
140
+ if cropped.shape[0] < 10 or cropped.shape[1] < 10:
141
+ return panel_image
142
+
143
+ return cropped
144
+
145
+
146
  # ----------------------------------------------------------------------
147
  # Core functions to solve the non-English path issue
148
  # ----------------------------------------------------------------------
 
196
  # Gradio Processing Function
197
  # ----------------------------------------------------------------------
198
 
199
+ def process_manga_images(files, output_structure, use_rtl, remove_borders, progress=gr.Progress(track_tqdm=True)):
200
  """
201
  The main processing logic for the Gradio interface.
202
  Receives uploaded files and settings, processes them, and returns a path to a ZIP file.
 
245
  x, y, width, height = panel.to_xywh()
246
  panel_img = page.img[y:y + height, x:x + width]
247
 
248
+ # If the user checked the box, attempt to remove borders
249
+ if remove_borders:
250
+ panel_img = remove_border(panel_img)
251
+
252
  output_filepath = ""
253
  # Check user's choice for the output structure
254
  if output_structure == "Group panels in folders":
 
312
  file_types=["image"],
313
  )
314
 
315
+ with gr.Accordion("Advanced Settings", open=True):
316
+ # Add the Radio button for selecting the output structure
317
  output_structure_choice = gr.Radio(
318
  label="ZIP File Structure",
319
  choices=["Group panels in folders", "Create a flat directory"],
 
321
  info="Choose how to organize panels in the output ZIP file."
322
  )
323
 
324
+ # Add the Checkbox for RTL setting
325
  rtl_checkbox = gr.Checkbox(
326
  label="Right-to-Left (RTL) Reading Order",
327
+ value=True, # Default to True
328
  info="Check this for manga that is read from right to left."
329
  )
330
+
331
+ # Add the Checkbox for removing borders
332
+ remove_borders_checkbox = gr.Checkbox(
333
+ label="Attempt to remove panel borders",
334
+ value=False,
335
+ info="Crops the image to the content area. May not be perfect for all images."
336
+ )
337
 
338
  process_button = gr.Button("Start Analysis & Cropping", variant="primary")
339
 
 
344
 
345
  process_button.click(
346
  fn=process_manga_images,
347
+ inputs=[image_input, output_structure_choice, rtl_checkbox, remove_borders_checkbox],
348
  outputs=output_zip,
349
  api_name="process"
350
  )
requirements.txt CHANGED
@@ -1,3 +1,3 @@
1
- opencv-python
2
  requests
3
  gradio
 
1
+ opencv-contrib-python
2
  requests
3
  gradio
webui.bat CHANGED
@@ -10,7 +10,7 @@ set APPLICATION_NAME=app.py
10
  :: Define the name of the virtual environment directory
11
  set VENV_NAME=venv
12
  :: Set to 1 to always attempt to update packages from requirements.txt on every launch
13
- set ALWAYS_UPDATE_REQS=1
14
  :: ---------------------------------
15
 
16
 
 
10
  :: Define the name of the virtual environment directory
11
  set VENV_NAME=venv
12
  :: Set to 1 to always attempt to update packages from requirements.txt on every launch
13
+ set ALWAYS_UPDATE_REQS=0
14
  :: ---------------------------------
15
 
16