Commit ac7d28ad authored by Matteo's avatar Matteo
Browse files

refactor Args reading with dependency injection and add doxygen docs

parent cc3faa0d
using json = nlohmann::json;
void extractIrregularityImagesForAudio(std::string outputPath, std::string videoPath, json irregularityFileInput, json &irregularityFileOutput2) {
void extractIrregularityImagesForAudio(std::string outputPath, const std::string videoPath, json irregularityFileInput, json &irregularityFileOutput2) {
// Make fromAudioAnalyser folder
int capsDirectory = fs::create_directory(outputPath + "fromAudioAnalyser/");
......
/**
* @file Irregularity.h
* @author Matteo Spanio (dev2@audioinnova.com)
* @brief Header file containing the Irregularity class
* @version 1.0
* @date 2023-05-14
*
* @copyright Copyright (c) 2023
*
*/
#ifndef IRREGULARITY_H
#define IRREGULARITY_H
#include <boost/uuid/uuid.hpp>
......
/**
* @file IrregularityFile.h
* @author Matteo Spanio (dev2@audioinnova.com)
* @brief Header file containing the IrregularityFile class
* @version 1.0
* @date 2023-05-14
*
* @copyright Copyright (c) 2023
*
*/
#ifndef IRREGULARITY_FILE_H
#define IRREGULARITY_FILE_H
......
......@@ -85,39 +85,6 @@ static json irregularityFileOutput2 {};
// RotatedRect identifying the processing area
RotatedRect rect, rectTape, rectCapstan;
// config.json parameters
struct Config {
fs::path workingPath;
string filesName;
bool brands;
float speed;
};
struct Threshold {
float percentual;
int angle;
int scale;
int pos;
};
struct SceneObject {
int minDist;
Threshold threshold;
};
static Config config;
static SceneObject tape;
static SceneObject capstan;
// Constants Paths
static const string READING_HEAD_IMG = "input/readingHead.png";
static const string CAPSTAN_TEMPLATE_IMG = "input/capstanBERIO058prova.png";
static const string CONFIG_FILE = "config/config.json";
double rotatedRectArea(RotatedRect rect) {
return rect.size.width * rect.size.height;
}
/**
* @fn void pprint(string text, string color)
* @brief Prints a text in a given color.
......@@ -129,33 +96,34 @@ void pprint(string text, string color) {
cout << color << text << END << endl;
}
/**
* @fn bool getArguments(int argc, char** argv)
* @brief Get operation arguments from command line or config.json file.
*
* @param argc Command line arguments count;
* @param argv Command line arguments.
* @return true if input configuration is valid;
* @return false otherwise.
*/
bool getArguments(int argc, char** argv) {
// Read configuration file
ifstream iConfig(CONFIG_FILE);
iConfig >> configurationFile;
if (argc == 1) {
// Read from JSON file
string working_path = configurationFile["WorkingPath"];
config = {
fs::path(working_path),
configurationFile["FilesName"],
configurationFile["Brands"],
configurationFile["Speed"]
};
struct Args {
fs::path workingPath;
string filesName;
bool brands;
float speed;
} else {
// Get from command line
Args(fs::path workingPath, string filesName, bool brands, float speed) {
this->workingPath = workingPath;
this->filesName = filesName;
this->brands = brands;
this->speed = speed;
}
~Args() {}
static Args from_file(fs::path path) {
ifstream iConfig(path);
json j;
iConfig >> j;
return Args(
fs::path(string(j["WorkingPath"])),
j["FilesName"],
j["Brands"],
j["Speed"]
);
}
static Args from_cli(int argc, char** argv) {
po::variables_map vm;
try {
po::options_description desc(
"A tool that implements MPAI CAE-ARP Video Analyser Technical Specification.\n"
......@@ -168,53 +136,41 @@ bool getArguments(int argc, char** argv) {
("files-name,f", po::value<string>()->required(), "Specify the name of the Preservation files (without extension)")
("brands,b", po::value<bool>()->required(), "Specify if the tape presents brands on its surface")
("speed,s", po::value<float>()->required(), "Specify the speed at which the tape was read");
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
if (vm.count("help")) {
cout << desc << "\n";
return false;
std::exit(EXIT_SUCCESS);
}
po::notify(vm);
// Access the stored options
config.workingPath = fs::path(vm["working-path"].as<string>());
config.filesName = vm["files-name"].as<string>();
config.brands = vm["brands"].as<bool>();
config.speed = vm["speed"].as<float>();
} catch (po::invalid_command_line_syntax& e) {
cerr << RED << BOLD << "The command line syntax is invalid: " << END << RED << e.what() << END << endl;
return false;
pprint("The command line syntax is invalid: " + string(e.what()), RED + BOLD);
std::exit(EXIT_FAILURE);
} catch (po::required_option& e) {
cerr << "Error: " << e.what() << endl;
return false;
std::exit(EXIT_FAILURE);
} catch (nlohmann::detail::type_error e) {
pprint("config.json error! " + string(e.what()), RED);
std::exit(EXIT_FAILURE);
}
return Args(
fs::path(vm["working-path"].as<string>()),
vm["files-name"].as<string>(),
vm["brands"].as<bool>(),
vm["speed"].as<float>()
);
}
};
capstan = {
configurationFile["MinDistCapstan"],
{
configurationFile["CapstanThresholdPercentual"],
configurationFile["AngleThreshCapstan"],
configurationFile["ScaleThreshCapstan"],
configurationFile["PosThreshCapstan"]
}
};
tape = {
configurationFile["MinDist"],
{
configurationFile["TapeThresholdPercentual"],
configurationFile["AngleThresh"],
configurationFile["ScaleThresh"],
configurationFile["PosThresh"]
}
};
// Constants Paths
static const string READING_HEAD_IMG = "input/readingHead.png";
static const string CAPSTAN_TEMPLATE_IMG = "input/capstanBERIO058prova.png";
static const string CONFIG_FILE = "config/config.json";
return true;
double rotatedRectArea(RotatedRect rect) {
return rect.size.width * rect.size.height;
}
/**
* @fn std::tuple<int, int, double, double, vector<Vec4f>, vector<Vec4f>> findObject(Mat model, SceneObject object)
* @brief Find the model in the scene using the Generalized Hough Transform. It returns the best matches.
......@@ -288,7 +244,7 @@ std::tuple<int, int, double, double, vector<Vec4f>, vector<Vec4f>> findObject(Ma
* @return true if some areas have been detected;
* @return false otherwise.
*/
bool findProcessingAreas(Mat myFrame) {
bool findProcessingAreas(Mat myFrame, SceneObject tape, SceneObject capstan) {
/*********************************************************************************************/
/*********************************** READING HEAD DETECTION **********************************/
......@@ -353,7 +309,6 @@ bool findProcessingAreas(Mat myFrame) {
// Read template image - it is smaller than before, therefore there is no need to downsample
Mat templateShape = imread(CAPSTAN_TEMPLATE_IMG, IMREAD_GRAYSCALE);
// templateShape = imread("../input/capstanBERIO058.png", IMREAD_GRAYSCALE);
if (useSURF) {
......@@ -506,7 +461,7 @@ Frame get_difference_for_roi(Frame previous, Frame current, RotatedRect roi) {
* @return true if a potential Irregularity has been found;
* @return false otherwise.
*/
bool frameDifference(cv::Mat prevFrame, cv::Mat currentFrame, int msToEnd) {
bool frameDifference(cv::Mat prevFrame, cv::Mat currentFrame, int msToEnd, SceneObject capstan, SceneObject tape, Args args) {
bool result = false;
/********************************** Capstan analysis *****************************************/
......@@ -586,7 +541,7 @@ bool frameDifference(cv::Mat prevFrame, cv::Mat currentFrame, int msToEnd) {
}
/***** BRANDS MANAGEMENT *****/
if (config.brands) {
if (args.brands) {
// At the beginning of the video, wait at least 5 seconds before the next Irregularity to consider it as a brand.
// It is not guaranteed that it will be the first brand, but it is generally a safe approach to have a correct image
if (firstBrand) {
......@@ -616,7 +571,7 @@ bool frameDifference(cv::Mat prevFrame, cv::Mat currentFrame, int msToEnd) {
*
* @param videoCapture the input Preservation Audio-Visual File;
*/
void processing(cv::VideoCapture videoCapture) {
void processing(cv::VideoCapture videoCapture, SceneObject capstan, SceneObject tape, Args args) {
const int video_length_ms = ((float) videoCapture.get(CAP_PROP_FRAME_COUNT) / videoCapture.get(CAP_PROP_FPS)) * 1000;
int video_current_ms = videoCapture.get(CAP_PROP_POS_MSEC);
......@@ -627,7 +582,7 @@ void processing(cv::VideoCapture videoCapture) {
// Whenever we find an Irregularity, we want to skip a lenght equal to the Studer reading head (3 cm = 1.18 inches).
int savingRate = 79; // [ms]. Time taken to cross 3 cm at 15 ips, or 1.5 cm at 7.5 ips. The considered lengths are the widths of the tape areas.
// The following condition constitutes a valid approach if the tape areas have widths always equal to the reading head
if (config.speed == 7.5)
if (args.speed == 7.5)
savingRate = 157; // Time taken to cross 3 cm at 7.5 ips
// The first frame of the video won't be processed
......@@ -636,7 +591,7 @@ void processing(cv::VideoCapture videoCapture) {
firstInstant = video_length_ms - video_current_ms;
while (videoCapture.isOpened()) {
cv::Mat frame;
Frame frame;
videoCapture >> frame;
video_current_ms = videoCapture.get(CAP_PROP_POS_MSEC);
......@@ -650,49 +605,34 @@ void processing(cv::VideoCapture videoCapture) {
if (video_current_ms == 0) // With OpenCV library, this happens at the last few frames of the video before realising that "frame" is empty.
return;
// Variables to display program status
// Display program status
int secToEnd = msToEnd / 1000;
int minToEnd = (secToEnd / 60) % 60;
secToEnd = secToEnd % 60;
string secStrToEnd = secToEnd < 10 ? "0" + to_string(secToEnd) : to_string(secToEnd);
string minStrToEnd = minToEnd < 10 ? "0" + to_string(minToEnd) : to_string(minToEnd);
string secStrToEnd = to_string(secToEnd), minStrToEnd = to_string(minToEnd);
if (minToEnd < 10)
minStrToEnd = "0" + minStrToEnd;
if (secToEnd < 10)
secStrToEnd = "0" + secStrToEnd;
// Display program status
cout << "\rIrregularities: " << savedFrames << ". ";
cout << "Remaining video time [mm:ss]: " << minStrToEnd << ":" << secStrToEnd << flush;
if ((video_current_ms - lastSaved > savingRate) && frameDifference(prevFrame, frame, msToEnd)) {
if ((video_current_ms - lastSaved > savingRate) && frameDifference(prevFrame, frame, msToEnd, capstan, tape, args)) {
// An Irregularity has been found!
// De-interlacing frame
cv::Mat oddFrame(frame.rows/2, frame.cols, CV_8UC3);
cv::Mat evenFrame(frame.rows/2, frame.cols, CV_8UC3);
utility::separateFrame(frame, oddFrame, evenFrame);
auto [odd_frame, even_frame] = frame.deinterlace();
// Extract the image corresponding to the ROIs
Point2f pts[4];
if (savingPinchRoller)
rectCapstan.points(pts);
else
rectTape.points(pts);
cv::Mat subImage(frame, cv::Rect(100, min(pts[1].y, pts[2].y), frame.cols - 100, static_cast<int>(rectTape.size.height)));
// De-interlacing
cv::Mat oddSubImage(subImage.rows/2, subImage.cols, CV_8UC3);
int evenSubImageRows = subImage.rows/2;
if (subImage.rows % 2 != 0) // If the found rectangle is of odd height, we must increase evenSubImage height by 1, otherwise we have segmentation_fault!!!
evenSubImageRows += 1;
cv::Mat evenSubImage(evenSubImageRows, subImage.cols, CV_8UC3);
utility::separateFrame(subImage, oddSubImage, evenSubImage);
savingPinchRoller ? rectCapstan.points(pts) : rectTape.points(pts);
Frame subImage(cv::Mat(frame, cv::Rect(100, min(pts[1].y, pts[2].y), frame.cols - 100, static_cast<int>(rectTape.size.height))));
// the following comment was in the previous code, if some errors occur, add this correction in the deinterlace method
// If the found rectangle is of odd height, we must increase evenSubImage height by 1, otherwise we have segmentation_fault!!!
auto [oddSubImage, evenSubImage] = subImage.deinterlace();
string timeLabel = getTimeLabel(video_current_ms, ":");
string safeTimeLabel = getTimeLabel(video_current_ms, "-");
string irregularityImageFilename = to_string(savedFrames) + "_" + safeTimeLabel + ".jpg";
cv::imwrite(irregularityImagesPath / irregularityImageFilename, oddFrame);
cv::imwrite(irregularityImagesPath / irregularityImageFilename, odd_frame); // FIXME: should it be frame? or maybe is only odd for the classification step?
// Append Irregularity information to JSON
Irregularity irreg = Irregularity(Source::Video, timeLabel);
......@@ -726,68 +666,56 @@ void processing(cv::VideoCapture videoCapture) {
*/
int main(int argc, char** argv) {
Args args = argc > 1 ? Args::from_cli(argc, argv) : Args::from_file(CONFIG_FILE);
SceneObject capstan = SceneObject::from_file(CONFIG_FILE, Object::CAPSTAN);
SceneObject tape = SceneObject::from_file(CONFIG_FILE, Object::TAPE);
json irregularityFileInput;
fs::path irregularityFileInputPath;
cv::Mat myFrame;
/*********************************************************************************************/
/*************************************** CONFIGURATION ***************************************/
/*********************************************************************************************/
// Get the input from config.json or command line
try {
bool continueExecution = getArguments(argc, argv);
if (!continueExecution) {
return 0;
}
} catch (nlohmann::detail::type_error e) {
cerr << RED << "config.json error!" << endl << e.what() << END << endl;
return -1;
}
const fs::path VIDEO_PATH = config.workingPath / "PreservationAudioVisualFile" / config.filesName;
const fs::path VIDEO_PATH = args.workingPath / "PreservationAudioVisualFile" / args.filesName;
if (files::findFileName(VIDEO_PATH, fileName, extension) == -1) {
cerr << RED << BOLD << "config.json error!" << END << endl << RED << VIDEO_PATH.string() << " cannot be found or opened." << END << endl;
return -1;
cerr << RED << BOLD << "Input error!" << END << endl << RED << VIDEO_PATH.string() << " cannot be found or opened." << END << endl;
std::exit(EXIT_FAILURE);
}
irregularityFileInputPath = config.workingPath / "temp" / fileName / "AudioAnalyser_IrregularityFileOutput1.json";
irregularityFileInputPath = args.workingPath / "temp" / fileName / "AudioAnalyser_IrregularityFileOutput1.json";
// Input JSON check
ifstream iJSON(irregularityFileInputPath);
if (iJSON.fail()) {
cerr << RED << BOLD << "config.json error!" << END << endl << RED << irregularityFileInputPath.string() << " cannot be found or opened." << END << endl;
return -1;
std::exit(EXIT_FAILURE);
}
if (config.speed != 7.5 && config.speed != 15) {
if (args.speed != 7.5 && args.speed != 15) {
cerr << RED << BOLD << "config.json error!" << END << endl << RED << "Speed parameter must be 7.5 or 15 ips." << END << endl;
return -1;
std::exit(EXIT_FAILURE);
}
if (tape.threshold.percentual < 0 || tape.threshold.percentual > 100) {
cerr << RED << BOLD << "config.json error!" << END << endl << RED << "TapeThresholdPercentual parameter must be a percentage value." << END << endl;
return -1;
std::exit(EXIT_FAILURE);
}
if (capstan.threshold.percentual < 0 || capstan.threshold.percentual > 100) {
cerr << RED << BOLD << "config.json error!" << END << endl << RED << "CapstanThresholdPercentual parameter must be a percentage value." << END << endl;
return -1;
std::exit(EXIT_FAILURE);
}
// Adjust input paramenters (considering given ones as pertinent to a speed reference = 7.5)
if (config.brands) {
if (config.speed == 15)
if (args.brands) {
if (args.speed == 15)
tape.threshold.percentual += 6;
} else
if (config.speed == 15)
if (args.speed == 15)
tape.threshold.percentual += 20;
else
tape.threshold.percentual += 21;
cout << endl;
cout << "Parameters:" << endl;
cout << " Brands: " << config.brands << endl;
cout << " Speed: " << config.speed << endl;
cout << " Brands: " << args.brands << endl;
cout << " Speed: " << args.speed << endl;
cout << " ThresholdPercentual: " << tape.threshold.percentual << endl;
cout << " ThresholdPercentualCapstan: " << capstan.threshold.percentual << endl;
cout << endl;
......@@ -800,7 +728,7 @@ int main(int argc, char** argv) {
/*********************************************************************************************/
// Make directory with fileName name
outputPath = config.workingPath / "temp" / fileName;
outputPath = args.workingPath / "temp" / fileName;
int outputFileNameDirectory = create_directory(outputPath);
irregularityImagesPath = outputPath / "IrregularityImages";
......@@ -812,8 +740,8 @@ int main(int argc, char** argv) {
cv::VideoCapture videoCapture(VIDEO_PATH);
if (!videoCapture.isOpened()) {
cerr << RED << BOLD << "Video unreadable." << END << endl;
return -1;
pprint("Video unreadable.", RED + BOLD);
std::exit(EXIT_FAILURE);
}
int frames_number = videoCapture.get(CAP_PROP_FRAME_COUNT);
......@@ -824,14 +752,14 @@ int main(int argc, char** argv) {
cout << "Video resolution: " << myFrame.cols << "x" << myFrame.rows << endl;
bool found = findProcessingAreas(myFrame);
bool found = findProcessingAreas(myFrame, tape, capstan);
// Reset frame position
videoCapture.set(CAP_PROP_POS_FRAMES, 0);
if (!found) {
pprint("Processing area not found. Try changing JSON parameters.", RED);
return -1; // Program terminated early
std::exit(EXIT_FAILURE);
}
/*********************************************************************************************/
......@@ -844,7 +772,7 @@ int main(int argc, char** argv) {
time_t startTimer, endTimer;
startTimer = time(NULL);
processing(videoCapture);
processing(videoCapture, capstan, tape, args);
endTimer = time(NULL);
float min = (endTimer - startTimer) / 60;
......
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp>
#include "utility.h"
using namespace cv;
using namespace std;
using json = nlohmann::json;
// Constructors
utility::Frame::Frame() : Mat() {}
utility::Frame::Frame(const Mat& m) : Mat(m) {}
......@@ -39,6 +44,14 @@ utility::Frame& utility::Frame::warp(cv::Mat rotationMatrix) {
cv::warpAffine(*this, *this, rotationMatrix, this->size(), INTER_CUBIC);
return *this;
}
pair<utility::Frame, utility::Frame> utility::Frame::deinterlace() const {
Frame odd_frame(cv::Mat(this->rows/2, this->cols, CV_8UC3));
Frame even_frame(cv::Mat(this->rows/2, this->cols, CV_8UC3));
utility::separateFrame(*this, odd_frame, even_frame);
return make_pair(odd_frame, even_frame);
}
void utility::detectShape(Ptr<GeneralizedHoughGuil> alg, Mat templateShape, int posThresh, vector<Vec4f> &positivePositions, Mat &positiveVotes, vector<Vec4f> &negativePositions, Mat &negativeVotes, Mat processingArea) {
......@@ -147,7 +160,7 @@ RotatedRect utility::drawShapes(Mat frame, Vec4f &positions, Scalar color, int w
return rr;
}
void utility::separateFrame(cv::Mat frame, cv::Mat &odd_frame, cv::Mat &even_frame) {
void utility::separateFrame(const cv::Mat frame, cv::Mat &odd_frame, cv::Mat &even_frame) {
int i_odd_frame = 0;
int i_even_frame = 0;
......@@ -190,4 +203,37 @@ cv::Mat utility::difference(cv::Mat &prevFrame, cv::Mat &currentFrame) {
}
}
return diff;
}
SceneObject::SceneObject(int minDist, Threshold threshold) {
this->minDist = minDist;
this->threshold = threshold;
}
SceneObject SceneObject::from_file(fs::path path, Object obj) {
ifstream iConfig(path);
json j;
iConfig >> j;
if (obj == Object::TAPE) {
return SceneObject(
j["MinDist"],
Threshold {
j["TapeThresholdPercentual"],
j["AngleThresh"],
j["ScaleThresh"],
j["PosThresh"]
}
);
} else {
return SceneObject(
j["MinDistCapstan"],
Threshold {
j["CapstanThresholdPercentual"],
j["AngleThreshCapstan"],
j["ScaleThreshCapstan"],
j["PosThreshCapstan"]
}
);
}
}
\ No newline at end of file
......@@ -27,14 +27,44 @@ namespace utility {
Frame& operator=(const Mat& m);
Frame& operator=(const Frame& f);
Frame clone() const;
/**
* @brief Downsample the image by a given factor.
*
* @param factor The factor by which the image will be downsampled.
* @return Frame& The downsampled image.
*/
Frame& downsample(int factor);
/**
* @brief Convert the image to a given color space.
*
* @param code The code of the color space to which the image will be converted.
* @return Frame& The converted image.
*/
Frame& convertColor(int code);
Frame difference(Frame& f);
/**
* @brief Crop the image to a given size, centered in a given point.
*
* @param rect_size The size of the cropped image.
* @param center The center of the cropped image.
* @return Frame& The cropped image.
*/
Frame& crop(Size rect_size, Point2f center);
/**
* @brief Warp the image using a given rotation matrix.
*
* @param rotationMatrix The rotation matrix used to warp the image.
* @return Frame& The warped image.
*/
Frame& warp(cv::Mat rotationMatrix);
/**
* @brief Deinterlace the image, returning two images, one containing the odd lines and the other containing the even lines.
*
* @return std::pair<Frame, Frame> The two images containing the odd and even lines.
*/
std::pair<Frame, Frame> deinterlace() const;
};
/**
* @fn void detectShape(Ptr<GeneralizedHoughGuil> alg, Mat templateShape, int posThresh, vector<Vec4f> &positivePositions, Mat &positiveVotes, vector<Vec4f> &negativePositions, Mat &negativeVotes, Mat processingArea)
* @brief Detects a given shape in an image, using a the OpenCV algorithm GeneralizedHoughGuil.
......@@ -75,7 +105,7 @@ namespace utility {
* @param[out] odd_frame odd plane;
* @param[out] even_frame even plane.
*/
void separateFrame(cv::Mat frame, cv::Mat &odd_frame, cv::Mat &even_frame);
void separateFrame(const cv::Mat frame, cv::Mat &odd_frame, cv::Mat &even_frame);
/**
......@@ -88,3 +118,25 @@ namespace utility {
*/
cv::Mat difference(cv::Mat &prevFrame, cv::Mat &currentFrame);
}
struct Threshold {
float percentual;
int angle;
int scale;
int pos;
};
enum Object {
TAPE,
CAPSTAN
};
struct SceneObject {
int minDist;
Threshold threshold;
SceneObject(int minDist, Threshold threshold);
~SceneObject() = default;
static SceneObject from_file(fs::path path, Object obj);
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment