def main(user_input=None):

  # Parse the command-line arguments
  if user_input is not None:
      arguments = user_input
  else:
      arguments = sys.argv[1:]

  prog = os.path.basename(sys.argv[0])
  completions = dict(prog=prog, version=version,)
  args = docopt(__doc__ % completions, argv=arguments, version='Signal extractor for videos (%s)' % version,)

  # load configuration file
  configuration = load([os.path.join(args['<configuration>'])])

  # get various parameters, either from config file or command-line 
  protocol = get_parameter(args, configuration, 'protocol', 'all')
  subset = get_parameter(args, configuration, 'subset', None)
  hrdir = get_parameter(args, configuration, 'hrdir', 'hr')
  resultdir = get_parameter(args, configuration, 'resultdir', 'results')
  overwrite = get_parameter(args, configuration, 'overwrite', False)
  plot = get_parameter(args, configuration, 'plot', False)
  verbosity_level = get_parameter(args, configuration, 'verbose', 0)
  
  # if the user wants more verbosity, lowers the logging level
  from bob.core.log import set_verbosity_level
  set_verbosity_level(logger, args['--verbose'])

  # TODO: find a way to check protocol names - Guillaume HEUSCH, 22-06-2018
  if hasattr(configuration, 'database'):
    objects = configuration.database.objects(protocol, subset)
  else:
    logger.error("Please provide a database in your configuration file !")
    sys.exit()

  # errors
  errors = []
  rmse = 0;
  mean_error_percentage = 0

  inferred = []
  ground_truth = []

  ################
  ### LET'S GO ###
  ################
  
  # if output dir exists and not overwriting, stop 
  if os.path.exists(resultdir) and not overwrite:
    logger.info("Skipping output `%s': already exists, use --overwrite to force an overwrite", resultdir)
    sys.exit()
  else: 
    bob.io.base.create_directories_safe(resultdir)

  for obj in objects:

    # load the heart rate 
    logger.debug("Loading computed heart rate from `%s'...", obj.path)
    hr_file = obj.make_path(hrdir, '.hdf5')
    try:
      hr = bob.io.base.load(hr_file)
    except (IOError, RuntimeError) as e:
      logger.warn("Skipping file `%s' (no heart rate file available)", obj.path)
      continue

    hr = hr[0]
    logger.debug("Computed heart rate : {0}".format(hr))

    # load ground truth
    gt = obj.load_heart_rate_in_bpm()
    logger.debug("Real heart rate : {0}".format(gt))
    ground_truth.append(gt)
    inferred.append(hr)
    error = hr - gt
    logger.debug("Error = {0}".format(error))
    errors.append(error)
    rmse += error**2
    mean_error_percentage += numpy.abs(error)/gt

  # compute global statistics 
  rmse /= len(errors)
  rmse = numpy.sqrt(rmse)
  rmse_text = "Root Mean Squared Error = {0:.2f}". format(rmse)
  mean_error_percentage /= len(errors)
  mean_err_percent_text = "Mean of error-rate percentage = {0:.2f}". format(mean_error_percentage)
  from scipy.stats import pearsonr
  correlation, p = pearsonr(inferred, ground_truth)
  pearson_text = "Pearson's correlation = {0:.2f} ({1:.2f} significance)". format(correlation, p)
 
  logger.info("==================")
  logger.info("=== STATISTICS ===")
  logger.info(rmse_text)
  logger.info(mean_err_percent_text)
  logger.info(pearson_text)

  # statistics in a text file
  stats_filename = os.path.join(resultdir, 'stats.txt')
  stats_file = open(stats_filename, 'w')
  stats_file.write(rmse_text + "\n")
  stats_file.write(mean_err_percent_text + "\n")
  stats_file.write(pearson_text + "\n")
  stats_file.close()

  # scatter plot
  from matplotlib import pyplot
  f = pyplot.figure()
  ax = f.add_subplot(1,1,1)
  ax.scatter(ground_truth, inferred)
  ax.plot([40, 110], [40, 110], 'r--', lw=2)
  pyplot.xlabel('Ground truth [bpm]')
  pyplot.ylabel('Estimated heart-rate [bpm]')
  ax.set_title('Scatter plot')
  scatter_file = os.path.join(resultdir, 'scatter.png')
  pyplot.savefig(scatter_file)

  # histogram of error
  f2 = pyplot.figure()
  ax2 = f2.add_subplot(1,1,1)
  ax2.hist(errors, bins=50, )
  ax2.set_title('Distribution of the error')
  distribution_file = os.path.join(resultdir, 'error_distribution.png')
  pyplot.savefig(distribution_file)

  # distribution of HR
  f3 = pyplot.figure()
  ax3 = f3.add_subplot(1,1,1)
  histoargs = {'bins': 50, 'alpha': 0.5, 'histtype': 'bar', 'range': (30, 120)} 
  pyplot.hist(ground_truth, label='Real HR', color='g', **histoargs)
  pyplot.hist(inferred, label='Estimated HR', color='b', **histoargs)
  pyplot.ylabel("Test set")

  if plot:
    pyplot.show()
  
  return 0
예제 #2
0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Signal extractor for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    pulsedir = get_parameter(args, configuration, 'pulsedir', 'pulse')
    start = get_parameter(args, configuration, 'start', 0)
    end = get_parameter(args, configuration, 'end', 0)
    motion = get_parameter(args, configuration, 'motion', 0.0)
    threshold = get_parameter(args, configuration, 'threshold', 0.5)
    skininit = get_parameter(args, configuration, 'skininit', False)
    framerate = get_parameter(args, configuration, 'framerate', 61)
    order = get_parameter(args, configuration, 'order', 128)
    window = get_parameter(args, configuration, 'window', 0)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))
        sys.exit()

    # build the bandpass filter one and for all
    bandpass_filter = build_bandpass_filter(framerate, order, plot)

    # does the actual work - for every video in the available dataset,
    # extract the signals and dumps the results to the corresponding directory
    for obj in objects:

        # expected output file
        output = obj.make_path(pulsedir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if os.path.exists(output) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load video
        video = obj.load_video(configuration.dbdir)
        logger.info("Processing input video from `%s'...", video.filename)

        # indices where to start and to end the processing
        logger.debug("Sequence length = {0}".format(len(video)))
        start_index = start
        end_index = end
        if (end_index == 0):
            end_index = len(video)
        if end_index > len(video):
            logger.warn("Skipping Sequence {0} : not long enough ({1})".format(
                obj.path, len(video)))
            continue

        # number of final frames
        nb_frames = len(video)
        if end_index > 0:
            nb_frames = end_index - start_index

        # the grayscale difference between two consecutive frames (for stable frame selection)
        if motion:
            diff_motion = numpy.zeros((nb_frames - 1, 1), dtype='float64')

        # load the result of face detection
        bounding_boxes = obj.load_face_detection()

        # skin color filter
        skin_filter = bob.ip.skincolorfilter.SkinColorFilter()

        # output data
        output_data = numpy.zeros(nb_frames, dtype='float64')
        chrom = numpy.zeros((nb_frames, 2), dtype='float64')

        # loop on video frames
        counter = 0
        for i, frame in enumerate(video):

            if i >= start_index and i < end_index:
                logger.debug("Processing frame %d/%d...", i + 1, end_index)

                try:
                    bbox = bounding_boxes[i]
                except NameError:
                    bbox, quality = bob.ip.facedetect.detect_single_face(frame)

                # motion difference (if asked for)
                if motion > 0 and (i < (len(video) - 1)) and (counter > 0):
                    current = crop_face(frame, bbox, bbox.size[1])
                    diff_motion[counter - 1] = compute_gray_diff(face, current)

                face = crop_face(frame, bbox, bbox.size[1])

                if plot and verbosity_level >= 2:
                    from matplotlib import pyplot
                    pyplot.imshow(numpy.rollaxis(numpy.rollaxis(face, 2), 2))
                    pyplot.show()

                # skin filter
                if counter == 0 or skininit:
                    skin_filter.estimate_gaussian_parameters(face)
                    logger.debug(
                        "Skin color parameters:\nmean\n{0}\ncovariance\n{1}".
                        format(skin_filter.mean, skin_filter.covariance))
                skin_mask = skin_filter.get_skin_mask(face, threshold)

                if plot and verbosity_level >= 2:
                    from matplotlib import pyplot
                    skin_mask_image = numpy.copy(face)
                    skin_mask_image[:, skin_mask] = 255
                    pyplot.imshow(
                        numpy.rollaxis(numpy.rollaxis(skin_mask_image, 2), 2))
                    pyplot.show()

                # sometimes skin is not detected !
                if numpy.count_nonzero(skin_mask) != 0:

                    # compute the mean rgb values of the skin pixels
                    r, g, b = compute_mean_rgb(face, skin_mask)
                    logger.debug(
                        "Mean color -> R = {0}, G = {1}, B = {2}".format(
                            r, g, b))

                    # project onto the chrominance colorspace
                    chrom[counter] = project_chrominance(r, g, b)
                    logger.debug("Chrominance -> X = {0}, Y = {1}".format(
                        chrom[counter][0], chrom[counter][1]))

                else:
                    logger.warn(
                        "No skin pixels detected in frame {0}, using previous value"
                        .format(i))
                    # very unlikely, but it could happened and messed up all experiments (averaging of scores ...)
                    if counter == 0:
                        chrom[counter] = project_chrominance(128., 128., 128.)
                    else:
                        chrom[counter] = chrom[counter - 1]

                counter += 1

            elif i > end_index:
                break

        # select the most stable number of consecutive frames, if asked for
        if motion > 0:
            n_stable_frames_to_keep = int(motion * nb_frames)
            logger.info(
                "Number of stable frames kept for motion -> {0}".format(
                    n_stable_frames_to_keep))
            index = select_stable_frames(diff_motion, n_stable_frames_to_keep)
            logger.info("Stable segment -> {0} - {1}".format(
                index, index + n_stable_frames_to_keep))
            chrom = chrom[index:(index + n_stable_frames_to_keep), :]

        if plot:
            from matplotlib import pyplot
            f, axarr = pyplot.subplots(2, sharex=True)
            axarr[0].plot(range(chrom.shape[0]), chrom[:, 0], 'k')
            axarr[0].set_title("X value in the chrominance subspace")
            axarr[1].plot(range(chrom.shape[0]), chrom[:, 1], 'k')
            axarr[1].set_title("Y value in the chrominance subspace")
            pyplot.show()

        # now that we have the chrominance signals, apply bandpass
        from scipy.signal import filtfilt
        x_bandpassed = numpy.zeros(nb_frames, dtype='float64')
        y_bandpassed = numpy.zeros(nb_frames, dtype='float64')
        x_bandpassed = filtfilt(bandpass_filter, numpy.array([1]), chrom[:, 0])
        y_bandpassed = filtfilt(bandpass_filter, numpy.array([1]), chrom[:, 1])

        if plot:
            from matplotlib import pyplot
            f, axarr = pyplot.subplots(2, sharex=True)
            axarr[0].plot(range(x_bandpassed.shape[0]), x_bandpassed, 'k')
            axarr[0].set_title("X bandpassed")
            axarr[1].plot(range(y_bandpassed.shape[0]), y_bandpassed, 'k')
            axarr[1].set_title("Y bandpassed")
            pyplot.show()

        # build the final pulse signal
        alpha = numpy.std(x_bandpassed) / numpy.std(y_bandpassed)
        pulse = x_bandpassed - alpha * y_bandpassed

        # overlap-add if window_size != 0
        if window > 0:
            window_size = window
            window_stride = window_size / 2
            for w in range(0, (len(pulse) - window_size), window_stride):
                pulse[w:w + window_size] = 0.0
                xw = x_bandpassed[w:w + window_size]
                yw = y_bandpassed[w:w + window_size]
                alpha = numpy.std(xw) / numpy.std(yw)
                sw = xw - alpha * yw
                sw *= numpy.hanning(window_size)
                pulse[w:w + window_size] += sw

        if plot:
            from matplotlib import pyplot
            f, axarr = pyplot.subplots(1)
            pyplot.plot(range(pulse.shape[0]), pulse, 'k')
            pyplot.title("Pulse signal")
            pyplot.show()

        output_data = pulse

        # saves the data into an HDF5 file with a '.hdf5' extension
        outdir = os.path.dirname(output)
        if not os.path.exists(outdir):
            bob.io.base.create_directories_safe(outdir)
        bob.io.base.save(output_data, output)
        logger.info("Output file saved to `%s'...", output)
예제 #3
0
def main(user_input=None):

  # Parse the command-line arguments
  if user_input is not None:
      arguments = user_input
  else:
      arguments = sys.argv[1:]

  prog = os.path.basename(sys.argv[0])
  completions = dict(prog=prog, version=version,)
  args = docopt(__doc__ % completions, argv=arguments, version='Non-rigid motion elimination for videos (%s)' % version,)

  # load configuration file
  configuration = load([os.path.join(args['<configuration>'])])

  # get various parameters, either from config file or command-line 
  protocol = get_parameter(args, configuration, 'protocol', 'all')
  subset = get_parameter(args, configuration, 'subset', None)
  illumdir = get_parameter(args, configuration, 'illumdir', 'illumination')
  motiondir = get_parameter(args, configuration, 'motiondir', 'motion')
  seglength = get_parameter(args, configuration, 'seglength', 61)
  cutoff = get_parameter(args, configuration, 'cutoff', 0.05)
  save_threshold = get_parameter(args, configuration, 'save-threshold', 'threshold.txt')
  load_threshold = get_parameter(args, configuration, 'load-threshold', '')
  cvpr14 = get_parameter(args, configuration, 'cvpr14', False)
  overwrite = get_parameter(args, configuration, 'overwrite', False)
  plot = get_parameter(args, configuration, 'plot', False)
  verbosity_level = get_parameter(args, configuration, 'verbose', 0)
 
  print(load_threshold)
  # if the user wants more verbosity, lowers the logging level
  from bob.core.log import set_verbosity_level
  set_verbosity_level(logger, args['--verbose'])

  # TODO: find a way to check protocol names - Guillaume HEUSCH, 22-06-2018
  if hasattr(configuration, 'database'):
    objects = configuration.database.objects(protocol, subset)
  else:
    logger.error("Please provide a database in your configuration file !")
    sys.exit()

  # determine the threshold for the standard deviation to be applied to the segments
  # this part is not executed if a threshold is provided
  if load_threshold == 'None':
    all_stds = []
    for obj in objects:

      # load the llumination corrected signal
      logger.debug("Computing standard deviations in color signals from `%s'...", obj.path)
      illum_file = obj.make_path(illumdir, '.hdf5')
      try:
        color = bob.io.base.load(illum_file)
      except (IOError, RuntimeError) as e:
        logger.warn("Skipping file `%s' (no color signals file available)",  obj.path)
        continue

      # skip this file if there are NaN ...
      if numpy.isnan(numpy.sum(color)):
        logger.warn("Skipping file `%s' (NaN in file)",  obj.path)
        continue
      
      # get the standard deviation in the segments
      green_segments, __ = build_segments(color, seglength)
      std_green = numpy.std(green_segments, 1, ddof=1)
      all_stds.extend(std_green.tolist())

    logger.info("Standard deviations are computed")

    # sort the std and find the 5% at the top to get the threshold
    sorted_stds = sorted(all_stds, reverse=True)
    cut_index = int(cutoff * len(all_stds)) + 1
    threshold = sorted_stds[cut_index]
    logger.info("The threshold was {0} (removing {1} percent of the largest segments)".format(threshold, 100*cutoff))

    # write threshold to file
    f = open(save_threshold, 'w')
    f.write(str(threshold))
    f.close()

  else:
    # load threshold
    f = open(load_threshold, 'r')
    threshold = float(f.readline().rstrip())

    # cut segments where the std is too large
    for obj in objects:

      # expected output file
      output = obj.make_path(motiondir, '.hdf5')

      # if output exists and not overwriting, skip this file
      if os.path.exists(output) and not overwrite:
        logger.info("Skipping output file `%s': already exists, use --overwrite to force an overwrite", output)
        continue

      # load the color signals
      logger.debug("Eliminating motion in color signals from `%s'...", obj.path)
      illum_file = obj.make_path(illumdir, '.hdf5')
      try:
        color = bob.io.base.load(illum_file)
      except (IOError, RuntimeError) as e:
        logger.warn("Skipping file `%s' (no color signals file available)",
            obj.path)
        continue
        
      # skip this file if there are NaN ...
      if numpy.isnan(numpy.sum(color)):
        logger.warn("Skipping file `%s' (NaN in file)",  obj.path)
        continue

      # divide the signals into segments
      green_segments, end_index = build_segments(color, seglength)
      # remove segments with high variability
      pruned_segments, gaps, cut_index = prune_segments(green_segments, threshold)
      
      # build final signal - but be sure that there are some segments left !
      if pruned_segments.shape[0] == 0:
        logger.warn("All segments have been discared in {0}".format(obj.path))
        continue
      if cvpr14:
        corrected_green = build_final_signal_cvpr14(pruned_segments, gaps)
      else:
        corrected_green = build_final_signal(pruned_segments, gaps)
     
      if plot:
        from matplotlib import pyplot
        f, axarr = pyplot.subplots(2, sharex=True)
        axarr[0].plot(range(end_index), color[:end_index], 'g')
        xmax, xmin, ymax, ymin = axarr[0].axis()
        for cuts in cut_index:
          axarr[0].vlines(cuts[0], ymin, ymax, color='black', linewidths='2')
          axarr[0].vlines(cuts[1], ymin, ymax, color='black', linewidths='2')
          axarr[0].plot(range(cuts[0],cuts[1]), color[cuts[0]:cuts[1]], 'r')
        axarr[0].set_title('Original color pulse')
        axarr[1].plot(range(corrected_green.shape[0]), corrected_green, 'g')
        axarr[1].set_title('Motion corrected color pulse')
        pyplot.show()

      # saves the data into an HDF5 file with a '.hdf5' extension
      outputdir = os.path.dirname(output)
      if not os.path.exists(outputdir): bob.io.base.create_directories_safe(outputdir)
      bob.io.base.save(corrected_green, output)
      logger.info("Output file saved to `%s'...", output)

  return 0
예제 #4
0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Filtering for signals (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    motiondir = get_parameter(args, configuration, 'motiondir', 'motion')
    pulsedir = get_parameter(args, configuration, 'pulsedir', 'pulse')
    Lambda = get_parameter(args, configuration, 'Lambda', 300)
    window = get_parameter(args, configuration, 'window', 23)
    framerate = get_parameter(args, configuration, 'framerate', 61)
    order = get_parameter(args, configuration, 'order', 128)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    # TODO: find a way to check protocol names - Guillaume HEUSCH, 22-06-2018
    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))
        sys.exit()

    # build the bandpass filter one and for all
    b = build_bandpass_filter(framerate, order, plot)

    ################
    ### LET'S GO ###
    ################
    for obj in objects:

        # expected output file
        output = obj.make_path(pulsedir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if os.path.exists(output) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load the corrected color signals of shape (3, nb_frames)
        logger.info("Filtering in signal from `%s'...", obj.path)
        motion_file = obj.make_path(motiondir, '.hdf5')
        try:
            motion_corrected_signal = bob.io.base.load(motion_file)
        except (IOError, RuntimeError) as e:
            logger.warn(
                "Skipping file `%s' (no motion corrected signal file available)",
                obj.path)
            continue

        # check whether the signal is long enough to be filtered with the bandpass of this order
        padlen = 3 * len(b)
        if motion_corrected_signal.shape[0] < padlen:
            logger.warn(
                "Skipping file {0} (unable to bandpass filter it, the signal is probably not long enough)"
                .format(obj.path))
            continue

        # detrend
        green_detrend = detrend(motion_corrected_signal, Lambda)
        # average
        green_averaged = average(green_detrend, window)
        # bandpass
        from scipy.signal import filtfilt
        green_bandpassed = filtfilt(b, numpy.array([1]), green_averaged)

        # plot the result
        if plot:
            from matplotlib import pyplot
            f, ax = pyplot.subplots(4, sharex=True)
            ax[0].plot(range(motion_corrected_signal.shape[0]),
                       motion_corrected_signal, 'g')
            ax[0].set_title('Original signal')
            ax[1].plot(range(motion_corrected_signal.shape[0]), green_detrend,
                       'g')
            ax[1].set_title('After detrending')
            ax[2].plot(range(motion_corrected_signal.shape[0]), green_averaged,
                       'g')
            ax[2].set_title('After averaging')
            ax[3].plot(range(motion_corrected_signal.shape[0]),
                       green_bandpassed, 'g')
            ax[3].set_title('Bandpassed signal')
            pyplot.show()

        output_data = numpy.copy(green_bandpassed)

        # saves the data into an HDF5 file with a '.hdf5' extension
        pulse_outdir = os.path.dirname(output)
        if not os.path.exists(pulse_outdir):
            bob.io.base.create_directories_safe(pulse_outdir)
        bob.io.base.save(output_data, output)
        logger.info("Output file saved to `%s'...", output)

    return 0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Illumination rectification for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    facedir = get_parameter(args, configuration, 'facedir', 'face')
    bgdir = get_parameter(args, configuration, 'bgdir', 'bg')
    illumdir = get_parameter(args, configuration, 'illumdir', 'illumination')
    start = get_parameter(args, configuration, 'start', 0)
    end = get_parameter(args, configuration, 'end', 0)
    step = get_parameter(args, configuration, 'step', 0.05)
    length = get_parameter(args, configuration, 'length', 1)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    # TODO: find a way to check protocol names - Guillaume HEUSCH, 22-06-2018
    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))
        sys.exit()

    # does the actual work - for every video in the available dataset,
    # extract the average color in both the mask area and in the backround,
    # and then correct face illumination by removing the global illumination
    for obj in objects:

        # expected output file
        output = obj.make_path(illumdir, '.hdf5')
        logger.debug("expected output file -> {0}".format(output))

        # if output exists and not overwriting, skip this file
        if os.path.exists(output) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load the color signal of the face
        face_file = obj.make_path(facedir, '.hdf5')
        try:
            face = bob.io.base.load(face_file)
        except (IOError, RuntimeError) as e:
            logger.warn("Skipping file `%s' (no face file available)",
                        obj.path)
            continue

        # load the color signal of the background
        bg_file = obj.make_path(bgdir, '.hdf5')
        try:
            bg = bob.io.base.load(bg_file)
        except (IOError, RuntimeError) as e:
            logger.warn("Skipping file `%s' (no background file available)",
                        obj.path)
            continue

        # indices where to start and to end the processing
        logger.debug("Sequence length = {0}".format(face.shape[0]))
        start_index = start
        end_index = end
        if (end_index == 0):
            end_index = face.shape[0]
        if end_index > face.shape[0]:
            logger.warn("Skipping Sequence {0} : not long enough ({1})".format(
                obj.path, face.shape[0]))
            continue

        logger.info("Processing sequence {0} ...".format(obj.path))

        # truncate the signals if needed
        face = face[start_index:end_index]
        bg = bg[start_index:end_index]
        logger.debug("Processing %d frames...", face.shape[0])

        # apply NLMS filtering
        corrected_green = rectify_illumination(face, bg, step, length)

        if plot:
            from matplotlib import pyplot
            f, axarr = pyplot.subplots(3, sharex=True)
            axarr[0].plot(range(face.shape[0]), face, 'g')
            axarr[0].set_title(
                r"$g_{face}$: average green value on the mask region")
            axarr[1].plot(range(bg.shape[0]), bg, 'g')
            axarr[1].set_title(
                r"$g_{bg}$: average green value on the background")
            axarr[2].plot(range(corrected_green.shape[0]), corrected_green,
                          'g')
            axarr[2].set_title(r"$g_{IR}$: illumination rectified signal")
            pyplot.show()

        # saves the data into an HDF5 file with a '.hdf5' extension
        outputdir = os.path.dirname(output)
        if not os.path.exists(outputdir):
            bob.io.base.create_directories_safe(outputdir)
        bob.io.base.save(corrected_green, output)
        logger.info("Output file saved to `%s'...", output)

    return 0
예제 #6
0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Signal extractor for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    pulsedir = get_parameter(args, configuration, 'pulsedir', 'pulse')
    npoints = get_parameter(args, configuration, 'npoints', 40)
    indent = get_parameter(args, configuration, 'indent', 10)
    quality = get_parameter(args, configuration, 'quality', 0.01)
    distance = get_parameter(args, configuration, 'distance', 10)
    framerate = get_parameter(args, configuration, 'framerate', 61)
    order = get_parameter(args, configuration, 'order', 128)
    window = get_parameter(args, configuration, 'window', 0)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))
        sys.exit()

    # build the bandpass filter one and for all
    bandpass_filter = build_bandpass_filter(framerate, order, plot)

    # does the actual work - for every video in the available dataset,
    # extract the signals and dumps the results to the corresponding directory
    for obj in objects:

        # expected output file
        output = obj.make_path(pulsedir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if (os.path.exists(output)) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load video
        video = obj.load_video(configuration.dbdir)
        logger.info("Processing input video from `%s'...", video.filename)

        # number of frames
        nb_frames = len(video)

        # load the result of face detection
        bounding_boxes = obj.load_face_detection()

        # output data
        output_data = numpy.zeros(nb_frames, dtype='float64')
        chrom = numpy.zeros((nb_frames, 2), dtype='float64')

        # loop on video frames
        for i, frame in enumerate(video):
            logger.debug("Processing frame %d/%d...", i + 1, len(video))

            if i == 0:
                # first frame:
                # -> load the keypoints detected by DMRF
                # -> infer the mask from the keypoints
                # -> detect the face
                # -> get "good features" inside the face
                kpts = obj.load_drmf_keypoints()
                mask_points, mask = kp66_to_mask(frame, kpts, int(indent),
                                                 plot)

                try:
                    bbox = bounding_boxes[i]
                except NameError:
                    bbox, quality = bob.ip.facedetect.detect_single_face(frame)

                # define the face width for the whole sequence
                facewidth = bbox.size[1]
                face = crop_face(frame, bbox, facewidth)
                good_features = get_good_features_to_track(
                    face, npoints, quality, distance, plot)
            else:
                # subsequent frames:
                # -> crop the face with the bounding_boxes of the previous frame (so
                #    that faces are of the same size)
                # -> get the projection of the corners detected in the previous frame
                # -> find the (affine) transformation relating previous corners with
                #    current corners
                # -> apply this transformation to the mask
                face = crop_face(frame, prev_bb, facewidth)
                good_features = track_features(prev_face, face, prev_features,
                                               plot)
                project = find_transformation(prev_features, good_features)
                if project is None:
                    logger.warn(
                        "Sequence {0}, frame {1} : No projection was found"
                        " between previous and current frame, mask from previous frame will be used"
                        .format(obj.path, i))
                else:
                    mask_points = get_current_mask_points(mask_points, project)

            # update stuff for the next frame:
            # -> the previous face is the face in this frame, with its bbox (and not
            #    with the previous one)
            # -> the features to be tracked on the next frame are re-detected
            try:
                prev_bb = bounding_boxes[i]
            except NameError:
                bb, quality = bob.ip.facedetect.detect_single_face(frame)
                prev_bb = bb

            prev_face = crop_face(frame, prev_bb, facewidth)
            prev_features = get_good_features_to_track(face, npoints, quality,
                                                       distance, plot)
            if prev_features is None:
                logger.warn(
                    "Sequence {0}, frame {1} No features to track"
                    " detected in the current frame, using the previous ones".
                    format(obj.path, i))
                prev_features = good_features

            # get the mask
            face_mask = get_mask(frame, mask_points)
            if plot and verbosity_level >= 2:
                from matplotlib import pyplot
                mask_image = numpy.copy(frame)
                mask_image[:, face_mask] = 255
                pyplot.imshow(numpy.rollaxis(numpy.rollaxis(mask_image, 2), 2))
                pyplot.show()

            # compute the mean rgb values of the pixels inside the mask
            r, g, b = compute_mean_rgb(frame, face_mask)
            # project onto the chrominance colorspace
            chrom[i] = project_chrominance(r, g, b)

        # now that we have the chrominance signals, apply bandpass
        from scipy.signal import filtfilt
        x_bandpassed = numpy.zeros(nb_frames, dtype='float64')
        y_bandpassed = numpy.zeros(nb_frames, dtype='float64')
        x_bandpassed = filtfilt(bandpass_filter, numpy.array([1]), chrom[:, 0])
        y_bandpassed = filtfilt(bandpass_filter, numpy.array([1]), chrom[:, 1])

        if plot:
            from matplotlib import pyplot
            f, axarr = pyplot.subplots(2, sharex=True)
            axarr[0].plot(range(x_bandpassed.shape[0]), x_bandpassed, 'k')
            axarr[0].set_title("X bandpassed")
            axarr[1].plot(range(y_bandpassed.shape[0]), y_bandpassed, 'k')
            axarr[1].set_title("Y bandpassed")
            pyplot.show()

        # build the final pulse signal
        alpha = numpy.std(x_bandpassed) / numpy.std(y_bandpassed)
        pulse = x_bandpassed - alpha * y_bandpassed

        # overlap-add if window_size != 0
        if int(window) > 0:
            window_size = int(window)
            window_stride = window_size / 2
            for w in range(0, (len(pulse) - window_size), window_stride):
                pulse[w:w + window_size] = 0.0
                xw = x_bandpassed[w:w + window_size]
                yw = y_bandpassed[w:w + window_size]
                alpha = numpy.std(xw) / numpy.std(yw)
                sw = xw - alpha * yw
                sw *= numpy.hanning(window_size)
                pulse[w:w + window_size] += sw

        if plot:
            from matplotlib import pyplot
            f, axarr = pyplot.subplots(1)
            pyplot.plot(range(pulse.shape[0]), pulse, 'k')
            pyplot.title("Pulse signal")
            pyplot.show()

        output_data = pulse

        # saves the data into an HDF5 file with a '.hdf5' extension
        out_pulsedir = os.path.dirname(output)
        if not os.path.exists(out_pulsedir):
            bob.io.base.create_directories_safe(out_pulsedir)
        bob.io.base.save(output_data, output)
        logger.info("Output file saved to `%s'...", output)
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Signal extractor for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    facedir = get_parameter(args, configuration, 'facedir', 'face')
    bgdir = get_parameter(args, configuration, 'bgdir', 'bg')
    npoints = get_parameter(args, configuration, 'npoints', 40)
    indent = get_parameter(args, configuration, 'indent', 10)
    quality = get_parameter(args, configuration, 'quality', 0.01)
    distance = get_parameter(args, configuration, 'distance', 10)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    wholeface = get_parameter(args, configuration, 'wholeface', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    # TODO: find a way to check protocol names - Guillaume HEUSCH, 22-06-2018
    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))
        sys.exit()

    # does the actual work - for every video in the available dataset,
    # extract the signals and dumps the results to the corresponding directory
    for obj in objects:

        # expected output file
        output_face = obj.make_path(facedir, '.hdf5')
        output_bg = obj.make_path(bgdir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if (os.path.exists(output_face)
                and os.path.exists(output_bg)) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output_face)
            continue

        # load video
        video = obj.load_video(configuration.dbdir)
        logger.info("Processing input video from `%s'...", video.filename)

        # load the result of face detection
        bounding_boxes = obj.load_face_detection()

        # average green color in the mask area
        face_color = numpy.zeros(len(video), dtype='float64')
        # average green color in the background area
        bg_color = numpy.zeros(len(video), dtype='float64')

        # loop on video frames
        for i, frame in enumerate(video):
            logger.debug("Processing frame %d/%d...", i + 1, len(video))

            if i == 0:
                # first frame:
                # -> load the keypoints detected by DMRF
                # -> infer the mask from the keypoints
                # -> detect the face
                # -> get "good features" inside the face
                if not wholeface:
                    kpts = obj.load_drmf_keypoints()
                    mask_points, mask = kp66_to_mask(frame, kpts, indent, plot)

                try:
                    bbox = bounding_boxes[i]
                except NameError:
                    bbox, quality = bob.ip.facedetect.detect_single_face(frame)

                # define the face width for the whole sequence
                facewidth = bbox.size[1]
                face = crop_face(frame, bbox, facewidth)

                if not wholeface:
                    good_features = get_good_features_to_track(
                        face, npoints, quality, distance, plot)
            else:
                # subsequent frames:
                # -> crop the face with the bounding_boxes of the previous frame (so
                #    that faces are of the same size)
                # -> get the projection of the corners detected in the previous frame
                # -> find the (affine) transformation relating previous corners with
                #    current corners
                # -> apply this transformation to the mask
                face = crop_face(frame, prev_bb, facewidth)
                if not wholeface:
                    good_features = track_features(prev_face, face,
                                                   prev_features, plot)
                    project = find_transformation(prev_features, good_features)
                    if project is None:
                        logger.warn(
                            "Sequence {0}, frame {1} : No projection was found"
                            " between previous and current frame, mask from previous frame will be used"
                            .format(obj.path, i))
                    else:
                        mask_points = get_current_mask_points(
                            mask_points, project)

            # update stuff for the next frame:
            # -> the previous face is the face in this frame, with its bbox (and not
            #    with the previous one)
            # -> the features to be tracked on the next frame are re-detected
            try:
                prev_bb = bounding_boxes[i]
            except NameError:
                bb, quality = bob.ip.facedetect.detect_single_face(frame)
                prev_bb = bb

            if not wholeface:
                prev_face = crop_face(frame, prev_bb, facewidth)
                prev_features = get_good_features_to_track(
                    face, npoints, quality, distance, plot)
                if prev_features is None:
                    logger.warn(
                        "Sequence {0}, frame {1} No features to track"
                        " detected in the current frame, using the previous ones"
                        .format(obj.path, i))
                    prev_features = good_features

                # get the bottom face region average colors
                face_mask = get_mask(frame, mask_points)
                # original algorithm: green only
                face_color[i] = compute_average_colors_mask(
                    frame, face_mask, plot)[1]
            else:
                face_color[i] = compute_average_colors_wholeface(face, plot)

            # get the background region average colors
            bg_mask = numpy.zeros((frame.shape[1], frame.shape[2]), dtype=bool)
            bg_mask[:100, :100] = True
            bg_color[i] = compute_average_colors_mask(frame, bg_mask, plot)[1]

        # saves the data into an HDF5 file with a '.hdf5' extension
        out_facedir = os.path.dirname(output_face)
        if not os.path.exists(out_facedir):
            bob.io.base.create_directories_safe(out_facedir)
        bob.io.base.save(face_color, output_face)
        logger.info("Output file saved to `%s'...", output_face)

        out_bgdir = os.path.dirname(output_bg)
        if not os.path.exists(out_bgdir):
            bob.io.base.create_directories_safe(out_bgdir)
        bob.io.base.save(bg_color, output_bg)
        logger.info("Output file saved to `%s'...", output_bg)
예제 #8
0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Signal extractor for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    pulsedir = get_parameter(args, configuration, 'pulsedir', 'pulse')
    hrdir = get_parameter(args, configuration, 'hrdir', 'hr')
    framerate = get_parameter(args, configuration, 'framerate', 61)
    nsegments = get_parameter(args, configuration, 'nsegments', 12)
    nfft = get_parameter(args, configuration, 'nfft', 8192)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, args['--verbose'])

    # TODO: find a way to check protocol names - Guillaume HEUSCH, 22-06-2018
    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    ################
    ### LET'S GO ###
    ################
    for obj in objects:

        # expected output file
        output = obj.make_path(hrdir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if os.path.exists(output) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load the filtered color signals of shape (3, nb_frames)
        logger.info("Frequency analysis of color signals from `%s'...",
                    obj.path)
        filtered_file = obj.make_path(pulsedir, '.hdf5')
        try:
            signal = bob.io.base.load(filtered_file)
        except (IOError, RuntimeError) as e:
            logger.warn("Skipping file `%s' (no color signals file available)",
                        obj.path)
            continue

        if plot:
            from matplotlib import pyplot
            pyplot.plot(range(signal.shape[0]), signal, 'g')
            pyplot.title('Filtered green signal')
            pyplot.show()

        # find the segment length, such that we have 8 50% overlapping segments (Matlab's default)
        segment_length = (2 * signal.shape[0]) // (nsegments + 1)

        # the number of points for FFT should be larger than the segment length ...
        if nfft < segment_length:
            logger.warn("Skipping file `%s' (nfft < nperseg)", obj.path)
            continue

        from scipy.signal import welch
        green_f, green_psd = welch(signal,
                                   framerate,
                                   nperseg=segment_length,
                                   nfft=nfft)

        # find the max of the frequency spectrum in the range of interest
        first = numpy.where(green_f > 0.7)[0]
        last = numpy.where(green_f < 4)[0]
        first_index = first[0]
        last_index = last[-1]
        range_of_interest = range(first_index, last_index + 1, 1)
        max_idx = numpy.argmax(green_psd[range_of_interest])
        f_max = green_f[range_of_interest[max_idx]]
        hr = f_max * 60.0
        logger.info("Heart rate = {0}".format(hr))

        if plot:
            from matplotlib import pyplot
            pyplot.semilogy(green_f, green_psd, 'g')
            xmax, xmin, ymax, ymin = pyplot.axis()
            pyplot.vlines(green_f[range_of_interest[max_idx]],
                          ymin,
                          ymax,
                          color='red')
            pyplot.title(
                'Power spectrum of the green signal (HR = {0:.1f})'.format(hr))
            pyplot.show()

        output_data = numpy.array([hr], dtype='float64')

        # saves the data into an HDF5 file with a '.hdf5' extension
        outdir = os.path.dirname(output)
        if not os.path.exists(outdir):
            bob.io.base.create_directories_safe(outdir)
        bob.io.base.save(output_data, output)
        logger.info("Output file saved to `%s'...", output)

    return 0
예제 #9
0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Signal extractor for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    pulsedir = get_parameter(args, configuration, 'pulsedir', 'pulse')
    npoints = get_parameter(args, configuration, 'npoints', 40)
    indent = get_parameter(args, configuration, 'indent', 10)
    quality = get_parameter(args, configuration, 'quality', 0.01)
    distance = get_parameter(args, configuration, 'distance', 10)
    stride = get_parameter(args, configuration, 'stride', 61)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))

    # the temporal stride
    temporal_stride = stride

    # does the actual work - for every video in the available dataset,
    # extract the signals and dumps the results to the corresponding directory
    for obj in objects:

        # expected output file
        output = obj.make_path(pulsedir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if (os.path.exists(output)) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load video
        video = obj.load_video(configuration.dbdir)
        logger.info("Processing input video from `%s'...", video.filename)
        nb_final_frames = len(video)

        # load the result of face detection
        bounding_boxes = obj.load_face_detection()

        # the result -> the pulse signal
        output_data = numpy.zeros(nb_final_frames, dtype='float64')

        # store the eigenvalues and the eigenvectors at each frame
        eigenvalues = numpy.zeros((3, nb_final_frames), dtype='float64')
        eigenvectors = numpy.zeros((3, 3, nb_final_frames), dtype='float64')

        # loop on video frames
        for i, frame in enumerate(video):
            logger.debug("Processing frame %d/%d...", i + 1, len(video))

            if i == 0:
                # first frame:
                # -> load the keypoints detected by DMRF
                # -> infer the mask from the keypoints
                # -> detect the face
                # -> get "good features" inside the face
                kpts = obj.load_drmf_keypoints()
                mask_points, mask = kp66_to_mask(frame, kpts, indent, plot)

                try:
                    bbox = bounding_boxes[i]
                except NameError:
                    bbox, quality = bob.ip.facedetect.detect_single_face(frame)

                # define the face width for the whole sequence
                facewidth = bbox.size[1]
                face = crop_face(frame, bbox, facewidth)
                good_features = get_good_features_to_track(
                    face, npoints, quality, distance, plot)
            else:
                # subsequent frames:
                # -> crop the face with the bounding_boxes of the previous frame (so
                #    that faces are of the same size)
                # -> get the projection of the corners detected in the previous frame
                # -> find the (affine) transformation relating previous corners with
                #    current corners
                # -> apply this transformation to the mask
                face = crop_face(frame, prev_bb, facewidth)
                good_features = track_features(prev_face, face, prev_features,
                                               plot)
                project = find_transformation(prev_features, good_features)
                if project is None:
                    logger.warn(
                        "Sequence {0}, frame {1} : No projection was found"
                        " between previous and current frame, mask from previous frame will be used"
                        .format(obj.path, i))
                else:
                    mask_points = get_current_mask_points(mask_points, project)

            # update stuff for the next frame:
            # -> the previous face is the face in this frame, with its bbox (and not
            #    with the previous one)
            # -> the features to be tracked on the next frame are re-detected
            try:
                prev_bb = bounding_boxes[i]
            except NameError:
                bb, quality = bob.ip.facedetect.detect_single_face(frame)
                prev_bb = bb

            prev_face = crop_face(frame, prev_bb, facewidth)
            prev_features = get_good_features_to_track(face, npoints, quality,
                                                       distance, plot)
            if prev_features is None:
                logger.warn(
                    "Sequence {0}, frame {1} No features to track"
                    " detected in the current frame, using the previous ones".
                    format(obj.path, i))
                prev_features = good_features

            # get the bottom face region
            face_mask = get_mask(frame, mask_points)

            if plot:
                from matplotlib import pyplot
                mask_image = numpy.copy(frame)
                mask_image[:, face_mask] = 255
                pyplot.title("mask pixels in frame {0}".format(i))
                pyplot.imshow(numpy.rollaxis(numpy.rollaxis(mask_image, 2), 2))
                pyplot.show()

            # get the skin pixels inside the region
            skin_pixels = frame[:, face_mask]
            skin_pixels = skin_pixels.astype('float64') / 255.0

            # build c matrix and get eigenvectors and eigenvalues
            eigenvalues[:, i], eigenvectors[:, :, i] = get_eigen(skin_pixels)

            # plot the cluster of skin pixels and eigenvectors (see Figure 1)
            if plot and verbosity_level >= 2:
                plot_eigenvectors(skin_pixels, eigenvectors[:, :, i])

            # build P and add it to the pulse signal
            if i >= temporal_stride:
                tau = i - temporal_stride
                p = build_P(i, int(stride), eigenvectors, eigenvalues)
                output_data[tau:i] += (p - numpy.mean(p))

        # plot the pulse signal
        if plot:
            import matplotlib.pyplot as plt
            fig = plt.figure()
            ax = fig.add_subplot(111)
            ax.plot(range(nb_final_frames), output_data)
            plt.show()

        # saves the data into an HDF5 file with a '.hdf5' extension
        outdir = os.path.dirname(output)
        if not os.path.exists(outdir):
            bob.io.base.create_directories_safe(outdir)
        bob.io.base.save(output_data, output)
        logger.info("Output file saved to `%s'...", output)

    return 0
예제 #10
0
def main(user_input=None):

    # Parse the command-line arguments
    if user_input is not None:
        arguments = user_input
    else:
        arguments = sys.argv[1:]

    prog = os.path.basename(sys.argv[0])
    completions = dict(
        prog=prog,
        version=version,
    )
    args = docopt(
        __doc__ % completions,
        argv=arguments,
        version='Signal extractor for videos (%s)' % version,
    )

    # load configuration file
    configuration = load([os.path.join(args['<configuration>'])])

    # get various parameters, either from config file or command-line
    protocol = get_parameter(args, configuration, 'protocol', 'all')
    subset = get_parameter(args, configuration, 'subset', None)
    skindir = get_parameter(args, configuration, 'skindir', 'skin')
    threshold = get_parameter(args, configuration, 'threshold', 0.5)
    skininit = get_parameter(args, configuration, 'skininit', False)
    overwrite = get_parameter(args, configuration, 'overwrite', False)
    plot = get_parameter(args, configuration, 'plot', False)
    gridcount = get_parameter(args, configuration, 'gridcount', False)
    verbosity_level = get_parameter(args, configuration, 'verbose', 0)

    print(protocol)
    print(type(protocol))

    # if the user wants more verbosity, lowers the logging level
    from bob.core.log import set_verbosity_level
    set_verbosity_level(logger, verbosity_level)

    if hasattr(configuration, 'database'):
        objects = configuration.database.objects(protocol, subset)
    else:
        logger.error("Please provide a database in your configuration file !")
        sys.exit()

    print(objects)

    # if we are on a grid environment, just find what I have to process.
    sge = False
    try:
        sge = os.environ.has_key('SGE_TASK_ID')  # python2
    except AttributeError:
        sge = 'SGE_TASK_ID' in os.environ  # python3

    if sge:
        pos = int(os.environ['SGE_TASK_ID']) - 1
        if pos >= len(objects):
            raise RuntimeError(
                "Grid request for job {} on a setup with {} jobs".format(
                    pos, len(objects)))
        objects = [objects[pos]]

    if gridcount:
        print(len(objects))
        sys.exit()

    # does the actual work - for every video in the available dataset,
    # extract the average color in both the mask area and in the backround,
    # and then correct face illumination by removing the global illumination
    for obj in objects:

        # expected output face file
        output = obj.make_path(skindir, '.hdf5')

        # if output exists and not overwriting, skip this file
        if os.path.exists(output) and not overwrite:
            logger.info(
                "Skipping output file `%s': already exists, use --overwrite to force an overwrite",
                output)
            continue

        # load the video sequence into a reader
        video = obj.load_video(configuration.dbdir)

        logger.info("Processing input video from `%s'...", video.filename)
        logger.debug("Sequence length = {0}".format(video.number_of_frames))

        # load the result of face detection
        bounding_boxes = obj.load_face_detection()

        # average colors of the skin color
        skin_filter = bob.ip.skincolorfilter.SkinColorFilter()
        skin_colors = numpy.zeros((len(video), 3), dtype='float64')

        ################
        ### LET'S GO ###
        ################
        for i, frame in enumerate(video):

            logger.debug("Processing frame %d / %d...", i, len(video))

            facewidth = bounding_boxes[i].size[1]
            face = crop_face(frame, bounding_boxes[i], facewidth)

            # skin filter
            if i == 0 or bool(skininit):
                skin_filter.estimate_gaussian_parameters(face)
                logger.debug(
                    "Skin color parameters:\nmean\n{0}\ncovariance\n{1}".
                    format(skin_filter.mean, skin_filter.covariance))
            skin_mask = skin_filter.get_skin_mask(face, threshold)

            if plot and i == 0:
                from matplotlib import pyplot
                skin_mask_image = numpy.copy(face)
                skin_mask_image[:, skin_mask] = 255
                pyplot.imshow(
                    numpy.rollaxis(numpy.rollaxis(skin_mask_image, 2), 2))
                pyplot.show()

            if numpy.count_nonzero(skin_mask) != 0:
                # green only
                skin_colors[i] = compute_average_colors_mask(face,
                                                             skin_mask)[1]
            else:
                logger.warn(
                    "No skin pixels detected in frame {0}, using previous value"
                    .format(i))
                if i == 0:
                    skin_colors[i] = project_chrominance(128., 128., 128.)
                else:
                    skin_colors[i] = skin_colors[i - 1]

        if plot:
            from matplotlib import pyplot

            f, axarr = pyplot.subplots(3, sharex=True)
            axarr[0].plot(range(skin_colors.shape[0]), skin_colors[:, 0], 'r')
            axarr[0].set_title("Average red value of the skin pixels")
            axarr[1].plot(range(skin_colors.shape[0]), skin_colors[:, 1], 'g')
            axarr[1].set_title("Average green value of the skin pixels")
            axarr[2].plot(range(skin_colors.shape[0]), skin_colors[:, 2], 'b')
            axarr[2].set_title("Average blue value of the skin pixels")

            pyplot.show()

        # saves the data into an HDF5 file with a '.hdf5' extension
        outdir = os.path.dirname(output)
        if not os.path.exists(outdir):
            bob.io.base.create_directories_safe(outdir)
        bob.io.base.save(skin_colors[:, 1], output)
        logger.info("Output file saved to `%s'...", output)

    return 0