Some plumbing:

- support for 16 bit images
- preventing copy constructors for certain utilities classes
This commit is contained in:
Tadas Baltrusaitis 2017-12-15 19:56:58 +00:00
parent 45548b097a
commit 7d58b86020
20 changed files with 197 additions and 181 deletions

View File

@ -80,5 +80,5 @@ For inquiries about the commercial licensing of the OpenFace toolkit please cont
# Final remarks
I did my best to make sure that the code runs out of the box but there are always issues and I would be grateful for your understanding that this is research code and not full fledged product. However, if you encounter any problems/bugs/issues please contact me on github or by emailing me at Tadas.Baltrusaitis@cl.cam.ac.uk for any bug reports/questions/suggestions.
I did my best to make sure that the code runs out of the box but there are always issues and I would be grateful for your understanding that this is research code and not full fledged product. However, if you encounter any problems/bugs/issues please contact me on github or by emailing me at Tadas.Baltrusaitis@cl.cam.ac.uk for any bug reports/questions/suggestions. I prefer questions and bug reports on github as that provides visibility to others who might be encountering same issues or who have the same questions.

View File

@ -79,69 +79,6 @@ vector<string> get_arguments(int argc, char **argv)
return arguments;
}
// TODO rem
void create_display_image(const cv::Mat& orig, cv::Mat& display_image, LandmarkDetector::CLNF& clnf_model)
{
// Draw head pose if present and draw eye gaze as well
// preparing the visualisation image
display_image = orig.clone();
// Creating a display image
cv::Mat xs = clnf_model.detected_landmarks(cv::Rect(0, 0, 1, clnf_model.detected_landmarks.rows/2));
cv::Mat ys = clnf_model.detected_landmarks(cv::Rect(0, clnf_model.detected_landmarks.rows/2, 1, clnf_model.detected_landmarks.rows/2));
double min_x, max_x, min_y, max_y;
cv::minMaxLoc(xs, &min_x, &max_x);
cv::minMaxLoc(ys, &min_y, &max_y);
double width = max_x - min_x;
double height = max_y - min_y;
int minCropX = max((int)(min_x-width/3.0),0);
int minCropY = max((int)(min_y-height/3.0),0);
int widthCrop = min((int)(width*5.0/3.0), display_image.cols - minCropX - 1);
int heightCrop = min((int)(height*5.0/3.0), display_image.rows - minCropY - 1);
double scaling = 350.0/widthCrop;
// first crop the image
display_image = display_image(cv::Rect((int)(minCropX), (int)(minCropY), (int)(widthCrop), (int)(heightCrop)));
// now scale it
cv::resize(display_image.clone(), display_image, cv::Size(), scaling, scaling);
// Make the adjustments to points
xs = (xs - minCropX)*scaling;
ys = (ys - minCropY)*scaling;
cv::Mat shape = clnf_model.detected_landmarks.clone();
xs.copyTo(shape(cv::Rect(0, 0, 1, clnf_model.detected_landmarks.rows/2)));
ys.copyTo(shape(cv::Rect(0, clnf_model.detected_landmarks.rows/2, 1, clnf_model.detected_landmarks.rows/2)));
// Do the shifting for the hierarchical models as well
for (size_t part = 0; part < clnf_model.hierarchical_models.size(); ++part)
{
cv::Mat xs = clnf_model.hierarchical_models[part].detected_landmarks(cv::Rect(0, 0, 1, clnf_model.hierarchical_models[part].detected_landmarks.rows / 2));
cv::Mat ys = clnf_model.hierarchical_models[part].detected_landmarks(cv::Rect(0, clnf_model.hierarchical_models[part].detected_landmarks.rows / 2, 1, clnf_model.hierarchical_models[part].detected_landmarks.rows / 2));
xs = (xs - minCropX)*scaling;
ys = (ys - minCropY)*scaling;
cv::Mat shape = clnf_model.hierarchical_models[part].detected_landmarks.clone();
xs.copyTo(shape(cv::Rect(0, 0, 1, clnf_model.hierarchical_models[part].detected_landmarks.rows / 2)));
ys.copyTo(shape(cv::Rect(0, clnf_model.hierarchical_models[part].detected_landmarks.rows / 2, 1, clnf_model.hierarchical_models[part].detected_landmarks.rows / 2)));
}
//LandmarkDetector::Draw(display_image, clnf_model);
}
int main (int argc, char **argv)
{
@ -271,7 +208,10 @@ int main (int argc, char **argv)
open_face_rec.WriteObservation();
}
visualizer.ShowObservation();
if(face_detections.size() > 0)
{
visualizer.ShowObservation();
}
// Grabbing the next frame in the sequence
captured_image = image_reader.GetNextImage();

View File

@ -159,6 +159,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\ImageCapture.h" />
<ClInclude Include="include\ImageManipulationHelpers.h" />
<ClInclude Include="include\RecorderCSV.h" />
<ClInclude Include="include\RecorderHOG.h" />
<ClInclude Include="include\RecorderOpenFace.h" />

View File

@ -68,5 +68,8 @@
<ClInclude Include="include\ImageCapture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\ImageManipulationHelpers.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -57,8 +57,6 @@ namespace Utilities
// Default constructor
ImageCapture() {};
// TODO block copy, move etc.
// Opening based on command line arguments
bool Open(std::vector<std::string>& arguments);
@ -94,9 +92,15 @@ namespace Utilities
private:
// Blocking copy and move, as it doesn't make sense to have several readers pointed at the same source
ImageCapture & operator= (const ImageCapture& other);
ImageCapture & operator= (const ImageCapture&& other);
ImageCapture(const ImageCapture&& other);
ImageCapture(const ImageCapture& other);
// Storing the latest captures
cv::Mat latest_frame;
cv::Mat latest_gray_frame;
cv::Mat_<uchar> latest_gray_frame;
// Keeping track of how many files are read and the filenames
size_t frame_num;
@ -110,7 +114,6 @@ namespace Utilities
bool image_focal_length_set;
bool image_optical_center_set;
// TODO make sure the error message makes sense
bool no_input_specified;
};

View File

@ -0,0 +1,124 @@
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017, Tadas Baltrusaitis all rights reserved.
//
// ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY
//
// BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT.
// IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE.
//
// License can be found in OpenFace-license.txt
//
// * Any publications arising from the use of this software, including but
// not limited to academic journal and conference publications, technical
// reports and manuals, must cite at least one of the following works:
//
// OpenFace: an open source facial behavior analysis toolkit
// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
// in IEEE Winter Conference on Applications of Computer Vision, 2016
//
// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
// in IEEE International. Conference on Computer Vision (ICCV), 2015
//
// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
// in Facial Expression Recognition and Analysis Challenge,
// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
//
// Constrained Local Neural Fields for robust facial landmark detection in the wild.
// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef __IMAGE_MANIPULATION_HELPERS_h_
#define __IMAGE_MANIPULATION_HELPERS_h_
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
namespace Utilities
{
//===========================================================================
// Converting between color spaces and bit depths
//===========================================================================
// Perform the conversion inplace
static void ConvertToRGB_8bit(cv::Mat& in_out)
{
if (in_out.channels() == 3 && in_out.depth() == CV_16U)
{
in_out = in_out / 256;
}
else if (in_out.channels() == 4)
{
if (in_out.depth() == CV_16U)
{
in_out = in_out / 256;
cv::cvtColor(in_out, in_out, CV_BGRA2BGR);
}
else
{
cv::cvtColor(in_out, in_out, CV_BGRA2BGR);
}
}
else if (in_out.channels() == 1)
{
if (in_out.depth() == CV_16U)
{
in_out = in_out / 256;
cv::cvtColor(in_out, in_out, CV_GRAY2BGR);
}
else
{
cv::cvtColor(in_out, in_out, CV_GRAY2BGR);
}
}
}
static void ConvertToGrayscale_8bit(const cv::Mat& in, cv::Mat& out)
{
if (in.channels() == 3)
{
// Make sure it's in a correct format
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
tmp.convertTo(out, CV_8U);
cv::cvtColor(out, out, CV_BGR2GRAY);
}
else
{
cv::cvtColor(in, out, CV_BGR2GRAY);
}
}
else if (in.channels() == 4)
{
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
tmp.convertTo(out, CV_8U);
cv::cvtColor(out, out, CV_BGRA2GRAY);
}
else
{
cv::cvtColor(in, out, CV_BGRA2GRAY);
}
}
else
{
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
tmp.convertTo(out, CV_8U);
}
else if (in.depth() == CV_8U)
{
out = in.clone();
}
}
}
}
#endif

View File

@ -68,10 +68,14 @@ namespace Utilities
const cv::Point3f& gazeDirection0, const cv::Point3f& gazeDirection1, const cv::Vec2d& gaze_angle, const std::vector<cv::Point2d>& eye_landmarks2d, const std::vector<cv::Point3d>& eye_landmarks3d,
const std::vector<std::pair<std::string, double> >& au_intensities, const std::vector<std::pair<std::string, double> >& au_occurences);
// TODO have set functions?
private:
// Blocking copy and move, as it doesn't make sense to read to write to the same file
RecorderCSV & operator= (const RecorderCSV& other);
RecorderCSV & operator= (const RecorderCSV&& other);
RecorderCSV(const RecorderCSV&& other);
RecorderCSV(const RecorderCSV& other);
// The actual output file stream that will be written
std::ofstream output_file;

View File

@ -68,6 +68,12 @@ namespace Utilities
private:
// Blocking copy and move, as it doesn't make sense to read to write to the same file
RecorderHOG & operator= (const RecorderHOG& other);
RecorderHOG & operator= (const RecorderHOG&& other);
RecorderHOG(const RecorderHOG&& other);
RecorderHOG(const RecorderHOG& other);
std::ofstream hog_file;
// Internals for recording

View File

@ -61,8 +61,6 @@ namespace Utilities
~RecorderOpenFace();
// TODO copy, assignment and move operators? Do not allow
// Closing and cleaning up the recorder
void Close();
@ -100,6 +98,12 @@ namespace Utilities
private:
// Blocking copy, assignment and move operators, as it does not make sense to save to the same location
RecorderOpenFace & operator= (const RecorderOpenFace& other);
RecorderOpenFace & operator= (const RecorderOpenFace&& other);
RecorderOpenFace(const RecorderOpenFace&& other);
RecorderOpenFace(const RecorderOpenFace& other);
// Keeping track of what to output and how to output it
const RecorderOpenFaceParameters params;

View File

@ -57,7 +57,8 @@ namespace Utilities
// Default constructor
SequenceCapture() {};
// TODO block copy, move etc.
// Destructor
~SequenceCapture();
// Opening based on command line arguments
bool Open(std::vector<std::string>& arguments);
@ -104,12 +105,18 @@ namespace Utilities
private:
// Blocking copy and move, as it doesn't make sense to have several readers pointed at the same source, and this would cause issues, especially with webcams
SequenceCapture & operator= (const SequenceCapture& other);
SequenceCapture & operator= (const SequenceCapture&& other);
SequenceCapture(const SequenceCapture&& other);
SequenceCapture(const SequenceCapture& other);
// Used for capturing webcam and video
cv::VideoCapture capture;
// Storing the latest captures
cv::Mat latest_frame;
cv::Mat latest_gray_frame;
cv::Mat_<uchar> latest_gray_frame;
// Keeping track of frame number and the files in the image sequence
size_t frame_num;

View File

@ -55,11 +55,7 @@ namespace Utilities
// The constructor for the visualizer that specifies what to visualize
Visualizer(std::vector<std::string> arguments);
Visualizer(bool vis_track, bool vis_hog, bool vis_align);
//~Visualizer();
// TODO copy, assignment and move operators? Do not allow
// Adding observations to the visualizer
// Pose related observations

View File

@ -32,7 +32,7 @@
///////////////////////////////////////////////////////////////////////////////
#include "ImageCapture.h"
#include "ImageManipulationHelpers.h"
#include <iostream>
// Boost includes
@ -177,8 +177,6 @@ bool ImageCapture::Open(std::vector<std::string>& arguments)
return false;
}
// TODO proper destructors and move constructors
bool ImageCapture::OpenImageFiles(const std::vector<std::string>& image_files, float fx, float fy, float cx, float cy)
{
@ -318,48 +316,6 @@ bool ImageCapture::OpenDirectory(std::string directory, std::string bbox_directo
}
// TODO this should be shared somewhere
void convertToGrayscale(const cv::Mat& in, cv::Mat& out)
{
if (in.channels() == 3)
{
// Make sure it's in a correct format
if (in.depth() != CV_8U)
{
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
tmp.convertTo(tmp, CV_8U);
cv::cvtColor(tmp, out, CV_BGR2GRAY);
}
}
else
{
cv::cvtColor(in, out, CV_BGR2GRAY);
}
}
else if (in.channels() == 4)
{
cv::cvtColor(in, out, CV_BGRA2GRAY);
}
else
{
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
out = tmp.clone();
}
else if (in.depth() != CV_8U)
{
in.convertTo(out, CV_8U);
}
else
{
out = in.clone();
}
}
}
void ImageCapture::SetCameraIntrinsics(float fx, float fy, float cx, float cy)
{
// If optical centers are not defined just use center of image
@ -389,6 +345,7 @@ void ImageCapture::SetCameraIntrinsics(float fx, float fy, float cx, float cy)
}
}
// Returns a read image in 3 channel RGB format, also prepares a grayscale frame if needed
cv::Mat ImageCapture::GetNextImage()
{
if (image_files.empty() || frame_num >= image_files.size())
@ -398,14 +355,18 @@ cv::Mat ImageCapture::GetNextImage()
return latest_frame;
}
latest_frame = cv::imread(image_files[frame_num], -1);
// Load the image as an 8 bit RGB
latest_frame = cv::imread(image_files[frame_num], CV_LOAD_IMAGE_COLOR);
if (latest_frame.empty())
{
ERROR_STREAM("Could not open the image: " + image_files[frame_num - 1]);
ERROR_STREAM("Could not open the image: " + image_files[frame_num]);
exit(1);
}
// Convert the latest frame to 3 channel 8 bit format if it is not in it already
//ConvertToRGB_8bit(latest_frame); TODO check
image_height = latest_frame.size().height;
image_width = latest_frame.size().width;
@ -431,7 +392,7 @@ cv::Mat ImageCapture::GetNextImage()
SetCameraIntrinsics(_fx, _fy, _cx, _cy);
// Set the grayscale frame
convertToGrayscale(latest_frame, latest_gray_frame);
ConvertToGrayscale_8bit(latest_frame, latest_gray_frame);
this->name = image_files[frame_num];

View File

@ -46,8 +46,6 @@ using namespace Utilities;
// Default constructor initializes the variables
RecorderCSV::RecorderCSV():output_file(){};
// TODO the other 4 constructors + destructors?
// Making sure full stop is used for decimal point separation
struct fullstop : std::numpunct<char> {
char do_decimal_point() const { return '.'; }
@ -178,7 +176,6 @@ bool RecorderCSV::Open(std::string output_file_name, bool is_sequence, bool outp
}
// TODO check if the stream is open
void RecorderCSV::WriteLine(int observation_count, double time_stamp, bool landmark_detection_success, double landmark_confidence,
const cv::Mat_<double>& landmarks_2D, const cv::Mat_<double>& landmarks_3D, const cv::Mat_<double>& pdm_model_params, const cv::Vec6d& rigid_shape_params, cv::Vec6d& pose_estimate,
const cv::Point3f& gazeDirection0, const cv::Point3f& gazeDirection1, const cv::Vec2d& gaze_angle, const std::vector<cv::Point2d>& eye_landmarks2d, const std::vector<cv::Point3d>& eye_landmarks3d,
@ -187,7 +184,8 @@ void RecorderCSV::WriteLine(int observation_count, double time_stamp, bool landm
if (!output_file.is_open())
{
std::cout << "The output CSV file is not open" << std::endl;
std::cout << "The output CSV file is not open, exiting" << std::endl;
exit(1);
}
// Making sure fixed and not scientific notation is used

View File

@ -40,8 +40,6 @@ using namespace Utilities;
// Default constructor initializes the variables
RecorderHOG::RecorderHOG() :hog_file() {};
// TODO the other 4 constructors + destructors?
// Opening the file and preparing the header for it
bool RecorderHOG::Open(std::string output_file_name)
{

View File

@ -187,7 +187,7 @@ RecorderOpenFace::RecorderOpenFace(const std::string in_filename, RecorderOpenFa
}
else
{
this->media_filename = path(filename).concat(".jpg").string();
this->media_filename = path(filename).concat(".bmp").string(); // TODO change back to jpg
metadata_file << "Output image:" << this->media_filename << endl;
this->media_filename = (path(record_root) / this->media_filename).string();
}

View File

@ -32,6 +32,7 @@
///////////////////////////////////////////////////////////////////////////////
#include "SequenceCapture.h"
#include "ImageManipulationHelpers.h"
#include <iostream>
@ -248,7 +249,12 @@ bool SequenceCapture::OpenWebcam(int device, int image_width, int image_height,
}
// TODO proper destructors and move constructors
// Destructor that releases the capture
SequenceCapture::~SequenceCapture()
{
if (capture.isOpened())
capture.release();
}
bool SequenceCapture::OpenVideoFile(std::string video_file, float fx, float fy, float cx, float cy)
{
@ -327,7 +333,7 @@ bool SequenceCapture::OpenImageSequence(std::string directory, float fx, float f
}
// Assume all images are same size in an image sequence
cv::Mat tmp = cv::imread(image_files[0], -1);
cv::Mat tmp = cv::imread(image_files[0], CV_LOAD_IMAGE_COLOR);
this->frame_height = tmp.size().height;
this->frame_width = tmp.size().width;
@ -346,47 +352,6 @@ bool SequenceCapture::OpenImageSequence(std::string directory, float fx, float f
}
void convertToGrayscale(const cv::Mat& in, cv::Mat& out)
{
if (in.channels() == 3)
{
// Make sure it's in a correct format
if (in.depth() != CV_8U)
{
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
tmp.convertTo(tmp, CV_8U);
cv::cvtColor(tmp, out, CV_BGR2GRAY);
}
}
else
{
cv::cvtColor(in, out, CV_BGR2GRAY);
}
}
else if (in.channels() == 4)
{
cv::cvtColor(in, out, CV_BGRA2GRAY);
}
else
{
if (in.depth() == CV_16U)
{
cv::Mat tmp = in / 256;
out = tmp.clone();
}
else if (in.depth() != CV_8U)
{
in.convertTo(out, CV_8U);
}
else
{
out = in.clone();
}
}
}
void SequenceCapture::SetCameraIntrinsics(float fx, float fy, float cx, float cy)
{
// If optical centers are not defined just use center of image
@ -450,13 +415,13 @@ cv::Mat SequenceCapture::GetNextFrame()
}
else
{
latest_frame = cv::imread(image_files[frame_num], -1);
latest_frame = cv::imread(image_files[frame_num], CV_LOAD_IMAGE_COLOR);
}
time_stamp = 0;
}
// Set the grayscale frame
convertToGrayscale(latest_frame, latest_gray_frame);
ConvertToGrayscale_8bit(latest_frame, latest_gray_frame);
frame_num++;

View File

@ -34,6 +34,7 @@
#include "Visualizer.h"
#include "VisualizationUtils.h"
#include "RotationHelpers.h"
#include "ImageManipulationHelpers.h"
// For drawing on images
#include <opencv2/imgproc.hpp>
@ -82,10 +83,15 @@ Visualizer::Visualizer(bool vis_track, bool vis_hog, bool vis_align)
this->vis_align = vis_align;
}
// Setting the image on which to draw
void Visualizer::SetImage(const cv::Mat& canvas, float fx, float fy, float cx, float cy)
{
// Convert the image to 8 bit RGB
captured_image = canvas.clone();
ConvertToRGB_8bit(captured_image);
this->fx = fx;
this->fy = fy;
this->cx = cx;

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 28 KiB