# 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)