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 |
|