# https://dahtah.github.io/imager/imager.html
library(imager)
library(config)
library(PlaneGeometry)
# check ffmpeg present
has.ffmpeg <- function()
{
Sys.which("ffmpeg") %>% { nchar(.) > 0 }
}
# dump video frames in tempdir
load.video <- function(fname,maxSize=1,skip.to=0,frames=NULL,fps=NULL,extra.args="",verbose=FALSE)
{
if (!has.ffmpeg()) stop("Can't find ffmpeg. Please install.")
dd <- paste0(tempdir(),"/vid")
unlink(dd,recursive=TRUE)
if (!is.null(frames)) extra.args <- sprintf("%s -vframes %i ",extra.args,frames)
if (!is.null(fps)) extra.args <- sprintf("%s -vf fps=%.4d ",extra.args,fps)
arg <- sprintf("-i %s %s -ss %s ",fname,extra.args,as.character(skip.to)) %>% paste0(dd,"/image-%d.jpg")
tryCatch({
dir.create(dd)
system2("ffmpeg",arg,stdout=verbose,stderr=verbose)
fls <- dir(dd,full.names=TRUE)
if (length(fls)==0) stop("No output was generated")
ordr <- stringr::str_extract(fls,"(\\d)+\\.jpg") %>% stringr::str_split(stringr::fixed(".")) %>% purrr::map_int(~ as.integer(.[[1]])) %>% order
fls <- fls[ordr]
fls
},
finally={})
}
#plotImages <- FALSE
plotImages <- TRUE
# camera FOV
fov <- 79
# tape length (cm)
length <- 6.0
# infiltrometer diameter (cm)
diameter <- 10.0
# wall thickness (cm)
wall <- 0.5
# refractive index
rindex <- 1.49 # methacrylate
# load video frames
#vidf <- 'trama_agr_3.mp4' # centered
vidf <- '15_6_20_PF_NT.mp4' # not-centered
images <- load.video(vidf, extra.args="-q:v 1")
#############################
# read config file or calibrate
ymlf <- gsub('mp4$', 'yml', vidf)
if( file.exists(ymlf) == TRUE )
{
config <- config::get(file = ymlf)
start <- config$start
x1 <- config$x1
y1 <- config$y1
x2 <- config$x2
y2 <- config$y2
x3 <- config$x3
y3 <- config$y3
fov <- config$fov
wall <- config$wall
length <- config$length
} else {
# compute diff of first frames
motion <- c()
curr <- load.image(images[1])
for (image in images[2:50])
{
nxt <- load.image(image)
diff <- sum(abs(nxt - curr))
curr <- nxt
motion <- c(motion, diff)
}
motion <- c(motion, diff)
# plot to locate the start frame
plot(seq(1,50), motion)
loc <- graphics::locator(1, "n")
start <- round(loc$x)
# remove margins in plot
op <- par(mar = rep(0, 4))
# click two points over the horizontal line separated 6cm, i.e. from 46 to 40
image <- load.image(images[start])
plot(image)
loc <- graphics::locator(2, "n")
# zoom and click again two points
size <- 150
plot(imsub(image,x>loc$x[1]-size & xloc$y[1]-size & yloc$x[2]-size & xloc$y[2]-size & yloc4$x[1]-size & xloc4$y[1]-size & y level)
{
break
}
}
water <- j
# plot water level
if(plotImages == TRUE)
{
lines(c(x1+j-1,x1+j-1), c(horiz1+1, horiz1+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
}
#############################
# tape analysis
# locate the marks as minimums
div <- length(col2)/12
min <- c()
min[1] <- which.min(col2[rep( 1:as.integer( 1*div))])
min[2] <- which.min(col2[rep(as.integer( 1*div):as.integer( 3*div))]) + as.integer( 1*div) - 1
min[3] <- which.min(col2[rep(as.integer( 3*div):as.integer( 5*div))]) + as.integer( 3*div) - 1
min[4] <- which.min(col2[rep(as.integer( 5*div):as.integer( 7*div))]) + as.integer( 5*div) - 1
min[5] <- which.min(col2[rep(as.integer( 7*div):as.integer( 9*div))]) + as.integer( 7*div) - 1
min[6] <- which.min(col2[rep(as.integer( 9*div):as.integer(11*div))]) + as.integer( 9*div) - 1
min[7] <- which.min(col2[rep(as.integer(11*div):length(col2))]) + as.integer(11*div) - 1
for (j in 1:7)
{
# center the mark
for (k in 1:10)
{
range <- rep((min[j]-10):(min[j]+10))
if( sum(col2[range]*range)/sum(col2[range]) > min[j])
{
min[j] <- min[j] - 1
}
else
{
min[j] <- min[j] + 1
}
}
# fit to a quadratic and compute its minimum
range <- rep((min[j]-10):(min[j]+10))
d <- data.frame(x=range, y=col2[range])
model <- lm(y~x+I(x^2), data=d);
min[j] <- -model$coefficients[[2]]/2/model$coefficients[[3]]
}
# plot tape marks
if(plotImages == TRUE)
{
lines(c(x1+min[1]-1,x1+min[1]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
lines(c(x1+min[2]-1,x1+min[2]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
lines(c(x1+min[3]-1,x1+min[3]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
lines(c(x1+min[4]-1,x1+min[4]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
lines(c(x1+min[5]-1,x1+min[5]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
lines(c(x1+min[6]-1,x1+min[6]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
lines(c(x1+min[7]-1,x1+min[7]-1), c(horiz2+1, horiz2+19), col = rgb(red = 1, green = 1, blue = 0, alpha = 1.0))
}
# compute linear regression
d <- data.frame(x=min, y=c(46, 45, 44, 43, 42, 41, 40))
model <- lm(y~x, data=d)
ca <- model$coefficients[[1]]
cb <- model$coefficients[[2]]
# apply regression to the water level
watercm <- water * cb + ca
# correct water level and apply regression
water2 <- (x1+water-1)-wall*((width-(x1+water-1))/scale-width/scale*0.5)/(width/scale/tan(fov/2*pi/180)/2)*scale
water2 <- water2 - x1 + 1
water2cm <- water2 * cb + ca
# store results
resultx <- c(resultx, time)
resulty <- c(resulty, watercm)
resultz <- c(resultz, water2cm)
}
data.frame(x=resultx, y=resulty, z=resultz)