R/motion_sense_function.R

Defines functions motion_sense

Documented in motion_sense

#' ReVuePro: motion_sense
#'
#' 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 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 A path to ffmpeg.exe. Defaults to location called by ffmpeg_dir.
#' @param sensor_res A percent scalar between 1 and 100, used to modify video pixel resolution for motion detection. Default is 25.
#' @param output_res A percent scalar between 1 and 100, used to modify pixel resolution output images (those with motion detected). Default is 50.
#' @param saturation A percent scalar between 0 and 300, used to modify the saturation of output images. Default is 100.
#' @param brightness A percent scalar between 0 and 200, used to modify the brightness of output images. Default is 100.
#' @param bin An integer that designates how many images will be alloted to each folder, following completion of conversion. Default is 1000.
#' @param write_files A binary "TRUE/FALSE" variable that determines whether differene images will be kept.
#' @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 BiocManager doParallel EBImage gtools
#' @examples
#' source = "C:/BirdsFeeding.mp4"
#' ffmpeg.locale = "C:/Users/Rudolf/ffmpeg.exe"
#' motion_sense(input_vid = source, ffmpeg_path, ips = 10, bin = 500)
#' @export

motion_sense = function(input_vid, ffmpeg_path = "DEFAULT", sensor_res = 25, output_res = 50, saturation = 100, brightness = 100, ips = 8, write_files = "FALSE", detection_conf = 95, parallel = "TRUE"){
  
  if (file.exists(input_vid)){
  Start.Time = Sys.time()
  require("EBImage")
  require("gtools")
  require("doParallel")
  registerDoParallel()
  
  confidence_val = 0.00036*(100-detection_conf) + 0.0157

	if (ffmpeg_path == "DEFAULT"){
      ffmpeg.path = paste(read.table(paste(.libPaths()[1], "/ffmpeg/","FFMPEG_DIRECTORY.txt", sep=""))[1,1])
	} else {
	ffmpeg.path = ffmpeg_path
	}

	if (saturation > 0 & saturation < 300.1){
  output_saturation = saturation/100
	} else {
	return("Value for saturation is not accepted.") }

	if (saturation > 0 & saturation < 300.1){
  output_brightness = (brightness/100) - 1
	} else {
	return("Value for brightness is not accepted.") }

  original_wd = getwd()
  
  ## Collecting 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"){
  pull_dur = paste('"', ffmpeg.path, '"', " -i ", '"', input_vid, '"', sep = "")
    if (Sys.info()['sysname'] == "Windows"){
	if(gsub(".*\\.", "", input_vid) == "mp4"){
  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 (gsub(".*\\.", "", input_vid) == "mov"){
  vid_width = suppressWarnings(gsub("x..*", "", 
	unlist(strsplit(system(pull_dur, intern = TRUE)[grep("Stream", 
 	system(pull_dur, intern = TRUE))][2], ", "))[4]))
  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))][2], ", "))[4])))
  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 {
return("The video file extension is not supported.")
	}
    } 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"){
	 	if(gsub(".*\\.", "", input_vid) == "mp4"){
	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 (gsub(".*\\.", "", input_vid) == "mov"){
 	 vid_width = suppressWarnings(gsub("x..*", "", 
	unlist(strsplit(system(pull_dur, intern = TRUE)[grep("Stream", 
 	system(pull_dur, intern = TRUE))][2], ", "))[4]))
 	 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))][2], ", "))[4])))
 	 vid_height = as.numeric(vid_height)
	} else {
	return("Video extension not supported.")
	}
    } 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)
    }
    out.filenames = paste("./", im.folder, "/", gsub("\\..*", "",
                                                     basename(input_vid)), "_%07d.jpg", sep = "")
    ffmpeg_res = paste(vid_width*(sensor_res/100), "x", vid_width*(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.")
  }
    
  ## Creating difference images
  
  base_wd = paste(dir, "/", im.folder, sep="")
    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")
    
    if (write_files == "TRUE"){
      setwd(dir)
      dir.create("Image Subtraction")
      setwd("./Image Subtraction")
      above_threshold = c()
      With_Buffer = c()
      
      for (i in 1:across){
        subtract.im = abs(round((readImage(paste(all.images[i+1])))@.Data - 
                                  (readImage(paste(all.images[i])))@.Data, 3))
        above_threshold[i] = length(c(which(subtract.im >= 0.2)))/image_res
        writeImage(subtract.im, paste("Test_", i, ".png", sep = ""))
      }
    } else if (write_files == "FALSE"){
      above_threshold = c()
      With_Buffer = c()
      
      if (parallel == "TRUE"){
        registerDoParallel()
        above_threshold = unlist(foreach (i=1:across) %dopar% {
          subtract.im = abs(round((EBImage::readImage(paste(all.images[i+1])))@.Data - 
                                    (EBImage::readImage(paste(all.images[i])))@.Data, 3))
          length(c(which(subtract.im >= 0.2)))/image_res
        })
      } else if (parallel == "FALSE") {
        for (i in 1:across){
          subtract.im = abs(round((readImage(paste(all.images[i+1])))@.Data - 
                                    (readImage(paste(all.images[i])))@.Data, 3))
          above_threshold[i] = length(c(which(subtract.im >= 0.2)))/image_res
        }
      } else {
        return("Incorrect call for parallel parameter")
      }
    } else {
      return("Incorrect designation for write_files variable")
    }
    With_Birds = c(which(above_threshold >= confidence_val))
    
    ## Selecting files to keep
    
    if (length(With_Birds) < 1){
    return("No motion detected in at the designated confidence value.")
    } else {
    for (i in 1:length(With_Birds)){
      With_Buffer = c(With_Buffer, c((With_Birds[i]-(2*ips)):(With_Birds[i]+(2*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))
    files_with_motion = c(basename(all.images[All_To_Keep]))
    }

  Files_With_Motion = files_with_motion
  
  # Deleting all files
  
  setwd(dir)
  unlink(paste("./", im.folder, sep=""), recursive = TRUE)
  
  # Re-running ffmpeg commands
  
  dir.create(im.folder)
  
  if (parallel == "TRUE"){
    
    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 = "")
    
    output_resolution = paste(vid_width*(output_res/100), "x", vid_height*(output_res/100), sep="")

    call = c(paste('"', ffmpeg.path,  '"', " -ss ", range_1[1], " -i ", '"', input_vid, '"',
                   " -to ", range_1[2], " -r ", ips, " ", "-s ", output_resolution, " ", "-vf format=gray", " ",
                   "-vf eq=saturation=", output_saturation, " ", '"', out.filenamesR1, '"', " -loglevel quiet", sep=""),
             paste('"', ffmpeg.path, '"', " -ss ", range_2[1], " -i ", '"', input_vid, '"',
                   " -to ", range_2[2], " -r ", ips, " ", "-s ", output_resolution, " ", "-vf format=gray", " ",
                   "-vf eq=saturation=", output_saturation, " ", '"', out.filenamesR2, '"', " -loglevel quiet", sep=""))
    
    registerDoParallel()
    foreach (i=1:2) %dopar% { 
      suppressMessages(system(call[i]))
    }  
    
  } else if (parallel == "FALSE"){
  out.filenames = paste("./", im.folder, "/", gsub("\\..*", "",
                  basename(input_vid)), "_%07d.jpg", sep = "")
 
 output_resolution = paste(vid_width*(output_res/100), "x", vid_height*(output_res/100), sep="")
  
  call = paste('"', ffmpeg.path, '"', " -i ", '"', input_vid, '"',
               " -r ", ips, " ", "-s ", output_resolution, " ", "-vf eq=saturation=",
               output_saturation, " ", '"', out.filenames, '"', " -loglevel quiet", sep="")
  
  suppressMessages(system(call))
  } else {
    return("Incorrect argument for 'parallel' variable.")
  }
  # Cleaning Up Files Without Motion
  
  setwd(paste("./", im.folder, sep = ""))
  file.remove(subset(list.files(), !(list.files() %in% Files_With_Motion)))
  
  ## Return
  
  setwd(original_wd)
  End.Time = Sys.time()
  closeAllConnections()
  To.Return = paste("Motion Detection Complete After ",
              round(as.numeric(End.Time-Start.Time), 3), " ", attr(End.Time-Start.Time, "units"), ".", sep="")
  return(To.Return)
  } else {
    return("Input file does not exist.")
  }
}
joshuakrobertson/R-Package_ReVuePro documentation built on June 2, 2020, 8:23 p.m.