mirror of
https://github.com/jehiah/TrafficSpeed.git
synced 2025-12-24 12:47:54 +08:00
script: rotate & crop
This commit is contained in:
10
src/README.md
Normal file
10
src/README.md
Normal 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
33
src/gif.jl
Normal 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
51
src/main_rotate.jl
Normal 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
33
src/rotate.jl
Normal 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
34
src/seek.jl
Normal 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
|
||||
Reference in New Issue
Block a user