Packing Better Montages than ImageMagick with Python Rect Packer

ImageMagick has a built in Montage creating tool. It's good enough for casual montaging, but it's definitely suboptimal for packing varying size images.

All photos from: https://unsplash.com/collections/1199299/fun-with-fall-(thanksgiving%2C-autumn)

Simply using ImageMagick's montage it looks something the following. First the script that I run:

TEMP_DIRECTORY=$(mktemp -d /tmp/montageXXXXXX)
/usr/local/bin/mogrify -path ${TEMP_DIRECTORY}/ -geometry 480x480\> "$@"
/usr/local/bin/montage ${TEMP_DIRECTORY}/* -geometry +2+2 "$( dirname "$1" )"/montage.jpg

First I rescale all the images to "up-to 480x480" keeping aspect ratio, and then run the montage with a 2x2 pixel border.

Original images (just scaled down)

This looks pretty bad. Mostly because montage will not pack the rectangles more densely.

We could first resize all the images so that their height is e.g. 480px:

for f in "$@"
do
	/usr/local/bin/convert "$f" -geometry x480 "${f%.*}_480h.jpg"
done

And then running montage, to get this:

Images resized to height=480px

Already looking much better, but we have little control over the resulting size of the montage, ImageMagick just does its best job at packing everything. With similar heights - it's an easy job. However we can still see a lot of annoying whitespace on the right. What if there's a better way to pack the images?

Enter, rectpack: https://github.com/secnot/rectpack

This is a Python package implementing a few algorithms for rectangle packing, a concrete spatial instance of the classic knapsack problem (NP complete!) from computer science: https://en.wikipedia.org/wiki/Knapsack_problem

Here's my script:

import cv2
import rpack
import os
import glob
from rectpack import newPacker
import pickle
import numpy as np
import argparse

parser = argparse.ArgumentParser(description='Montage creator with rectpack')
parser.add_argument('--width', help='Output image width', default=5200, type=int)
parser.add_argument('--aspect', help='Output image aspect ratio, \
    e.g. height = <width> * <aspect>', default=1.0, type=float)
parser.add_argument('--output', help='Output image name', default='output.png')
parser.add_argument('--input_dir', help='Input directory with images', default='./')
parser.add_argument('--debug', help='Draw "debug" info', default=False, type=bool)
parser.add_argument('--border', help='Border around images in px', default=2, type=int)
args = parser.parse_args()

files = sum([glob.glob(os.path.join(args.input_dir, '*.' + e)) for e in ['jpg', 'jpeg', 'png']], [])
print('found %d files in %s' % (len(files), args.input_dir))

print('getting images sizes...')
sizes = [(im_file, cv2.imread(im_file).shape) for im_file in files]

# NOTE: you could pick a different packing algo by setting pack_algo=..., e.g. pack_algo=rectpack.SkylineBlWm
packer = newPacker(rotation=False)
for i, r in enumerate(sizes):
    packer.add_rect(r[1][1] + args.border * 2, r[1][0] + args.border * 2, rid=i)

out_w = args.width
aspect_ratio_wh = args.aspect
out_h = int(out_w * aspect_ratio_wh)

packer.add_bin(out_w, out_h)

print('packing...')
packer.pack()

output_im = np.full((out_h, out_w, 3), 255, np.uint8)

used = []

for rect in packer.rect_list():
    b, x, y, w, h, rid = rect

    used += [rid]

    orig_file_name = sizes[rid][0]
    im = cv2.imread(orig_file_name, cv2.IMREAD_COLOR)
    output_im[out_h - y - h + args.border : out_h - y - args.border, x + args.border:x+w - args.border] = im
    if args.debug:
        cv2.rectangle(output_im, (x,out_h - y - h), (x+w,out_h - y), (255,0,0), 3)
        cv2.putText(output_im, "%d"%rid, (x, out_h - y), cv2.FONT_HERSHEY_PLAIN, 3.0, (0,0,255), 2)

print('used %d of %d images' % (len(used), len(files)))

print('writing image output %s:...' % args.output)
cv2.imwrite(args.output, output_im)

print('done.')

Running it like so:

$ python3 pack.py --input_dir ~/Downloads/montage/resize480/ --width 2200 --border 10 --debug True

Resulted in this:

Montage with rectpack

That doesn't look the best, but it's definitely nice it tries to tile things together.

There are some options to consider:

$ python3 pack.py --help
usage: pack.py [-h] [--width WIDTH] [--aspect ASPECT] [--output OUTPUT]
               [--input_dir INPUT_DIR] [--debug DEBUG] [--border BORDER]

Montage creator with rectpack

optional arguments:
  -h, --help            show this help message and exit
  --width WIDTH         Output image width
  --aspect ASPECT       Output image aspect ratio, e.g. height = <width> *
                        <aspect>
  --output OUTPUT       Output image name
  --input_dir INPUT_DIR
                        Input directory with images
  --debug DEBUG         Draw "debug" info
  --border BORDER       Border around images in px

Running over the fixed height images:

$ python3 pack.py --input_dir ~/Downloads/montage/h480/ --width 4800 --aspect 0.5 --border 5 --debug True

Or:

$ python3 pack.py --input_dir ~/Downloads/montage/h480/ --width 2500 --aspect 1.2 --border 5

This gives us more control of the montage.

Enjoy!
Roy.

Share

Mastering OpenCV 4 - my new book!

mastering opencv4
I'm very excited to announce the publication of my latest Mastering OpenCV book!

With many new chapters and all the others re-written practically from scratch, this edition is by far the best ever.

The excellent David Millán Escrivá and I go deep and wide across the range of capabilities of OpenCV, explaining the theory and implementing recent real-world vision tasks from the ground up.
It's been baking for many months in the oven, rising slowly, and finally ready for consumption... yum!

The sources are free to grab: https://github.com/PacktPublishing/Mastering-OpenCV-4-Third-Edition

And copies are available on

Amazon: https://amzn.to/2Ff1mmE

Packt: https://www.packtpub.com/application-development/mastering-opencv-4-third-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789533576

Enjoy reading!

Share

Cylindrical image warping for panorama stitching

Hey-o
Just sharing a code snippet to warp images to cylindrical coordinates, in case you're stitching panoramas in Python OpenCV...

This is an improved version from what I had in class some time ago... http://hi.cs.stonybrook.edu/cse-527
It runs VERY fast. No loops involved, all matrix operations. In C++ this code would look gnarly.. Thanks Numpy!

Enjoy!
Roy

Share

Take a SWIG out of the Gesture Recognition Toolkit (GRT)

Reporting on a project I worked on for the last few weeks - porting the excellent Gesture Recognition Toolkit (GRT) to Python.
Right now it's still a pull request: https://github.com/nickgillian/grt/pull/151.

Not exactly porting, rather I've simply added Python bindings to GRT that allow you to access the GRT C++ APIs from Python.
Did it using the wonderful SWIG project. Such a wondrous tool, SWIG is. Magical.

Here are the deets
Continue reading "Take a SWIG out of the Gesture Recognition Toolkit (GRT)"

Share

Aligning faces with py opencv-dlib combo

Face alignment with Dlib and OpenCV

This is my first trial at using Jupyter notebook to write a post, hope it makes sense.

I've recently taught a class on generative models: http://hi.cs.stonybrook.edu/teaching/cdt450

In class we've manipulated face images with neural networks.

One important thing I found that helped is to align the images so the facial features overlap.
It helps the nets learn the variance in faces better, rather than waste their "representation power" on the shift between faces.

The following is some code to align face images using the excellent Dlib (python bindings) http://dlib.net. First I'm just using a standard face detector, and then using the facial fatures extractor I'm using that information for a complete alignment of the face.

After the alignment - I'm just having fun with the aligned dataset 🙂
Continue reading "Aligning faces with py opencv-dlib combo"

Share

Build your AWS Lambda Machine Learning Function with Docker

I've recently made a tutorial on using Docker for machine learning purposes, and I thought also to publish it in here: http://hi.cs.stonybrook.edu/teaching/docker4ml

It includes videos, slides and code, with hands-on demonstrations in class.

A GitHub repo holds the code: https://github.com/royshil/Docker4MLTutorial

I made several scripts to make it easy to upload python code that performs an ML inference ("prediction") operation on AWS Lambda.

Enjoy!
Roy.

Share

Cross Compile TensorFlow C++ app for the Jetson TK1

Last time I've posted about cross compiling TF for the TK1. That however was a canned sample example from TF, based on the bazel build system.
Let's say we want to make our own TF C++ app and just link vs. TF for inference on the TK1.
Now that's a huge mess.

First we need to cross-compile TF with everything built in.
Then we need protobuf cross-compiled for the TK1.
Bundle everything together, cross(-compile) our fingers and pray.

The prayer doesn't help. But let's see what does...
Continue reading "Cross Compile TensorFlow C++ app for the Jetson TK1"

Share

Cross-compile latest Tensorflow (1.5+) for the Nvidia Jetson TK1

Been looking around for a solid resource on how to get Tensorflow to run on the Jetson TK1. Most what I found was how to get TF 0.8 to run, which was the last TF version to allow usage of cuDNN 6 that is the latest version available for the TK1.

The TK1 is an aging platform with halted support, but it is still a cheap option for high-powered embedded compute. Unfortunately, being so outdated it's impossible to get the latest and greatest of DNN to work on the CUDA GPU on the TK1, but we can certainly use the CPU!
So a word of disclaimer - this compiled TF version will not use the GPU, just the CPU. However, it will let you run the most recent NN architectures with the latest layer implementations.

Cross compilation for the TK1 solves the acute problem of space on the device itself, as well as speed of compilation. On the other hand it required bringing up a compilation toolchain, which took a while to find.

I am going to be assuming a Ubuntu 16.04 x86_64 machine, which is what I have, and really you can do this in a VM or a Docker container just as well on Windows.

Continue reading "Cross-compile latest Tensorflow (1.5+) for the Nvidia Jetson TK1"

Share

An automatic Tensorflow-CUDA-Docker-Jupyter machine on Google Cloud Platform


For a class I'm teaching (on deep learning and art) I had to create a machine that auto starts a jupyter notebook with tensorflow and GPU support. Just create an instance and presto - Jupyter notebook with TF and GPU!
How awesome is that?

Well... building it wasn't that simple.
So for your enjoyment - here's my recipe:
Continue reading "An automatic Tensorflow-CUDA-Docker-Jupyter machine on Google Cloud Platform"

Share

Projector-Camera Calibration - the "easy" way

First let me open by saying projector-camera calibration is NOT EASY. But it's technically not complicated too.

It is however, an amalgamation of optimizations that accrue and accumulate error with each step, so that the end product is not far from a random guess.
So 3D reconstructions I was able to get from my calibrated pro-cam were just a distorted mess of points.

Nevertheless, here come the deets.
Continue reading "Projector-Camera Calibration - the "easy" way"

Share