diff options
| author | Ryan Baumann <ryan.baumann@gmail.com> | 2016-07-29 15:30:50 -0400 |
|---|---|---|
| committer | Ryan Baumann <ryan.baumann@gmail.com> | 2016-07-29 15:30:50 -0400 |
| commit | c580b52b55b4473deb278f85433dcf347ed79e24 (patch) | |
| tree | 6a4ed56ad152a79ab1483cd95c66bbe2609e2cef | |
Initial commit
| -rw-r--r-- | README.md | 16 | ||||
| -rw-r--r-- | flowFileLoader.lua | 34 | ||||
| -rwxr-xr-x | run-sterogranimator.sh | 7 | ||||
| -rwxr-xr-x | run-torchwarp.sh | 27 | ||||
| -rw-r--r-- | torch_warp.lua | 65 |
5 files changed, 149 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f4867d --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# torch-warp + +This repository contains a torch implementation for applying optical flow deformations to pairs of images in order to morph between images. The optical flow calculation and loading code is from [`manuelruder/artistic-videos`](https://github.com/manuelruder/artistic-videos), and is based on [DeepFlow](http://lear.inrialpes.fr/src/deepflow/). Theoretically, you could drop in another optical flow program which outputs `.flo` files in the [Middlebury format](http://vision.middlebury.edu/flow/data/). + +## Dependencies + +* torch7 +* DeepFlow and DeepMatching binaries in the current directory, as `deepflow2-static` and `deepmatching-static` + +I had very little luck getting DeepFlow to work on OS X, so I'm using a Docker image to run this. + +## Usage + +For input, you need two PNG images of the same dimensions named e.g. `filename_0.png` and `filename_1.png`. You can then run `./run-torchwarp.sh filename` to run all the steps and output the morphing animation as `morphed_filename.gif`. + +You can also use `./run-stereogranimator.sh ID` with an image ID from [NYPL's Stereogranimator](http://stereo.nypl.org/) to download an animated GIF and run it through the morphing process. diff --git a/flowFileLoader.lua b/flowFileLoader.lua new file mode 100644 index 0000000..f185c3c --- /dev/null +++ b/flowFileLoader.lua @@ -0,0 +1,34 @@ +require 'torch' +require 'image' + +--[[ + Reads a flow field from a binary flow file. + + bytes contents + 0-3 tag: "PIEH" in ASCII, which in little endian happens to be the float 202021.25 + (just a sanity check that floats are represented correctly) + 4-7 width as an integer + 8-11 height as an integer + 12-end data (width*height*2*4 bytes total) +--]] +local function flowFileLoader_load(fileName, scale) + local flowFile = torch.DiskFile(fileName, 'r') + flowFile:binary() + flowFile:readFloat() + local W = flowFile:readInt() + local H = flowFile:readInt() + -- image.warp needs 2xHxW, and also expects (y, x) for some reason... + local flow = torch.Tensor(2, H, W) + for y=1, H do + for x=1, W do + flow[2][y][x] = flowFile:readFloat() * scale + flow[1][y][x] = flowFile:readFloat() * scale + end + end + flowFile:close() + return flow +end + +return { + load = flowFileLoader_load +} diff --git a/run-sterogranimator.sh b/run-sterogranimator.sh new file mode 100755 index 0000000..1d27eee --- /dev/null +++ b/run-sterogranimator.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +wget -O $1.gif "http://stereo.nypl.org/view/$1.gif?n=1" + +convert $1.gif -coalesce +adjoin $1_%01d.png + +./run-torchwarp.sh $1 diff --git a/run-torchwarp.sh b/run-torchwarp.sh new file mode 100755 index 0000000..56f3cfd --- /dev/null +++ b/run-torchwarp.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +BASENAME=$(basename $1) + +convert -verbose $1_0.png PNG24:${BASENAME}_normalized_0.png +convert -verbose $1_1.png PNG24:${BASENAME}_normalized_1.png + +./makeOptFlow.sh ${BASENAME}_normalized_%01d.png $BASENAME 0 + +SEQUENCE=`seq 0.00 0.05 1.00` + +for scale in $SEQUENCE; do + echo $scale + th torch_warp.lua \ + -flow_file $BASENAME/backward_1_0.flo \ + -source_image ${BASENAME}_normalized_0.png \ + -output_image warped_${BASENAME}_0_$scale.png \ + -scale $scale + th torch_warp.lua \ + -flow_file $BASENAME/forward_0_1.flo \ + -source_image ${BASENAME}_normalized_1.png \ + -output_image warped_${BASENAME}_1_$scale.png \ + -scale $(bc <<< "1.0-$scale") + convert warped_${BASENAME}_0_$scale.png warped_${BASENAME}_1_$scale.png -compose blend -define compose:args=$(bc <<< "100*$scale/1") -composite blended_${BASENAME}_$scale.png +done + +convert $(ls blended_${BASENAME}_*.png) $(ls blended_${BASENAME}_*.png | tac | sed '1d;$d') -delay 10 -loop 0 morphed_$BASENAME.gif diff --git a/torch_warp.lua b/torch_warp.lua new file mode 100644 index 0000000..321e8cf --- /dev/null +++ b/torch_warp.lua @@ -0,0 +1,65 @@ +require 'torch' +require 'nn' +require 'image' + +local flowFile = require 'flowFileLoader' + +local cmd = torch.CmdLine() + +cmd:option('-flow_file', 'examples/example.flo', 'Target optical flow file') +cmd:option('-source_image', 'examples/example.png', 'Source image to warp') +cmd:option('-output_image', 'example_output.png', 'Destination warped image') +cmd:option('-scale', '0.5', 'Scale for optical flow, from 0-1') + +local function main(params) + local flow = flowFile.load(params.flow_file, params.scale) + local imageWarped = warpImage(image.load(params.source_image, 3), flow) + image.save(params.output_image, imageWarped) +end + +-- warp a given image according to the given optical flow. +-- Disocclusions at the borders will be filled with the VGG mean pixel. +function warpImage(img, flow) + local mean_pixel = torch.DoubleTensor({123.68/256.0, 116.779/256.0, 103.939/256.0}) + result = image.warp(img, flow, 'bilinear', true, 'pad', -1) + for x=1, result:size(2) do + for y=1, result:size(3) do + if result[1][x][y] == -1 and result[2][x][y] == -1 and result[3][x][y] == -1 then + result[1][x][y] = mean_pixel[1] + result[2][x][y] = mean_pixel[2] + result[3][x][y] = mean_pixel[3] + end + end + end + return result +end + +local tmpParams = cmd:parse(arg) +local params = nil +local file = nil + +if tmpParams.args == '' or file == nil then + params = cmd:parse(arg) +else + local args = {} + io.input(file) + local argPos = 1 + while true do + local line = io.read() + if line == nil then break end + if line:sub(0, 1) == '-' then + local splits = str_split(line, " ", 2) + args[argPos] = splits[1] + args[argPos + 1] = splits[2] + argPos = argPos + 2 + end + end + for i=1, #arg do + args[argPos] = arg[i] + argPos = argPos + 1 + end + params = cmd:parse(args) + io.close(file) +end + +main(params) |
