R/motion_quant_function.R

#' ReVuePro: motion_quant
#'
#' A function that detects motion in a .mp4 or .mov film, and saves images where motion was detected. This function calls ffmpeg and is currently limited to Windows and Linux architecture.
#' @details This function splits a .mp4 or .mov film into a series of jpg images of equal resolution, according to user designates images per second. Ffmpeg must be installed for this function to operate correctly. Furthermore, this function calls upon ffmpeg using Windows language, and is therefore currently limited to Windows users.
#' @param input_vid A path to the video file that one wishes to be converted.
#' @param ffmpeg_path A path to ffmpeg.exe. Default path is assigned by the ffmpeg_dir function.
#' @param sensor_res A percent scalar between 1 and 100, used to modify video pixel resolution for motion detection. Default is 25.
#' @param ips Images per second. How many jpg images one wishes to produce from one second of video footage. Default is 8 images per second.
#' @param detection_conf A percent scalar between 1 and 100, used to determine level of movement required to save an image. Default is 95.
#' @param parallel A binary "TRUE/FALSE" variable that determines whether internal operations will be run by parallel processing. If "TRUE", function will call upon the "doParallel" package. Default is "TRUE".
#' @keywords ffmpeg, mp4, jpg
#' @import doParallel EBImage ggplot2 gtools
#' @examples
#' source = "C:/BirdsFeeding.mp4"
#' ffmpeg.locale = "C:/Users/Rudolf/ffmpeg.exe"
#' motion_quant(input_vid = source, ffmpeg_path = ffmpeg.locale, ips = 10, bin = 500, plot = "TRUE")
#' @export

motion_quant = function (input_vid, ffmpeg_path = "DEFAULT", sensor_res = 25, 
    ips = 8, detection_conf = 95, parallel = "TRUE", plot = "FALSE") {
    if (file.exists(input_vid)) {
        Start.Time = Sys.time()
        require("EBImage")
        require("gtools")
        require("doParallel")
        require("ggplot2")
        registerDoParallel()
        confidence_val = 0.00036 * (100 - detection_conf) + 0.0157
        original_wd = getwd()
        if (ffmpeg_path == "DEFAULT") {
            ffmpeg.path = paste(read.table(paste(.libPaths()[1], 
                "/ffmpeg/", "FFMPEG_DIRECTORY.txt", sep = ""))[1, 
                1])
        }
        else {
            ffmpeg.path = ffmpeg_path
        }
        dir = gsub(basename(input_vid), "", input_vid)
        setwd(dir)
        im.folder = paste(gsub("\\..*", "", basename(input_vid)), 
            "motion", sep = "_")
        dir.create(im.folder)
        if (parallel == "TRUE") {
            if (Sys.info()['sysname'] == "Windows"){
            pull_dur = paste("\"", ffmpeg.path, "\"", " -i ", 
                "\"", input_vid, "\"", sep = "")
            vid_width = suppressWarnings(gsub("x..*", "", unlist(strsplit(system(pull_dur, 
                intern = TRUE)[grep("Stream", system(pull_dur, 
                intern = TRUE))], ", "))[3]))
            vid_width = as.numeric(vid_width)
            vid_height = suppressWarnings(gsub("[[:space:]].*", 
                "", gsub(".*x", "", unlist(strsplit(system(pull_dur, 
                  intern = TRUE)[grep("Stream", system(pull_dur, 
                  intern = TRUE))], ", "))[3])))
            vid_height = as.numeric(vid_height)
            Time = suppressWarnings(unlist(strsplit(gsub(".*: ", 
                "", unlist(strsplit(system(pull_dur, intern = TRUE)[grep("Duration", 
                  system(pull_dur, intern = TRUE))], ","))[1]), 
                ":")))
            Dur_Seconds = as.numeric(Time[1]) * 3600 + as.numeric(Time[2]) * 
                60 + as.numeric(Time[3])
            } else if (Sys.info()['sysname'] == "Linux"){
                dims = system(paste("ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 ",
                "\"", input_vid, "\"", sep = ""), intern = TRUE)
                vid_width = as.numeric(unlist(strsplit(dims, "x"))[1])
                vid_height = as.numeric(unlist(strsplit(dims, "x"))[2])
                time = system(paste("ffprobe -v error -select_streams v:0 -show_entries stream=duration -of csv=s=x:p=0 ",
                "\"", input_vid, "\"", sep = ""), intern = TRUE)
                Dur_Seconds = as.numeric(time)
            }
            range_1 = c(0, floor(Dur_Seconds/2))
            range_2 = c((floor(Dur_Seconds/2) + 1), floor(Dur_Seconds))
            out.filenamesR1 = paste("./", im.folder, "/", gsub("\\..*", 
                "", basename(input_vid)), "R1_%07d.jpg", sep = "")
            out.filenamesR2 = paste("./", im.folder, "/", gsub("\\..*", 
                "", basename(input_vid)), "R2_%07d.jpg", sep = "")
            ffmpeg_res = paste(vid_width * (sensor_res/100), 
                "x", vid_height * (sensor_res/100), sep = "")
            call = c(paste("\"", ffmpeg.path, "\"", " -ss ", 
                range_1[1], " -i ", "\"", input_vid, "\"", " -to ", 
                range_1[2], " -r ", ips, " ", "-s ", ffmpeg_res, 
                " ", "-vf format=gray", " ", "\"", out.filenamesR1, 
                "\"", " -loglevel quiet", sep = ""), paste("\"", 
                ffmpeg.path, "\"", " -ss ", range_2[1], " -i ", 
                "\"", input_vid, "\"", " -to ", range_2[2], " -r ", 
                ips, " ", "-s ", ffmpeg_res, " ", "-vf format=gray", 
                " ", "\"", out.filenamesR2, "\"", " -loglevel quiet", 
                sep = ""))
            registerDoParallel()
            foreach(i = 1:2) %dopar% {
                suppressMessages(system(call[i]))
            }
        }
        else if (parallel == "FALSE") {
            pull_dur = paste("\"", ffmpeg.path, "\"", " -i ", 
                "\"", input_vid, "\"", sep = "")
            if (Sys.info()['sysname'] == "Windows"){    
            vid_width = suppressWarnings(gsub("x..*", "", unlist(strsplit(system(pull_dur, 
                intern = TRUE)[grep("Stream", system(pull_dur, 
                intern = TRUE))], ", "))[3]))
            vid_width = as.numeric(vid_width)
            vid_height = suppressWarnings(gsub("[[:space:]].*", 
                "", gsub(".*x", "", unlist(strsplit(system(pull_dur, 
                  intern = TRUE)[grep("Stream", system(pull_dur, 
                  intern = TRUE))], ", "))[3])))
            vid_height = as.numeric(vid_height)
            } else if (Sys.info()['sysname'] == "Linux"){
                dims = system(paste("ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 ",
                "\"", input_vid, "\"", sep = ""), intern = TRUE)
                vid_width = as.numeric(unlist(strsplit(dims, "x"))[1])
                vid_height = as.numeric(unlist(strsplit(dims, "x"))[2])
            }
            out.filenames = paste("./", im.folder, "/", gsub("\\..*", 
                "", basename(input_vid)), "_%07d.jpg", sep = "")
            ffmpeg_res = paste(vid_width * (sensor_res/100), 
                "x", vid_height * (sensor_res/100), sep = "")
            call = paste("\"", ffmpeg.path, "\"", " -i ", "\"", 
                input_vid, "\"", " -r ", ips, " ", "-s ", ffmpeg_res, 
                " ", "-vf format=gray", " ", "\"", out.filenames, 
                "\"", " -loglevel quiet", sep = "")
            suppressMessages(system(call))
        }
        else {
            return("Incorrect argument for 'parallel' variable.")
        }
        base_wd = paste(dir, "/", im.folder, sep = "")
        Image_Sequence = c()
        For_Plotting_X = c()
        For_Plotting_Y = c()
        setwd(base_wd)
        subtract = vector("list", length(list.files()) - 1)
        all.images = c(mixedsort(list.files(full.names = TRUE)))
        across = length(all.images) - 1
        image_for_res = readImage(paste(all.images[2]))
        image_res = dim(image_for_res)[1] * dim(image_for_res)[2]
        rm("image_for_res")
        above_threshold = c()
        With_Buffer = c()
        if (parallel == "TRUE") {
            registerDoParallel()
            above_threshold = unlist(foreach(i = 1:across) %dopar% 
                {
                  bw.image_1 <- EBImage::readImage(paste(all.images[i]))
                  bw.image_2 <- EBImage::readImage(paste(all.images[i + 
                    1]))
                  subtract.im = abs(round(bw.image_2@.Data - 
                    bw.image_1@.Data, 3))
                  length(c(which(subtract.im >= 0.2)))/image_res
                })
        }
        else if (parallel == "FALSE") {
            for (i in 1:across) {
                bw.image_1 <- readImage(paste(all.images[i]))
                bw.image_2 <- readImage(paste(all.images[i + 
                  1]))
                subtract.im = abs(round(bw.image_2@.Data - bw.image_1@.Data, 
                  3))
                above_threshold[i] = length(c(which(subtract.im >= 
                  0.2)))/image_res
            }
        }
        else {
            return("Incorrect call for parallel parameter")
        }
        With_Birds = c(which(above_threshold >= confidence_val))
        Image_Sequence = c(Image_Sequence, across)
        Image_Sequence_Sum = sum(Image_Sequence)
        For_Plotting_X = c(For_Plotting_X, seq(1:Image_Sequence_Sum)/ips)
        For_Plotting_Y = c(For_Plotting_Y, above_threshold)
        if (length(With_Birds) < 1) {
            return("No motion detected at the designated confidence value.")
        }
        else {
            for (i in 1:length(With_Birds)) {
                With_Buffer = c(With_Buffer, c((With_Birds[i] - 
                  (ips)):(With_Birds[i] + (ips))))
            }
            All_To_Keep = c(unique(With_Buffer))
            All_To_Keep = subset(All_To_Keep, All_To_Keep > 0)
            All_To_Keep = subset(All_To_Keep, !is.na(All_To_Keep))
            frames_with_motion = c(length(All_To_Keep), (across + 
                1))
        }
        Files_With_Motion = frames_with_motion
        Total_Time_Sec = Files_With_Motion[2]/ips
        Movement_Time_Sec = Files_With_Motion[1]/ips
        setwd(dir)
        unlink(paste("./", im.folder, sep = ""), recursive = TRUE)
        if (plot == "TRUE") {
            my.theme = theme(panel.grid.minor = element_blank(), 
                axis.title = element_text(size = 14), axis.text = element_text(size = 14, 
                  colour = "black"), axis.title.y = element_text(angle = 0, 
                  vjust = 0.5), panel.grid.major = element_line(colour = "grey75"), 
                legend.title = element_text(size = 14, colour = "black"), 
                legend.text = element_text(size = 12, colour = "black"))
            Results_Plot = ggplot(data.frame(X = For_Plotting_X, 
                Y = For_Plotting_Y), aes(x = X, y = Y)) + theme_bw() + 
                my.theme + geom_line(colour = "dodgerblue3", 
                size = 1) + xlab("Seconds") + ylab("Proportion of Changed \n Pixels/Image") + 
                geom_hline(colour = "black", linetype = "dashed", 
                  size = 1, aes(yintercept = confidence_val))
            print(Results_Plot)
        }
        else {
            print("")
        }
        setwd(original_wd)
        End.Time = Sys.time()
        Final.Results = data.frame(`Total Analyzed Time` = Total_Time_Sec, 
            `Total Movement Time` = Movement_Time_Sec, Units = "Seconds")
        closeAllConnections()
        return(Final.Results)
    }
    else {
        return("Input file does not exist.")
    }
}
joshuakrobertson/R-Package_ReVuePro documentation built on June 2, 2020, 8:23 p.m.