script: rotate & crop

This commit is contained in:
Jehiah Czebotar
2015-12-24 16:14:05 -05:00
parent f8d4e5dad8
commit 04ab7745dd
5 changed files with 161 additions and 0 deletions

10
src/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Rotate and Crop
Adjust the rotate angle, then pick the X/Y range
```bash
julia main_rotate.jl --file=../IMG_2399_1024.MOV -r .321 -x 721 -X 1821 -y -24 -Y 201
```

33
src/gif.jl Normal file
View File

@@ -0,0 +1,33 @@
# https://github.com/mbauman/TrafficSpeed
#
# Let's create a GIF to display a snippet of the raw footage. There aren't any (to my knowledge) native
# Julia libraries to work with GIFs, but we have ImageMagick installed through BinDeps, which uses Homebrew
# since I'm on a Mac. So let's just create a simple helper function to shell out to the `convert` binary.
# Inspired by Tom Breloff's animated plots: https://github.com/tbreloff/Plots.jl/blob/master/src/animation.jl
immutable GIF
data::Vector{UInt8}
end
using Images
import Homebrew
"""
animate(f, n; fps=20, width)
Call function `f` repeatedly, `n` times. The function `f` must take one argument (the frame number),
and it must return an Image for that frame. Optionally specify the number of frames per second
and a width for proportional scaling (defaults to the actual width).
"""
function animate(f, n; fps = 20, width=0)
mktempdir() do dir
for i=1:n
img = f(i)
frame = width > 0 ? Images.imresize(img, (width, floor(Int, width/size(img, 1) * size(img, 2)))) : img
Images.save(@sprintf("%s/%06d.png", dir, i), frame)
end
speed = round(Int, 100 / fps)
run(`$(Homebrew.brew_prefix)/bin/convert -delay $speed -loop 0 $dir/*.png $dir/result.gif`)
return GIF(open(readbytes, "$dir/result.gif"))
end
end
Base.writemime(io::IO, ::MIME"text/html", g::GIF) = write(io, "<img src=\"data:image/gif;base64,$(base64encode(g.data))\" />")
Base.write(io::IO, g::GIF) = write(io, g.data)

51
src/main_rotate.jl Normal file
View File

@@ -0,0 +1,51 @@
using ArgParse
using Images
# using FixedPointNumbers, ImageMagick, Colors, Gadfly, DataFrames, ProgressMeter
# using Interpolations, AffineTransforms
import VideoIO
include("./rotate.jl")
function parse_commandline()
s = ArgParseSettings()
@add_arg_table s begin
"--file"
help = "filename"
"--rotate", "-r"
help = "rotation range"
arg_type = Float64
default = 0.0
"--x-min", "-x"
arg_type = Int
default = 0
"--x-max", "-X"
arg_type = Int
default = 0
"--y-min", "-y"
arg_type = Int
default = 0
"--y-max", "-Y"
arg_type = Int
default = 0
end
return parse_args(s)
end
function main()
parsed_args = parse_commandline()
io = VideoIO.open(parsed_args["file"])
f = VideoIO.openvideo(io)
img = read(f, Image)
# width:range, height:range
cropped = rotate_and_crop(img, parsed_args["rotate"], (parsed_args["x-min"]:parsed_args["x-max"], parsed_args["y-min"]:parsed_args["y-max"]))
isdir("../data") || mkdir("../data")
Images.save("../data/rotate-cropped.png", cropped)
run(`open ../data/rotate-cropped.png`)
end
main()

33
src/rotate.jl Normal file
View File

@@ -0,0 +1,33 @@
using Interpolations, AffineTransforms, Images
import VideoIO
"""
Rotate and crop a matrix by the angle θ.
Optional arguments:
* region - a tuple of two arrays that specify the section of the rotated image to return; defaults to the unrotated viewport
* fill - the value to use for regions that fall outside the rotated image; defaults to zero(T)
"""
function rotate_and_crop{T}(A::AbstractMatrix{T}, θ, region=(1:size(A, 1), 1:size(A, 2)), fill=zero(T))
etp = extrapolate(interpolate(A, BSpline(Linear()), OnGrid()), fill)
R = TransformedArray(etp, tformrotate(θ))
if region[1][1] == 0 && region[2][1] == 0
region = (1:size(R, 1), 1:size(R, 2))
end
Base.unsafe_getindex(R, region[1], region[2]) # Extrapolations can ignore bounds checks
end
# While the above will work for images, it may iterate through them inefficiently depending on the storage order
rotate_and_crop(A::Image, θ, region) = shareproperties(A, rotate_and_crop(A.data, θ, region))
# # This gets called often, so let's optimize it a little bit. Instead of just
# # using read, I use the internal `retrieve!` with a pre-allocated buffer.
# # This is safe since I know it's getting rotated and discarded immediately
# const _buffer = Array{UInt8}(3, size(img.data, 1), size(img.data, 2))
# function readroi(f::VideoIO.VideoReader, region=(1:size(A, 1), 1:size(A, 2)))
# VideoIO.retrieve!(f, _buffer)
# # _buffer is a 3-dimensional array (color x width x height), but by reinterpreting
# # it as RGB{UFixed8}, it becomes a matrix of colors that we can rotate
# Image(rotate_and_crop(reinterpret(RGB{UFixed8}, _buffer), 0.321, region), Dict("spatialorder"=>["x","y"]))
# end

34
src/seek.jl Normal file
View File

@@ -0,0 +1,34 @@
import VideoIO
# The VideoIO library is really great, but it's missing a random access seeking API.
# This should eventually be pushed upstream (https://github.com/kmsquire/VideoIO.jl/issues/30)
function Base.seek(s::VideoIO.VideoReader, time, video_stream=1)
pCodecContext = s.pVideoCodecContext
seek(s.avin, time, video_stream)
VideoIO.avcodec_flush_buffers(pCodecContext)
s
end
function Base.seek(avin::VideoIO.AVInput, time, video_stream = 1)
# AVFormatContext
fc = avin.apFormatContext[1]
# Get stream information
stream_info = avin.video_info[video_stream]
seek_stream_index = stream_info.stream_index0
stream = stream_info.stream
time_base = stream_info.codec_ctx.time_base
ticks_per_frame = stream_info.codec_ctx.ticks_per_frame
pos = Int(div(time*time_base.den, time_base.num*ticks_per_frame))
# println("seek $pos in $video_stream time_base:$time_base ticks_per_frame:$ticks_per_frame seek_stream_index:$seek_stream_index")
# Seek
ret = VideoIO.av_seek_frame(fc, seek_stream_index, pos, VideoIO.AVSEEK_FLAG_ANY)
ret < 0 && throw(ErrorException("Could not seek to start of stream"))
return avin
end
# While we're at it, It's very handy to know how many frames there are:
Base.length(s::VideoIO.VideoReader) = s.avin.video_info[1].stream.nb_frames