first commit
10
README
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Simple image processor.
|
||||||
|
|
||||||
|
Will read from ./frames and output in ./output
|
||||||
|
|
||||||
|
Run 'bash process.sh' for bulk conversion, with 3000 circles and seed of 1 on 8 threads.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
BIN
frames/image1.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
frames/image1_pixel.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
frames/image2.png
Normal file
After Width: | Height: | Size: 463 KiB |
BIN
frames/image2_pixel.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
output/image1.jpg.png
Normal file
After Width: | Height: | Size: 212 KiB |
BIN
output/image1_pixel.jpg.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
output/image2.png.png
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
output/image2_pixel.png.png
Normal file
After Width: | Height: | Size: 17 KiB |
18
process.sh
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Number of parallel jobs
|
||||||
|
N=8
|
||||||
|
|
||||||
|
# Loop through the images in chunks of $N
|
||||||
|
for ((i=0; i<$(ls frames/ | wc -l); i+=N)); do
|
||||||
|
# Launch $N jobs in parallel
|
||||||
|
for ((j=0; j<N; j++)); do
|
||||||
|
idx=$((i+j))
|
||||||
|
if [ $idx -lt $(ls frames/ | wc -l) ]; then
|
||||||
|
python processor.py "frames/$(ls frames/ | sed -n $((idx+1))p)" "output/$(ls frames/ | sed -n $((idx+1))p)" 3000 1&
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait for the $N jobs to finish before launching the next batch
|
||||||
|
wait
|
||||||
|
done
|
69
processor.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
from numpy import random
|
||||||
|
|
||||||
|
def load_image(image_path):
|
||||||
|
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
|
||||||
|
if image is None:
|
||||||
|
raise ValueError(f"Image at path {image_path} could not be loaded.")
|
||||||
|
return image.astype(np.float32)
|
||||||
|
|
||||||
|
def draw_circle(image, reference_image, center, radius):
|
||||||
|
mask = np.zeros(image.shape[:2], dtype=np.uint8)
|
||||||
|
cv2.circle(mask, center, int(radius), 255, -1)
|
||||||
|
|
||||||
|
mean_color = cv2.mean(reference_image, mask=mask)[:3]
|
||||||
|
mean_color = tuple(map(int, mean_color))
|
||||||
|
|
||||||
|
tmp_image = image.copy()
|
||||||
|
cv2.circle(tmp_image, center, int(radius), mean_color, -1)
|
||||||
|
return tmp_image
|
||||||
|
|
||||||
|
def calculate_vector_distance(image1, image2):
|
||||||
|
return np.linalg.norm(image1 - image2)
|
||||||
|
|
||||||
|
def main(in_path, out_path, iterations, seed):
|
||||||
|
random.seed(seed)
|
||||||
|
image1 = load_image(in_path)
|
||||||
|
height, width, _ = image1.shape
|
||||||
|
image2 = np.zeros((height,width,3), np.uint8)
|
||||||
|
|
||||||
|
last_time = time.time()
|
||||||
|
|
||||||
|
for i in range(iterations):
|
||||||
|
print(f"Image {in_path} pass {i}", end="\r")
|
||||||
|
|
||||||
|
center = (random.randint(width), random.randint(height))
|
||||||
|
radius = max(width, height) / 2
|
||||||
|
step = radius
|
||||||
|
|
||||||
|
for _ in range(int(np.log2(max(width, height))) + 1):
|
||||||
|
candidate_up = draw_circle(image2, image1, center, radius + step)
|
||||||
|
candidate_up_distance = calculate_vector_distance(image1, candidate_up)
|
||||||
|
|
||||||
|
candidate_down = draw_circle(image2, image1, center, max(0, radius - step))
|
||||||
|
candidate_down_distance = calculate_vector_distance(image1, candidate_down)
|
||||||
|
|
||||||
|
if candidate_down_distance <= candidate_up_distance:
|
||||||
|
radius = max(0, radius - step)
|
||||||
|
else:
|
||||||
|
radius += step
|
||||||
|
|
||||||
|
step /= 2
|
||||||
|
|
||||||
|
image2 = draw_circle(image2, image1, center, radius)
|
||||||
|
|
||||||
|
print(f"{out_path} took {-last_time + time.time()} seconds")
|
||||||
|
cv2.imwrite(f"{out_path}.png", np.uint8(image2))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Draw and optimize a circle on an image using gradient ascent.")
|
||||||
|
parser.add_argument("input", help="Path to the reference image.")
|
||||||
|
parser.add_argument("output", help="Path to save the output image.")
|
||||||
|
parser.add_argument("circles", help="The amount of circles to draw.")
|
||||||
|
parser.add_argument("seed", help="The seed to use.")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args.input, args.output, int(args.circles), int(args.seed))
|