summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Baumann <ryan.baumann@gmail.com>2016-07-29 15:30:50 -0400
committerRyan Baumann <ryan.baumann@gmail.com>2016-07-29 15:30:50 -0400
commitc580b52b55b4473deb278f85433dcf347ed79e24 (patch)
tree6a4ed56ad152a79ab1483cd95c66bbe2609e2cef
Initial commit
-rw-r--r--README.md16
-rw-r--r--flowFileLoader.lua34
-rwxr-xr-xrun-sterogranimator.sh7
-rwxr-xr-xrun-torchwarp.sh27
-rw-r--r--torch_warp.lua65
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)