visualization
KimJunHan opened this issue · 3 comments
KimJunHan commented
lreiher commented
Here is a script that we have used to create this and similar visualizations from individual image files. Note that the script depends on ffmpeg
. The script should be pretty self-explanatory.
Usage
usage: video-mosaic.py [-h] [--prediction PREDICTION] [-c {4,6}] [-r R] [-b B]
folder output
Creates a mosaic of camera images, drone images, and network prediction.
positional arguments:
folder directory with subfolders 'front', 'homo', ...
output output directory for video frames and video
optional arguments:
-h, --help show this help message and exit
--prediction PREDICTION
folder name in FOLDER containing the predictions
-c {4,6} number of cameras
-r R video fps [Hz]
-b B border strength [px]
Code
#!/usr/bin/env python
import os
import argparse
import tqdm
import subprocess
import numpy as np
import cv2
MAIN_IMG_SHAPE = (256, 512)
# parse command line arguments
parser = argparse.ArgumentParser(description="Creates a mosaic of camera images, drone images, and network prediction.")
parser.add_argument("folder", type=str, help="directory with subfolders 'front', 'homo', ...")
parser.add_argument("output", type=str, help="output directory for video frames and video")
parser.add_argument("--prediction", type=str, help="folder name in FOLDER containing the predictions", default="prediction")
parser.add_argument("-c", type=int, help="number of cameras", choices=(4,6), default=4)
parser.add_argument("-r", type=int, help="video fps [Hz]", default=20)
parser.add_argument("-b", type=int, help="border strength [px]", default=4)
args = parser.parse_args()
base = os.path.abspath(os.path.expanduser(args.folder))
output = os.path.abspath(os.path.expanduser(args.output))
n_cams = args.c
fps = args.r
# define subfolders
folder = {}
if n_cams == 4:
folder["front"] = os.path.join(base, "front")
folder["rear"] = os.path.join(base, "rear")
folder["left"] = os.path.join(base, "left")
folder["right"] = os.path.join(base, "right")
folder["front_cc"] = os.path.join(base, "front_cc")
folder["rear_cc"] = os.path.join(base, "rear_cc")
folder["left_cc"] = os.path.join(base, "left_cc")
folder["right_cc"] = os.path.join(base, "right_cc")
elif n_cams == 6:
folder["front"] = os.path.join(base, "front")
folder["rear"] = os.path.join(base, "rear")
folder["front_left"] = os.path.join(base, "front_left")
folder["front_right"] = os.path.join(base, "front_right")
folder["rear_left"] = os.path.join(base, "rear_left")
folder["rear_right"] = os.path.join(base, "rear_right")
folder["front_cc"] = os.path.join(base, "front_cc")
folder["rear_cc"] = os.path.join(base, "rear_cc")
folder["front_left_cc"] = os.path.join(base, "front_left_cc")
folder["front_right_cc"] = os.path.join(base, "front_right_cc")
folder["rear_left_cc"] = os.path.join(base, "rear_left_cc")
folder["rear_right_cc"] = os.path.join(base, "rear_right_cc")
folder["homo"] = os.path.join(base, "homo")
folder["drone"] = os.path.join(base, "drone")
folder["prediction"] = os.path.join(base, args.prediction)
if not os.path.exists(output):
os.makedirs(output)
# get filenames
files = {}
for key, fol in folder.items():
files[key] = sorted([os.path.join(fol, f) for f in os.listdir(fol)])
n = len(files["prediction"])
# determine mosaic image shapes
BORDER = args.b
SHAPE1 = np.array(MAIN_IMG_SHAPE, dtype=np.uint32)
SHAPE2 = np.array(((SHAPE1[0] - BORDER) / 2, (SHAPE1[0] - BORDER)), dtype=np.uint32)
SHAPE3 = np.array(((SHAPE1[0] - 3 * BORDER) / 4, (SHAPE1[0] - 3 * BORDER) / 2), dtype=np.uint32)
MOSAIC_SHAPE = np.array((SHAPE1[0] + BORDER + SHAPE2[0], SHAPE1[1] + BORDER + SHAPE2[1]), dtype=np.uint32)
# aliases
b = BORDER
h1, w1 = SHAPE1
h2, w2 = SHAPE2
h3, w3 = SHAPE3
# define helper functions
def load_image(filename):
img = cv2.imread(filename)
return img
def resize_image(img, shape, interpolation=cv2.INTER_CUBIC):
# resize relevant image axis to length of corresponding target axis while preserving aspect ratio
axis = 0 if float(shape[0]) / float(img.shape[0]) > float(shape[1]) / float(img.shape[1]) else 1
factor = float(shape[axis]) / float(img.shape[axis])
img = cv2.resize(img, (0,0), fx=factor, fy=factor, interpolation=interpolation)
# crop other image axis to match target shape
center = img.shape[int(not axis)] / 2.0
step = shape[int(not axis)] / 2.0
left = int(center-step)
right = int(center+step)
if axis == 0:
img = img[:, left:right]
else:
img = img[left:right, :]
return img
# process all samples
for i in tqdm.tqdm(range(n)):
# init mosaic
mosaic = np.zeros((MOSAIC_SHAPE[0], MOSAIC_SHAPE[1], 3))
# load images
prediction = load_image(files["prediction"][i])
if n_cams == 6:
tmp = np.zeros((SHAPE1[0], SHAPE1[1], 3))
tmp[int((h1 - prediction.shape[0]) / 2) : int((h1 + prediction.shape[0]) / 2), :] = prediction
prediction = tmp
prediction = resize_image(prediction, SHAPE1)
drone = load_image(files["drone"][i])
drone = resize_image(drone, SHAPE1)
blend = 0.5 * prediction + 0.5 * drone
prediction = resize_image(prediction, SHAPE2)
drone = resize_image(drone, SHAPE2)
homo = load_image(files["homo"][i])
homo = resize_image(homo, SHAPE2)
if n_cams == 4:
front = load_image(files["front"][i])
front = resize_image(front, SHAPE3)
rear = load_image(files["rear"][i])
rear = resize_image(rear, SHAPE3)
left = load_image(files["left"][i])
left = resize_image(left, SHAPE3)
right = load_image(files["right"][i])
right = resize_image(right, SHAPE3)
front_cc = load_image(files["front_cc"][i])
front_cc = resize_image(front_cc, SHAPE3)
rear_cc = load_image(files["rear_cc"][i])
rear_cc = resize_image(rear_cc, SHAPE3)
left_cc = load_image(files["left_cc"][i])
left_cc = resize_image(left_cc, SHAPE3)
right_cc = load_image(files["right_cc"][i])
right_cc = resize_image(right_cc, SHAPE3)
elif n_cams == 6:
front = load_image(files["front"][i])
front = resize_image(front, SHAPE3)
rear = load_image(files["rear"][i])
rear = resize_image(rear, SHAPE3)
front_left = load_image(files["front_left"][i])
front_left = resize_image(front_left, SHAPE3)
front_right = load_image(files["front_right"][i])
front_right = resize_image(front_right, SHAPE3)
rear_left = load_image(files["rear_left"][i])
rear_left = resize_image(rear_left, SHAPE3)
rear_right = load_image(files["rear_right"][i])
rear_right = resize_image(rear_right, SHAPE3)
# fill mosaic
mosaic[ : h2 , : w2 ] = homo
mosaic[ h2 + b : 2 * h2 + b , : w2 ] = drone
mosaic[2 * h2 + 2 * b : , : w2 ] = prediction
mosaic[ h2 + b : , w2 + b : ] = blend
mosaic[0 : h3 , w2 + b : w2 + b + w3 ] = front if n_cams == 4 else 0
mosaic[0 : h3 , w2 + 2 * b + w3 : w2 + 2 * b + 2 * w3] = rear if n_cams == 4 else front
mosaic[h3 + b : 2 * h3 + b , w2 + b : w2 + b + w3 ] = left if n_cams == 4 else rear_left
mosaic[h3 + b : 2 * h3 + b , w2 + 2 * b + w3 : w2 + 2 * b + 2 * w3] = right if n_cams == 4 else front_left
mosaic[0 : h3 , w2 + 3 * b + 2 * w3 : w2 + 3*b + 3*w3] = front_cc if n_cams == 4 else rear
mosaic[0 : h3 , w2 + 4 * b + 3 * w3 : w2 + 4*b + 4*w3] = rear_cc if n_cams == 4 else 0
mosaic[h3 + b : 2 * h3 + b , w2 + 3 * b + 2 * w3 : w2 + 3*b + 3*w3] = left_cc if n_cams == 4 else front_right
mosaic[h3 + b : 2 * h3 + b , w2 + 4 * b + 3 * w3 : w2 + 4*b + 4*w3] = right_cc if n_cams == 4 else rear_right
# include filename as caption
caption = os.path.splitext(os.path.basename(files["prediction"][i]))[0]
cv2.putText(mosaic, caption, (w2 + b + 5, mosaic.shape[0] - 5),
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.25,
color=(255, 255, 255),
thickness=1)
# save mosaic frame
cv2.imwrite(os.path.join(output, f"{caption}.png"), mosaic)
# run ffmpeg
ffmpeg = subprocess.Popen(["ffmpeg",
"-framerate", f"{fps}",
"-pattern_type", "glob",
"-i", f"\"{os.path.join(output, '*.png')}\"",
"-c:v", "libx264",
"-profile:v", "high",
"-crf", "20",
"-pix_fmt", "yuv420p",
os.path.join(output, f"mosaic.mp4")]
)
KimJunHan commented
Thanks for your replay
One question
what do you mean subfolder??
should I make theme as directories with mkdir front, homo... ext for
examples??
[image: image.png]
I got some error to visualize it with video-mosaic.py
I need your help
thanks.
2024년 8월 19일 (월) 오후 6:06, Lennart Reiher ***@***.***>님이 작성:
… Here is a script that we have used to create this and similar
visualizations from individual image files. Note that the script depends on
ffmpeg <https://www.ffmpeg.org/>. The script should be pretty
self-explanatory.
mosaic.png (view on web)
<https://github.com/user-attachments/assets/91a503e2-e335-4fd5-a52c-f2a8bae333b9>
Usage
usage: video-mosaic.py [-h] [--prediction PREDICTION] [-c {4,6}] [-r R] [-b B]
folder output
Creates a mosaic of camera images, drone images, and network prediction.
positional arguments:
folder directory with subfolders 'front', 'homo', ...
output output directory for video frames and video
optional arguments:
-h, --help show this help message and exit
--prediction PREDICTION
folder name in FOLDER containing the predictions
-c {4,6} number of cameras
-r R video fps [Hz]
-b B border strength [px]
Code
#!/usr/bin/env python
import osimport argparseimport tqdmimport subprocessimport numpy as npimport cv2
MAIN_IMG_SHAPE = (256, 512)
# parse command line argumentsparser = argparse.ArgumentParser(description="Creates a mosaic of camera images, drone images, and network prediction.")parser.add_argument("folder", type=str, help="directory with subfolders 'front', 'homo', ...")parser.add_argument("output", type=str, help="output directory for video frames and video")parser.add_argument("--prediction", type=str, help="folder name in FOLDER containing the predictions", default="prediction")parser.add_argument("-c", type=int, help="number of cameras", choices=(4,6), default=4)parser.add_argument("-r", type=int, help="video fps [Hz]", default=20)parser.add_argument("-b", type=int, help="border strength [px]", default=4)args = parser.parse_args()
base = os.path.abspath(os.path.expanduser(args.folder))output = os.path.abspath(os.path.expanduser(args.output))n_cams = args.cfps = args.r
# define subfoldersfolder = {}if n_cams == 4:
folder["front"] = os.path.join(base, "front")
folder["rear"] = os.path.join(base, "rear")
folder["left"] = os.path.join(base, "left")
folder["right"] = os.path.join(base, "right")
folder["front_cc"] = os.path.join(base, "front_cc")
folder["rear_cc"] = os.path.join(base, "rear_cc")
folder["left_cc"] = os.path.join(base, "left_cc")
folder["right_cc"] = os.path.join(base, "right_cc")elif n_cams == 6:
folder["front"] = os.path.join(base, "front")
folder["rear"] = os.path.join(base, "rear")
folder["front_left"] = os.path.join(base, "front_left")
folder["front_right"] = os.path.join(base, "front_right")
folder["rear_left"] = os.path.join(base, "rear_left")
folder["rear_right"] = os.path.join(base, "rear_right")
folder["front_cc"] = os.path.join(base, "front_cc")
folder["rear_cc"] = os.path.join(base, "rear_cc")
folder["front_left_cc"] = os.path.join(base, "front_left_cc")
folder["front_right_cc"] = os.path.join(base, "front_right_cc")
folder["rear_left_cc"] = os.path.join(base, "rear_left_cc")
folder["rear_right_cc"] = os.path.join(base, "rear_right_cc")folder["homo"] = os.path.join(base, "homo")folder["drone"] = os.path.join(base, "drone")folder["prediction"] = os.path.join(base, args.prediction)if not os.path.exists(output):
os.makedirs(output)
# get filenamesfiles = {}for key, fol in folder.items():
files[key] = sorted([os.path.join(fol, f) for f in os.listdir(fol)])n = len(files["prediction"])
# determine mosaic image shapesBORDER = args.bSHAPE1 = np.array(MAIN_IMG_SHAPE, dtype=np.uint32)SHAPE2 = np.array(((SHAPE1[0] - BORDER) / 2, (SHAPE1[0] - BORDER)), dtype=np.uint32)SHAPE3 = np.array(((SHAPE1[0] - 3 * BORDER) / 4, (SHAPE1[0] - 3 * BORDER) / 2), dtype=np.uint32)MOSAIC_SHAPE = np.array((SHAPE1[0] + BORDER + SHAPE2[0], SHAPE1[1] + BORDER + SHAPE2[1]), dtype=np.uint32)# aliasesb = BORDERh1, w1 = SHAPE1h2, w2 = SHAPE2h3, w3 = SHAPE3
# define helper functionsdef load_image(filename):
img = cv2.imread(filename)
return img
def resize_image(img, shape, interpolation=cv2.INTER_CUBIC):
# resize relevant image axis to length of corresponding target axis while preserving aspect ratio
axis = 0 if float(shape[0]) / float(img.shape[0]) > float(shape[1]) / float(img.shape[1]) else 1
factor = float(shape[axis]) / float(img.shape[axis])
img = cv2.resize(img, (0,0), fx=factor, fy=factor, interpolation=interpolation)
# crop other image axis to match target shape
center = img.shape[int(not axis)] / 2.0
step = shape[int(not axis)] / 2.0
left = int(center-step)
right = int(center+step)
if axis == 0:
img = img[:, left:right]
else:
img = img[left:right, :]
return img
# process all samplesfor i in tqdm.tqdm(range(n)):
# init mosaic
mosaic = np.zeros((MOSAIC_SHAPE[0], MOSAIC_SHAPE[1], 3))
# load images
prediction = load_image(files["prediction"][i])
if n_cams == 6:
tmp = np.zeros((SHAPE1[0], SHAPE1[1], 3))
tmp[int((h1 - prediction.shape[0]) / 2) : int((h1 + prediction.shape[0]) / 2), :] = prediction
prediction = tmp
prediction = resize_image(prediction, SHAPE1)
drone = load_image(files["drone"][i])
drone = resize_image(drone, SHAPE1)
blend = 0.5 * prediction + 0.5 * drone
prediction = resize_image(prediction, SHAPE2)
drone = resize_image(drone, SHAPE2)
homo = load_image(files["homo"][i])
homo = resize_image(homo, SHAPE2)
if n_cams == 4:
front = load_image(files["front"][i])
front = resize_image(front, SHAPE3)
rear = load_image(files["rear"][i])
rear = resize_image(rear, SHAPE3)
left = load_image(files["left"][i])
left = resize_image(left, SHAPE3)
right = load_image(files["right"][i])
right = resize_image(right, SHAPE3)
front_cc = load_image(files["front_cc"][i])
front_cc = resize_image(front_cc, SHAPE3)
rear_cc = load_image(files["rear_cc"][i])
rear_cc = resize_image(rear_cc, SHAPE3)
left_cc = load_image(files["left_cc"][i])
left_cc = resize_image(left_cc, SHAPE3)
right_cc = load_image(files["right_cc"][i])
right_cc = resize_image(right_cc, SHAPE3)
elif n_cams == 6:
front = load_image(files["front"][i])
front = resize_image(front, SHAPE3)
rear = load_image(files["rear"][i])
rear = resize_image(rear, SHAPE3)
front_left = load_image(files["front_left"][i])
front_left = resize_image(front_left, SHAPE3)
front_right = load_image(files["front_right"][i])
front_right = resize_image(front_right, SHAPE3)
rear_left = load_image(files["rear_left"][i])
rear_left = resize_image(rear_left, SHAPE3)
rear_right = load_image(files["rear_right"][i])
rear_right = resize_image(rear_right, SHAPE3)
# fill mosaic
mosaic[ : h2 , : w2 ] = homo
mosaic[ h2 + b : 2 * h2 + b , : w2 ] = drone
mosaic[2 * h2 + 2 * b : , : w2 ] = prediction
mosaic[ h2 + b : , w2 + b : ] = blend
mosaic[0 : h3 , w2 + b : w2 + b + w3 ] = front if n_cams == 4 else 0
mosaic[0 : h3 , w2 + 2 * b + w3 : w2 + 2 * b + 2 * w3] = rear if n_cams == 4 else front
mosaic[h3 + b : 2 * h3 + b , w2 + b : w2 + b + w3 ] = left if n_cams == 4 else rear_left
mosaic[h3 + b : 2 * h3 + b , w2 + 2 * b + w3 : w2 + 2 * b + 2 * w3] = right if n_cams == 4 else front_left
mosaic[0 : h3 , w2 + 3 * b + 2 * w3 : w2 + 3*b + 3*w3] = front_cc if n_cams == 4 else rear
mosaic[0 : h3 , w2 + 4 * b + 3 * w3 : w2 + 4*b + 4*w3] = rear_cc if n_cams == 4 else 0
mosaic[h3 + b : 2 * h3 + b , w2 + 3 * b + 2 * w3 : w2 + 3*b + 3*w3] = left_cc if n_cams == 4 else front_right
mosaic[h3 + b : 2 * h3 + b , w2 + 4 * b + 3 * w3 : w2 + 4*b + 4*w3] = right_cc if n_cams == 4 else rear_right
# include filename as caption
caption = os.path.splitext(os.path.basename(files["prediction"][i]))[0]
cv2.putText(mosaic, caption, (w2 + b + 5, mosaic.shape[0] - 5),
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.25,
color=(255, 255, 255),
thickness=1)
# save mosaic frame
cv2.imwrite(os.path.join(output, f"{caption}.png"), mosaic)
# run ffmpegffmpeg = subprocess.Popen(["ffmpeg",
"-framerate", f"{fps}",
"-pattern_type", "glob",
"-i", f"\"{os.path.join(output, '*.png')}\"",
"-c:v", "libx264",
"-profile:v", "high",
"-crf", "20",
"-pix_fmt", "yuv420p",
os.path.join(output, f"mosaic.mp4")]
)
—
Reply to this email directly, view it on GitHub
<#41 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AL7TMCZYP2U4FDY3FALQXBLZSGYSPAVCNFSM6AAAAABMMOAPXSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEOJWGA2TONRRGI>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
lreiher commented
Yes, the input folder
needs to have subdirectories front
, homo
, ..., where the images are stored. As I said, the script is self-explanatory, we cannot provide further support beyond this point.