Commit
·
801759b
1
Parent(s):
a1a2d25
Add some debug code
Browse files- README.md +1 -1
- controlled_downscale.py +58 -6
README.md
CHANGED
@@ -46,7 +46,7 @@ Q: Why is this needed? Can't I use a post-processor to downscale the image?
|
|
46 |
|
47 |
Q: Is there special A1111 user-interface integration?
|
48 |
|
49 |
-
A: Yes... but not yet merged into the standard ControlNet extension's code. See https://
|
50 |
|
51 |
A: From my experience SD has a hard time creating genuine pixel art (even with dedicated base models and loras), where it has a mismatch of logical pixel sizes, smooth curves, etc. What appears to be a straight line at a glance, might bend around. This can cause post-processors to create artifacts based on quantization rounding a pixel to a position one pixel off in some direction. This model is intended to help fix that.
|
52 |
|
|
|
46 |
|
47 |
Q: Is there special A1111 user-interface integration?
|
48 |
|
49 |
+
A: Yes... but not yet merged into the standard ControlNet extension's code. See (https://civitai.com/posts/371477) if you want to integrate the changes yourself in the meantime.
|
50 |
|
51 |
A: From my experience SD has a hard time creating genuine pixel art (even with dedicated base models and loras), where it has a mismatch of logical pixel sizes, smooth curves, etc. What appears to be a straight line at a glance, might bend around. This can cause post-processors to create artifacts based on quantization rounding a pixel to a position one pixel off in some direction. This model is intended to help fix that.
|
52 |
|
controlled_downscale.py
CHANGED
@@ -59,6 +59,48 @@ class ExtractedBoxes:
|
|
59 |
down = back.down_pos()
|
60 |
return (down[0] + 1, down[1] + 1)
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
def average_box_dimensions(boxes: List[DownBox]) -> Dim:
|
64 |
assert len(boxes) > 0
|
@@ -235,7 +277,7 @@ def str2bool(value) -> bool:
|
|
235 |
raise argparse.ArgumentTypeError("Boolean value expected.")
|
236 |
|
237 |
|
238 |
-
def controlled_downscale(*, control_path: str, input_path: str, output_downscaled_path: Optional[str], output_quantized_path: Optional[str], sample_radius: Optional[int], downsampler: Image.Resampling, trim_cropped_edges: bool) -> None:
|
239 |
"""
|
240 |
Downsample and rescale an image.
|
241 |
|
@@ -246,6 +288,8 @@ def controlled_downscale(*, control_path: str, input_path: str, output_downscale
|
|
246 |
:param sample_radius: Radius for sampling (Manhattan distance).
|
247 |
:param downsampler: Downsampler to use.
|
248 |
:param trim_cropped_edges: Drop mapped checker grid elements that are cropped in the control image.
|
|
|
|
|
249 |
"""
|
250 |
if not output_downscaled_path and not output_quantized_path:
|
251 |
raise ValueError("At least one of output_up and output_down must be specified.")
|
@@ -262,6 +306,10 @@ def controlled_downscale(*, control_path: str, input_path: str, output_downscale
|
|
262 |
quantized_image = ImageRef(Image.new("RGB", input_image.size))
|
263 |
|
264 |
extracted_boxes = extract_boxes(control_image)
|
|
|
|
|
|
|
|
|
265 |
|
266 |
if output_downscaled_path:
|
267 |
downscaled_image = ImageRef(Image.new("RGB", extracted_boxes.down_dimensions()))
|
@@ -279,13 +327,15 @@ def controlled_downscale(*, control_path: str, input_path: str, output_downscale
|
|
279 |
|
280 |
def main(cli_args: List[str]) -> None:
|
281 |
parser = argparse.ArgumentParser(description="Downsample and rescale image.")
|
282 |
-
parser.add_argument("--control", required=True, help="Path to control image.")
|
283 |
-
parser.add_argument("--input", required=True, help="Path to input image.")
|
284 |
-
parser.add_argument("--output-downscaled", help="Path to save the output downscaled image.")
|
285 |
-
parser.add_argument("--output-quantized", help="Path to save the output quantized image (downscaled and then upscaled to the original size).")
|
286 |
parser.add_argument("--sample-radius", type=int, default=None, help="Radius for sampling (Manhattan distance).")
|
287 |
parser.add_argument("--downsampler", choices=["box", "bilinear", "bicubic", "hamming", "lanczos"], default="box", help="Downsampler to use.")
|
288 |
parser.add_argument("--trim-cropped-edges", type=str2bool, default=False, help="Drop mapped checker grid elements that are cropped in the control image.")
|
|
|
|
|
289 |
|
290 |
args = parser.parse_args(cli_args)
|
291 |
downsampler = Image.Resampling[args.downsampler.upper()]
|
@@ -297,7 +347,9 @@ def main(cli_args: List[str]) -> None:
|
|
297 |
output_quantized_path=args.output_quantized,
|
298 |
sample_radius=args.sample_radius,
|
299 |
downsampler=downsampler,
|
300 |
-
trim_cropped_edges=args.trim_cropped_edges
|
|
|
|
|
301 |
)
|
302 |
|
303 |
|
|
|
59 |
down = back.down_pos()
|
60 |
return (down[0] + 1, down[1] + 1)
|
61 |
|
62 |
+
def full_dimensions(self) -> Dim:
|
63 |
+
if len(self._boxes) == 0:
|
64 |
+
return (0, 0)
|
65 |
+
back = self._boxes[-1]
|
66 |
+
max = back.max()
|
67 |
+
return (max[0] + 1, max[1] + 1)
|
68 |
+
|
69 |
+
def to_colored_checkers(self, *, full=True) -> Image.Image:
|
70 |
+
if full:
|
71 |
+
width, height = self.full_dimensions()
|
72 |
+
else:
|
73 |
+
width, height = self.down_dimensions()
|
74 |
+
if width == 0 or height == 0:
|
75 |
+
return Image.new("RGB", (0, 0))
|
76 |
+
image = Image.new("RGB", (width, height))
|
77 |
+
colors = [
|
78 |
+
(255, 255, 255),
|
79 |
+
(0, 0, 0),
|
80 |
+
(255, 0, 0),
|
81 |
+
(255, 127, 0),
|
82 |
+
(255, 255, 0),
|
83 |
+
(0, 255, 0),
|
84 |
+
(0, 0, 255),
|
85 |
+
(75, 0, 130),
|
86 |
+
(148, 0, 211),
|
87 |
+
(255, 0, 255),
|
88 |
+
]
|
89 |
+
colorsMax = len(colors)
|
90 |
+
currColor = 0
|
91 |
+
for box in self._boxes:
|
92 |
+
color = colors[currColor]
|
93 |
+
currColor = (currColor + 1) % colorsMax
|
94 |
+
if full:
|
95 |
+
dim = box.dimensions()
|
96 |
+
pos = box.min()
|
97 |
+
else:
|
98 |
+
dim = (1, 1)
|
99 |
+
pos = box.down_pos()
|
100 |
+
subImage = Image.new("RGB", dim, color)
|
101 |
+
image.paste(subImage, pos)
|
102 |
+
return image
|
103 |
+
|
104 |
|
105 |
def average_box_dimensions(boxes: List[DownBox]) -> Dim:
|
106 |
assert len(boxes) > 0
|
|
|
277 |
raise argparse.ArgumentTypeError("Boolean value expected.")
|
278 |
|
279 |
|
280 |
+
def controlled_downscale(*, control_path: str, input_path: str, output_downscaled_path: Optional[str], output_quantized_path: Optional[str], sample_radius: Optional[int], downsampler: Image.Resampling, trim_cropped_edges: bool, output_colorized_full_path: Optional[str], output_colorized_down_path: Optional[str]) -> None:
|
281 |
"""
|
282 |
Downsample and rescale an image.
|
283 |
|
|
|
288 |
:param sample_radius: Radius for sampling (Manhattan distance).
|
289 |
:param downsampler: Downsampler to use.
|
290 |
:param trim_cropped_edges: Drop mapped checker grid elements that are cropped in the control image.
|
291 |
+
:param output_colorized_full_path: Colorize the full checker image to debug the checker parsing.
|
292 |
+
:param output_colorized_down_path: Colorize the downscaled checker image to debug the checker parsing.
|
293 |
"""
|
294 |
if not output_downscaled_path and not output_quantized_path:
|
295 |
raise ValueError("At least one of output_up and output_down must be specified.")
|
|
|
306 |
quantized_image = ImageRef(Image.new("RGB", input_image.size))
|
307 |
|
308 |
extracted_boxes = extract_boxes(control_image)
|
309 |
+
if output_colorized_full_path:
|
310 |
+
extracted_boxes.to_colored_checkers(full=True).save(output_colorized_full_path)
|
311 |
+
if output_colorized_down_path:
|
312 |
+
extracted_boxes.to_colored_checkers(full=False).save(output_colorized_down_path)
|
313 |
|
314 |
if output_downscaled_path:
|
315 |
downscaled_image = ImageRef(Image.new("RGB", extracted_boxes.down_dimensions()))
|
|
|
327 |
|
328 |
def main(cli_args: List[str]) -> None:
|
329 |
parser = argparse.ArgumentParser(description="Downsample and rescale image.")
|
330 |
+
parser.add_argument("--control", type=str, required=True, help="Path to control image.")
|
331 |
+
parser.add_argument("--input", type=str, required=True, help="Path to input image.")
|
332 |
+
parser.add_argument("--output-downscaled", type=str, help="Path to save the output downscaled image.")
|
333 |
+
parser.add_argument("--output-quantized", type=str, help="Path to save the output quantized image (downscaled and then upscaled to the original size).")
|
334 |
parser.add_argument("--sample-radius", type=int, default=None, help="Radius for sampling (Manhattan distance).")
|
335 |
parser.add_argument("--downsampler", choices=["box", "bilinear", "bicubic", "hamming", "lanczos"], default="box", help="Downsampler to use.")
|
336 |
parser.add_argument("--trim-cropped-edges", type=str2bool, default=False, help="Drop mapped checker grid elements that are cropped in the control image.")
|
337 |
+
parser.add_argument("--output-colorized-full", type=str, help="Colorize the full checker image to debug the checker parsing.")
|
338 |
+
parser.add_argument("--output-colorized-down", type=str, help="Colorize the downscaled checker image to debug the checker parsing.")
|
339 |
|
340 |
args = parser.parse_args(cli_args)
|
341 |
downsampler = Image.Resampling[args.downsampler.upper()]
|
|
|
347 |
output_quantized_path=args.output_quantized,
|
348 |
sample_radius=args.sample_radius,
|
349 |
downsampler=downsampler,
|
350 |
+
trim_cropped_edges=args.trim_cropped_edges,
|
351 |
+
output_colorized_full_path=args.output_colorized_full,
|
352 |
+
output_colorized_down_path=args.output_colorized_down,
|
353 |
)
|
354 |
|
355 |
|