Commit 124dd4b2 authored by Matteo Spanio's avatar Matteo Spanio
Browse files

update

parent 7944a0f3
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
\ No newline at end of file
...@@ -6,4 +6,6 @@ bin/ ...@@ -6,4 +6,6 @@ bin/
build/ build/
log.txt log.txt
venv/ venv/
__pycache__/ __pycache__/
\ No newline at end of file docs/html
docs/latex
\ No newline at end of file
[submodule "docs/doxygen-awesome-css"]
path = docs/doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git
CMAKE_MINIMUM_REQUIRED(VERSION 3.2) CMAKE_MINIMUM_REQUIRED(VERSION 3.2)
PROJECT(video_analyser) PROJECT(video_analyser)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
SET(CMAKE_CXX_STANDARD 23) SET(CMAKE_CXX_STANDARD 20)
# include(FetchContent) include(FetchContent)
# FetchContent_Declare( FetchContent_Declare(
# googletest googletest
# URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
# ) )
# # For Windows: Prevent overriding the parent project's compiler/linker settings # For Windows: Prevent overriding the parent project's compiler/linker settings
# SET(gtest_force_shared_crt ON CACHE BOOL "" FORCE) SET(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# FetchContent_MakeAvailable(googletest) FetchContent_MakeAvailable(googletest)
LINK_DIRECTORIES(/usr/local/lib) LINK_DIRECTORIES(/usr/local/lib)
add_library(analyser_lib add_library(analyser_lib
src/lib/colors.h src/lib/colors.hpp
src/lib/time.cpp src/lib/core.hpp
src/lib/time.h src/lib/core.cpp
src/lib/enums.h src/lib/detection.hpp
src/lib/enums.cpp src/lib/detection.cpp
src/lib/Irregularity.h src/lib/io.hpp
src/lib/Irregularity.cpp src/lib/io.cpp
src/lib/IrregularityFile.h src/lib/time.cpp
src/lib/IrregularityFile.cpp src/lib/time.hpp
src/lib/TimeLabel.h src/lib/enums.hpp
src/lib/TimeLabel.cpp src/lib/enums.cpp
src/utility.h src/lib/Irregularity.hpp
src/utility.cpp src/lib/Irregularity.cpp
) src/lib/IrregularityFile.hpp
src/lib/IrregularityFile.cpp
add_library(files src/lib/TimeLabel.hpp
src/lib/files.h src/lib/TimeLabel.cpp
src/lib/files.cpp src/lib/files.hpp
) src/lib/files.cpp
src/utility.hpp
FIND_PACKAGE(OpenCV REQUIRED) src/utility.cpp
FIND_PACKAGE(nlohmann_json 3.2.0 REQUIRED) )
FIND_PACKAGE(Boost COMPONENTS program_options REQUIRED)
FIND_PACKAGE(OpenCV REQUIRED)
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS}) FIND_PACKAGE(nlohmann_json 3.2.0 REQUIRED)
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR}) FIND_PACKAGE(Boost COMPONENTS program_options REQUIRED)
ADD_EXECUTABLE(video_analyser ./src/main.cpp) INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(video_analyser
${OpenCV_LIBRARIES} ADD_EXECUTABLE(video_analyser ./src/main.cpp)
nlohmann_json::nlohmann_json
${Boost_PROGRAM_OPTIONS_LIBRARY} TARGET_LINK_LIBRARIES(analyser_lib
analyser_lib ${OpenCV_LIBRARIES}
files nlohmann_json::nlohmann_json
) ${Boost_PROGRAM_OPTIONS_LIBRARY}
)
# enable_testing()
TARGET_LINK_LIBRARIES(video_analyser
# ADD_EXECUTABLE( ${OpenCV_LIBRARIES}
# test_suite nlohmann_json::nlohmann_json
# tests/irregularity_test.cpp ${Boost_PROGRAM_OPTIONS_LIBRARY}
# tests/enums_test.cpp analyser_lib
# ) )
# TARGET_LINK_LIBRARIES( enable_testing()
# test_suite
# GTest::gtest_main ADD_EXECUTABLE(
# analyser_lib test_suite
# ${OpenCV_LIBRARIES} tests/irregularity_test.cpp
# ${Boost_PROGRAM_OPTIONS_LIBRARY} tests/enums_test.cpp
# nlohmann_json::nlohmann_json tests/core_test.cpp
# ) tests/files_test.cpp
)
# include(GoogleTest)
# gtest_discover_tests(test_suite) TARGET_LINK_LIBRARIES(
test_suite
GTest::gtest_main
analyser_lib
${OpenCV_LIBRARIES}
${Boost_PROGRAM_OPTIONS_LIBRARY}
nlohmann_json::nlohmann_json
)
include(GoogleTest)
gtest_discover_tests(test_suite)
UNAME := $(shell uname)
FORMATTER = clang-format
DOCS_GEN = doxygen
FORMAT_OPT = -i -style="{BasedOnStyle: google,IndentWidth: 4,ColumnLimit: 120}"
TARGET = video_analyser
ifeq ($(UNAME), Linux)
OPEN = xdg-open
endif
ifeq ($(UNAME), Darwin)
OPEN = open
endif
ifeq ($(UNAME), Windows)
OPEN = start
endif
.PHONY: all clean test docs run build
build: build:
cmake -S . -B build cmake -S . -B build
cmake --build build cmake --build build
test: test:
cd build && ctest ./bin/test_suite
clean: clean:
rm -rf build rm -rf build
rm -rf bin rm -rf bin
rm -rf docs/html
rm -rf docs/latex
format:
$(FORMATTER) $(FORMAT_OPT) src/*.cpp src/*.h src/lib/*.cpp src/lib/*.hpp tests/*.cpp src/*.hpp
docs:
$(DOCS_GEN) docs/Doxyfile && $(OPEN) docs/html/index.html
all:
cd build && cmake .. && make
run: run:
./bin/audio_analyser ./bin/video_analyser
...@@ -2,157 +2,57 @@ ...@@ -2,157 +2,57 @@
[![MPAI CAE-ARP](https://img.shields.io/badge/MPAI%20CAE--ARP-gray?style=for-the-badge&logo=AppleMusic&logoColor=cyan&link=https://mpai.community/standards/mpai-cae/about-mpai-cae/)](https://mpai.community/standards/mpai-cae/about-mpai-cae/) [![MPAI CAE-ARP](https://img.shields.io/badge/MPAI%20CAE--ARP-gray?style=for-the-badge&logo=AppleMusic&logoColor=cyan&link=https://mpai.community/standards/mpai-cae/about-mpai-cae/)](https://mpai.community/standards/mpai-cae/about-mpai-cae/)
[TOC]
## Description ## Description
Implements the Technical Specification of [MPAI CAE-ARP](https://mpai.community/standards/mpai-cae/about-mpai-cae/#Figure2) *Video Analyser* AIM, providing: Implements the Technical Specification of [MPAI CAE-ARP](https://mpai.community/standards/mpai-cae/about-mpai-cae/#Figure2) *Video Analyser* AIM, providing:
* 2 Irregularity Files; * 2 Irregularity Files;
* Irregularity Images. * Irregularity Images.
## Getting started ## Quick start
The *Video Analyser* is written in C++23. It relies on OpenCV to elaborate Irregularity Images and on Boost C++ Libraries to create the command line interface and generate UUIDs. The configuration file is read with [nlohmann/json](https://github.com/nlohmann/json).
## Installation
[Boost C++ Libraries](https://www.boost.org) are required for creating the command line interface (with [Boost.Program_options](https://www.boost.org/doc/libs/1_81_0/doc/html/program_options.html)) and generating UUIDs (with [Uuid](https://www.boost.org/doc/libs/1_81_0/libs/uuid/doc/uuid.html)).
You can install them following [official instructions](https://www.boost.org/doc/libs/1_81_0/more/getting_started/unix-variants.html) (Boost version 1.81.0).
Boost `program_options` library shall be separately built following [these additional instructions](https://www.boost.org/doc/libs/1_81_0/more/getting_started/unix-variants.html#easy-build-and-install).
[OpenCV](https://docs.opencv.org/4.x/index.html) is required for elaborating Irregularity Images. You can install it following [official instructions](https://docs.opencv.org/3.4/d0/db2/tutorial_macos_install.html).
Finally, [nlohmann/json](https://github.com/nlohmann/json) is required for reading the configuration file. Clone the repository:
Installation instructions are under the "Integration" section.
In the root folder there is a CMakeLists.txt file that specifies the configuration for CMake:
``` ```
CMAKE_MINIMUM_REQUIRED(VERSION 3.2) git clone https://gitlab.dei.unipd.it/mpai/video-analyzer.git
PROJECT(video_analyser)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
SET(CMAKE_CXX_STANDARD 23)
LINK_DIRECTORIES(/usr/local/lib)
FIND_PACKAGE(OpenCV REQUIRED)
FIND_PACKAGE(nlohmann_json 3.2.0 REQUIRED)
FIND_PACKAGE(Boost COMPONENTS program_options REQUIRED)
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR})
ADD_EXECUTABLE(video_analyser ./src/script.cpp)
TARGET_LINK_LIBRARIES(video_analyser ${OpenCV_LIBRARIES} nlohmann_json::nlohmann_json ${Boost_PROGRAM_OPTIONS_LIBRARY})
``` ```
`LINK_DIRECTORIES` specifies the path to the installed libraries.
Make sure that the flag `${Boost_PROGRAM_OPTIONS_LIBRARY}` is present under `TARGET_LINK_LIBRARIES`.
Once the libraries are installed, you can build the *Video Analyser* moving to `/build` directory and invoking CMake commands: Install the dependencies:
- [Boost C++ Libraries](https://www.boost.org);
- [OpenCV](https://docs.opencv.org/4.x/index.html);
- [nlohmann/json](https://github.com/nlohmann/json).
Build the project from the root directory:
``` ```
cd /path/to/video/analyser/build make build
cmake ..
make
``` ```
## Usage Add the Preservation Files to the `data` directory following this structure:
Once the program is built, you should customise the configuration file `config.json`.
There are four required parameters of interest:
1. `WorkingPath` that specifies the working path where all input files are stored and where all output files will be saved;
2. `FilesName` that specifies the name of the preservation files to be considered.
3. `Brands` that specifies if the tape presents brands on its surface;
4. `Speed` that specifies the speed at which the tape was read;
There are also other required parameters which deeply influence the behaviour of the *Video Analyser* and, therefore, ***should not be modified unless with great knowledge of what you are doing***. They are:
1. `TapeThresholdPercentual` that specifies the minimum percentage of different pixels for considering the current frame under the tape ROI as a potential Irregularity;
2. `CapstanThresholdPercentual` that specifies the minimum percentage of different pixels for considering the current frame under the capstan ROI as a potential Irregularity;
3. `MinDist` that specifies the minimum distance between the centers of the detected objects for the detection of the reading head;
4. `AngleThresh` that specifies the angle votes threshold for the detection of the reading head;
5. `ScaleThresh` that specifies the scale votes threshold for the detection of the reading head;
6. `PosThresh` that specifies the position votes threshold for the detection of the reading head;
7. `MinDistCapstan` that specifies the minimum distance between the centers of the detected objects for the detection of the capstan;
8. `AngleThreshCapstan` that specifies the angle votes threshold for the detection of the capstan;
9. `ScaleThreshCapstan` that specifies the scale votes threshold for the detection of the capstan;
10. `PosThreshCapstan` that specifies the position votes threshold for the detection of the capstan.
To execute the script without issues, the inner structure of the `WorkingPath` directory shall be like:
``` ```
. data
├── AccessCopyFiles
│ └── ...
├── PreservationAudioFile ├── PreservationAudioFile
│ ├── File1.wav │ ├── File1.wav
│ ├── File2.wav │ ├── File2.wav
│ └── ... │ └── ...
├── PreservationAudioVisualFile └── PreservationAudioVisualFile
│ ├── File1.mp4 ├── File1.mp4
│ ├── File2.mp4 ├── File2.mp4
│ └── ...
├── PreservationMasterFiles
│ └── ...
└── temp
├── File1
│ ├── AudioAnalyser_IrregularityFileOutput1.json
│ ├── AudioAnalyser_IrregularityFileOutput2.json
│ ├── AudioBlocks
│ │ ├── AudioBlock1.wav
│ │ ├── AudioBlock2.wav
│ │ └── ...
│ ├── EditingList.json
│ ├── IrregularityImages
│ │ ├── IrregularityImage1.jpg
│ │ ├── IrregularityImage2.jpg
│ │ └── ...
│ ├── RestoredAudioFiles
│ │ ├── RestoredAudioFile1.wav
│ │ ├── RestoredAudioFile2.wav
│ │ └── ...
│ ├── TapeIrregularityClassifier_IrregularityFileOutput1.json
│ ├── TapeIrregularityClassifier_IrregularityFileOutput2.json
│ ├── VideoAnalyser_IrregularityFileOutput1.json
│ └── VideoAnalyser_IrregularityFileOutput2.json
├── File2
│ ├── AudioAnalyser_IrregularityFileOutput1.json
│ ├── AudioAnalyser_IrregularityFileOutput2.json
│ ├── AudioBlocks
│ │ ├── AudioBlock1.wav
│ │ ├── AudioBlock2.wav
│ │ └── ...
│ ├── EditingList.json
│ ├── IrregularityImages
│ │ ├── IrregularityImage1.jpg
│ │ ├── IrregularityImage2.jpg
│ │ └── ...
│ ├── RestoredAudioFiles
│ │ ├── RestoredAudioFile1.wav
│ │ ├── RestoredAudioFile2.wav
│ │ └── ...
│ ├── TapeIrregularityClassifier_IrregularityFileOutput1.json
│ ├── TapeIrregularityClassifier_IrregularityFileOutput2.json
│ ├── VideoAnalyser_IrregularityFileOutput1.json
│ └── VideoAnalyser_IrregularityFileOutput2.json
└── ... └── ...
``` ```
`PreservationAudioFile` and `PreservationAudioVisualFile` directories contain the input of ARP Workflow, while `AccessCopyFiles` and `PreservationMasterFiles` directories contain its output. `temp` directory is used to store all files exchanged between the AIMs within the Workflow.
Please note that:
* Corresponding input files shall present the same name;
* The name of Irregularity Files given above is ***mandatory***.
With this structure, `FilesName` parameter could be equal to `File1` or `File2`. Run the project from the root directory:
You can now launch the *Video Analyser* moving to the `/bin` directory from the command line with:
``` ```
cd /path/to/video/analyser/bin make run
./video_analyser
``` ```
Useful log information will be displayed during execution.
To enable integration in more complex workflows, it is also possible to launch the *Video Analyser* with command line arguments: ## Generate the documentation
``` Along with the source code, the documentation of the *Video Analyser* is provided in the `docs` folder. The documentation is generated with [Doxygen](https://www.doxygen.nl/index.html) and can be accessed by opening the `index.html` file in the `docs/html` folder with a browser.
./video_analyser [-h] -w WORKING_PATH -f FILES_NAME -b BRANDS -s SPEED
``` To generate the documentation, run the following command from the root folder:
If you use the `-h` flag:
``` ```
./video_analyser -h make docs
``` ```
all instructions will be displayed.
Note that Doxygen must be installed on your machine.
## Support ## Support
If you require additional information or have any problem, you can contact us at: If you require additional information or have any problem, you can contact us at:
...@@ -173,12 +73,4 @@ This project takes advantage of the following libraries: ...@@ -173,12 +73,4 @@ This project takes advantage of the following libraries:
Developed with IDE [Visual Studio Code](https://code.visualstudio.com). Developed with IDE [Visual Studio Code](https://code.visualstudio.com).
## License ## License
This project is licensed with [GNU GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.html). This project is licensed with [GNU GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.html).
\ No newline at end of file
# TODO
This section refers to the code delivered by February 2023.
- To be able to work with the "old" neural network (by Ilenya), the output images should correspond to the old "whole tape" where, from the frame judged as interesting, an area corresponding to the height of the tape was extracted (so about the height of the current rectangle) and as wide as the original frame (so 720px). This area will then have to be resized to 224x224 as in the past. If instead you decide to use the new neural network, no changes are needed;
- A resize function of the entire video should be implemented if it does not conform to the PAL standard (currently taken for granted);
- Progressive videos, which do not require deinterlacing, should be managed (in the code there are several steps that operate considering this property);
\ No newline at end of file
#ifndef FORAUDIOANALYSER_H
#define FORAUDIOANALYSER_H
#include <filesystem>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <nlohmann/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include "lib/time.hpp"
#include "lib/core.hpp"
using namespace cv;
using namespace std;
using json = nlohmann::json; using json = nlohmann::json;
namespace fs = std::filesystem;
namespace va = videoanalyser;
const string G_IMG_FOLDER_PATH = "fromAudioAnalyser";
va::Result<int> extract_irregularity_images_for_audio(std::string output_path, const std::string video_path,
json irregularity_file_input, json &irregularity_file_output) {
// Make fromAudioAnalyser folder
int caps_directory = fs::create_directory(output_path + G_IMG_FOLDER_PATH + "/");
// Open video
cv::VideoCapture videoCapture(video_path);
// Compute video length in milliseconds
int fps = videoCapture.get(CAP_PROP_FPS);
for (int i = 0; i < irregularity_file_input["Irregularities"].size(); i++) {
// Declare output image frame
cv::Mat frame;
std::string frame_path;
// Extract TimeLabel from input JSON
std::string time_label = irregularity_file_input["Irregularities"][i]["TimeLabel"];
int irr_time_in_ms = time_label_to_ms(time_label);
std::string safe_time_label = getTimeLabel(irr_time_in_ms, "-");
// Compute the frame number corresponding to the Irregularity
int irr_frame = std::round((float)(irr_time_in_ms / 1000) * fps);
try {
frame_path = output_path + G_IMG_FOLDER_PATH + "/AudioIrregularity_" + safe_time_label + ".jpg";
videoCapture.set(CAP_PROP_POS_FRAMES, irr_frame);
videoCapture >> frame;
cv::imwrite(frame_path, frame);
void extractIrregularityImagesForAudio(std::string outputPath, std::string videoPath, json irregularityFileInput, json &irregularityFileOutput2) { // Append Irregularity information to JSON
boost::uuids::uuid uuid = boost::uuids::random_generator()();
// Make fromAudioAnalyser folder irregularity_file_output["Irregularities"] +=
int capsDirectory = fs::create_directory(outputPath + "fromAudioAnalyser/"); {{"IrregularityID", irregularity_file_input["Irregularities"][i]["IrregularityID"]},
{"Source", "a"},
// Open video {"TimeLabel", time_label},
cv::VideoCapture videoCapture(videoPath); {"ImageURI", frame_path}};
} catch (cv::Exception e) {
// Compute video length in milliseconds return va::Error("TimeLabel error for Audio Analyser Irregularity " + i);
int frameCount = videoCapture.get(CAP_PROP_FRAME_COUNT); }
int fps = videoCapture.get(CAP_PROP_FPS); }
int videoLenghtMS = (frameCount / fps) * 1000 + std::round((float)((frameCount % fps) * 1000) / fps); return va::Result<int>(0);
}
for (int i = 0; i < irregularityFileInput["Irregularities"].size(); i++) { #endif // FORAUDIOANALYSER_H
// Declare output image frame \ No newline at end of file
cv::Mat frame;
std::string framePath;
// Extract TimeLabel from input JSON
std::string timeLabel = irregularityFileInput["Irregularities"][i]["TimeLabel"];
// Obtain time measures from JSON
int h = stoi(timeLabel.substr(0, 2));
int min = stoi(timeLabel.substr(3, 2));
int sec = stoi(timeLabel.substr(6, 2));
int ms = stoi(timeLabel.substr(9, 3));
std::string safeTimeLabel = timeLabel;
safeTimeLabel[2] = '-';
safeTimeLabel[5] = '-';
safeTimeLabel[8] = '-';
// Compute the Irregularity instant in milliseconds
int irrInstMS = ms + sec*1000 + min*60000 + h*3600000;
// Compute the frame number corresponding to the Irregularity
int irrFrame = std::round((float)(irrInstMS/1000)*fps);
try {
framePath = outputPath + "fromAudioAnalyser/AudioIrregularity_" + safeTimeLabel + ".jpg";
videoCapture.set(CAP_PROP_POS_FRAMES, irrFrame);
videoCapture >> frame;
cv::imwrite(framePath, frame);
// Append Irregularity information to JSON
boost::uuids::uuid uuid = boost::uuids::random_generator()();
irregularityFileOutput2["Irregularities"] += {{
"IrregularityID", irregularityFileInput["Irregularities"][i]["IrregularityID"]
}, {
"Source", "a"
}, {
"TimeLabel", timeLabel
}, {
"ImageURI", framePath
}
};
} catch (cv::Exception e) {
std::cout << "\033[0;31mTimeLabel error for Audio Analyser Irregularity " << i << "." << std::endl;
}
}
}
\ No newline at end of file
#include <boost/lexical_cast.hpp> #include "Irregularity.hpp"
#include <boost/uuid/uuid_io.hpp> Irregularity::Irregularity(const Irregularity& other)
#include <boost/uuid/uuid.hpp> : id(other.id), source(other.source), time_label(other.time_label), type(other.type) {}
#include <boost/uuid/uuid_generators.hpp>
#include "Irregularity.h"
Irregularity::Irregularity(Irregularity&& other) noexcept
: id(std::move(other.id)),
source(other.source),
time_label(std::move(other.time_label)),
type(std::move(other.type)) {}
Irregularity::Irregularity(Source source, string time_label, IrregularityType type, string image_URI) Irregularity::Irregularity(Source source, string time_label) {
{
this->id = boost::uuids::random_generator()(); this->id = boost::uuids::random_generator()();
this->source = source; this->source = source;
this->time_label = time_label; this->time_label = time_label;
this->type = type; this->type = std::nullopt;
this->image_URI = image_URI;
} }
Irregularity::~Irregularity() {} Irregularity::Irregularity(Source source, string time_label, IrregularityType type) {
this->id = boost::uuids::random_generator()();
this->source = source;
this->time_label = time_label;
this->type = type;
}
json Irregularity::toJSON() { json Irregularity::to_JSON() const {
json j; json j;
j["IrregularityID"] = boost::lexical_cast<string>(this->id); j["IrregularityID"] = boost::lexical_cast<string>(this->id);
j["Source"] = sourceToString(this->source); j["Source"] = sourceToString(this->source);
j["TimeLabel"] = this->time_label; j["TimeLabel"] = this->time_label;
j["IrregularityType"] = irregularityTypeToString(this->type); if (this->type.has_value()) j["IrregularityType"] = irregularityTypeToString(this->type.value());
if (!this->image_URI.empty())
j["ImageURI"] = this->image_URI; if (this->image_URI.has_value()) j["ImageURI"] = this->image_URI.value();
if (this->audio_URI.has_value()) j["AudioURI"] = this->audio_URI.value();
return j; return j;
} }
Irregularity Irregularity::fromJSON(json j) { Irregularity Irregularity::from_JSON(const json& j) {
Source source = sourceFromString(j["Source"]);
string time_label = j["TimeLabel"];
IrregularityType type = irregularityTypeFromString(j["IrregularityType"]);
return Irregularity( return Irregularity(source, time_label, type);
sourceFromString(j["Source"]), }
j["TimeLabel"],
irregularityTypeFromString(j["IrregularityType"]), Source Irregularity::get_source() const { return this->source; }
j["ImageURI"] string Irregularity::get_time_label() const { return this->time_label; }
); std::optional<IrregularityType> Irregularity::get_type() const { return this->type; }
boost::uuids::uuid Irregularity::get_id() const { return this->id; }
std::optional<string> Irregularity::get_audio_URI() const { return this->audio_URI; }
Irregularity& Irregularity::set_audio_URI(string audio_URI) {
this->audio_URI = audio_URI;
return *this;
}
std::optional<string> Irregularity::get_image_URI() const { return this->image_URI; }
Irregularity& Irregularity::set_image_URI(string image_URI) {
this->image_URI = image_URI;
return *this;
} }
#ifndef IRREGULARITY_H
#define IRREGULARITY_H
#include <boost/uuid/uuid.hpp>
#include <nlohmann/json.hpp>
#include "enums.h"
using std::string;
using json = nlohmann::json;
/**
* @brief an irregularity of the tape detected by the system
*
*/
struct Irregularity
{
boost::uuids::uuid id;
Source source;
string time_label;
IrregularityType type;
string image_URI;
Irregularity(Source source, string time_label, IrregularityType type, string image_URI);
~Irregularity();
json toJSON();
static Irregularity fromJSON(json j);
};
#endif // IRREGULARITY_H
\ No newline at end of file
/**
* @file Irregularity.hpp
* @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/lexical_cast.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <nlohmann/json.hpp>
#include "enums.hpp"
using std::string;
using json = nlohmann::json;
/**
* @class Irregularity
* @brief an irregularity of the tape detected by the system
*
*/
class Irregularity {
private:
boost::uuids::uuid id;
Source source;
string time_label;
std::optional<IrregularityType> type;
std::optional<string> image_URI;
std::optional<string> audio_URI;
public:
Irregularity(const Irregularity& other);
Irregularity(Irregularity&& other) noexcept;
Irregularity(Source source, string time_label);
Irregularity(Source source, string time_label, IrregularityType type);
~Irregularity() = default;
/**
* @brief Convert the Irregularity to a JSON object
*
* @return json
*/
json to_JSON() const;
/**
* @brief Create an Irregularity object from a JSON object
*
* @param j the JSON object
* @return Irregularity
*/
static Irregularity from_JSON(const json& j);
/**
* @brief Get the source object
*
* @return Source
*/
Source get_source() const;
/**
* @brief Get the time label object
*
* @return string
*/
string get_time_label() const;
/**
* @brief Get the type object
*
* @return IrregularityType
*/
std::optional<IrregularityType> get_type() const;
/**
* @brief Get the id object
*
* @return boost::uuids::uuid
*/
boost::uuids::uuid get_id() const;
/**
* @brief Get the audio URI object
*
* @return std::optional<string>
*/
std::optional<string> get_audio_URI() const;
/**
* @brief Get the image URI object
*
* @return std::optional<string>
*/
std::optional<string> get_image_URI() const;
/**
* @brief Set the audio URI object
*
* @param audio_URI
* @return Irregularity&
*/
Irregularity& set_audio_URI(string audio_URI);
/**
* @brief Set the image URI object
*
* @param image_URI
* @return Irregularity&
*/
Irregularity& set_image_URI(string image_URI);
};
#endif // IRREGULARITY_H
\ No newline at end of file
#include <exception> #include "IrregularityFile.hpp"
#include "IrregularityFile.h"
IrregularityFile::IrregularityFile(uint16_t offset, std::list<Irregularity> irregularities) IrregularityFile::IrregularityFile(std::optional<uint16_t> offset) : offset_(offset) {}
: offset(offset), irregularities(irregularities) {}
IrregularityFile::~IrregularityFile() {} IrregularityFile::IrregularityFile(const IrregularityFile& rhs) {
std::transform(rhs.irregularities_.begin(), rhs.irregularities_.end(), std::back_inserter(irregularities_),
json IrregularityFile::toJSON() { [](const std::unique_ptr<Irregularity>& ptr) { return std::make_unique<Irregularity>(*ptr); });
json j;
j["Offset"] = this->offset;
return j;
} }
IrregularityFile IrregularityFile::fromJSON(json j) { IrregularityFile& IrregularityFile::add(std::unique_ptr<Irregularity> irregularity) {
throw "Not implemented"; irregularities_.push_back(std::move(irregularity));
}
uint16_t IrregularityFile::getOffset() const {
return this->offset;
}
std::list<Irregularity> IrregularityFile::getIrregularities() const {
return this->irregularities;
}
IrregularityFile& IrregularityFile::add(const Irregularity irregularity) {
this->irregularities.push_back(irregularity);
return *this; return *this;
} }
IrregularityFile& IrregularityFile::remove_by_id(const boost::uuids::uuid id) { IrregularityFile& IrregularityFile::remove_by_id(const boost::uuids::uuid id) {
for (auto it = this->irregularities.begin(); it != this->irregularities.end(); ++it) { auto it =
if (it->id == id) { std::find_if(irregularities_.begin(), irregularities_.end(),
this->irregularities.erase(it); [&id](const std::unique_ptr<Irregularity>& irregularity) { return irregularity->get_id() == id; });
break; if (it != irregularities_.end()) {
} irregularities_.erase(it);
} }
return *this; return *this;
} }
IrregularityFile& IrregularityFile::sort() { IrregularityFile& IrregularityFile::sort() {
this->irregularities.sort([](const Irregularity& a, const Irregularity& b) { std::sort(irregularities_.begin(), irregularities_.end(),
return a.time_label < b.time_label; [](const std::unique_ptr<Irregularity>& a, const std::unique_ptr<Irregularity>& b) {
}); return a->get_time_label() < b->get_time_label();
});
return *this; return *this;
} }
\ No newline at end of file
std::optional<uint16_t> IrregularityFile::get_offset() const { return offset_; }
std::vector<std::unique_ptr<Irregularity>>::iterator IrregularityFile::begin() { return irregularities_.begin(); }
std::vector<std::unique_ptr<Irregularity>>::iterator IrregularityFile::end() { return irregularities_.end(); }
json IrregularityFile::toJSON() const {
json j;
j["Offset"] = offset_.value_or(0);
j["Irregularities"] = json::array();
for (const auto& irregularity : irregularities_) {
j["Irregularities"].push_back(irregularity->to_JSON());
}
return j;
}
IrregularityFile IrregularityFile::fromJSON(const json j) {
if (!j.contains("Offset") || !j.contains("Irregularities")) {
throw std::invalid_argument("Invalid JSON");
}
IrregularityFile irregularity_file(j["Offset"].get<uint16_t>());
for (const auto& irregularity : j["Irregularities"]) {
irregularity_file.add(std::make_unique<Irregularity>(Irregularity::from_JSON(irregularity)));
}
return irregularity_file;
}
#ifndef IRREGULARITY_FILE_H
#define IRREGULARITY_FILE_H
#include <list>
#include <nlohmann/json.hpp>
#include "Irregularity.h"
using json = nlohmann::json;
class IrregularityFile
{
private:
/* data */
uint16_t offset;
std::list<Irregularity> irregularities;
public:
IrregularityFile(uint16_t offset, std::list<Irregularity> irregularities);
~IrregularityFile();
static IrregularityFile fromJSON(const json j);
json toJSON();
uint16_t getOffset() const;
std::list<Irregularity> getIrregularities() const;
IrregularityFile& add(const Irregularity irregularity);
IrregularityFile& remove_by_id(const boost::uuids::uuid id);
IrregularityFile& sort();
};
#endif // IRREGULARITY_FILE_H
\ No newline at end of file
/**
* @file IrregularityFile.hpp
* @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
#include <algorithm>
#include <exception>
#include <iterator>
#include <memory>
#include <nlohmann/json.hpp>
#include <optional>
#include <vector>
#include "Irregularity.hpp"
using json = nlohmann::json;
/**
* @class IrregularityFile
* @brief An IrregularityFile is a collection of Irregularities detected on a
* tape.
*
*/
class IrregularityFile {
public:
/**
* @brief Create an IrregularityFile object from a JSON object
*
* @param j
* @return IrregularityFile
*/
static IrregularityFile fromJSON(const json j);
/**
* @brief Convert the IrregularityFile to a JSON object
*
* @return json
*/
json toJSON() const;
IrregularityFile(std::optional<uint16_t> offset = std::nullopt);
/**
* @brief Copy constructor
*
* @param rhs
*/
IrregularityFile(const IrregularityFile& rhs);
~IrregularityFile(){};
/**
* @brief Add an Irregularity to the IrregularityFile
*
* @param irregularity
* @return IrregularityFile&
*/
IrregularityFile& add(std::unique_ptr<Irregularity> irregularity);
/**
* @brief Remove an Irregularity from the IrregularityFile
*
* @param id
* @return IrregularityFile&
*/
IrregularityFile& remove_by_id(const boost::uuids::uuid id);
/**
* @brief Sort the IrregularityFile by time_label
*
* @return IrregularityFile&
*/
IrregularityFile& sort();
/**
* @brief Get the offset object
*
* @return std::optional<uint16_t>
*/
std::optional<uint16_t> get_offset() const;
/**
* @brief Get an iterator to the beginning of the IrregularityFile
*
* @return std::vector<std::unique_ptr<Irregularity>>::iterator
*/
std::vector<std::unique_ptr<Irregularity>>::iterator begin();
/**
* @brief Get an iterator to the end of the IrregularityFile
*
* @return std::vector<std::unique_ptr<Irregularity>>::iterator
*/
std::vector<std::unique_ptr<Irregularity>>::iterator end();
private:
std::optional<uint16_t> offset_;
std::vector<std::unique_ptr<Irregularity>> irregularities_;
};
#endif // IRREGULARITY_FILE_HPP
#include "TimeLabel.h" #include "TimeLabel.hpp"
TimeLabel::TimeLabel(/* args */) TimeLabel::TimeLabel(/* args */) {}
{
}
TimeLabel::~TimeLabel() TimeLabel::~TimeLabel() {}
{ \ No newline at end of file
}
\ No newline at end of file
#ifndef TIME_LABEL_H
#define TIME_LABEL_H
class TimeLabel
{
private:
/* data */
public:
TimeLabel(/* args */);
~TimeLabel();
};
#endif // TIME_LABEL_H
\ No newline at end of file
#ifndef TIME_LABEL_H
#define TIME_LABEL_H
/**
* @brief A label that displays the current time in the format hh:mm:ss.mms
* @todo Implement this class:
* - Implement the constructor
* - Implement the destructor
* - Move functions from time.h to here
*/
class TimeLabel {
private:
/* data */
public:
TimeLabel(/* args */);
~TimeLabel();
};
#endif // TIME_LABEL_H
\ No newline at end of file
#include <stdlib.h>
#include <string>
using std::string;
string PURPLE = "\033[95m";
string CYAN = "\033[96m";
string DARK_CYAN = "\033[36m";
string BLUE = "\033[94m";
string GREEN = "\033[92m";
string YELLOW = "\033[93m";
string RED = "\033[91m";
string BOLD = "\033[1m";
string UNDERLINE = "\033[4m";
string END = "\033[0m";
\ No newline at end of file
/**
* @file colors.hpp
* @author Matteo Spanio (dev2@audioinnova.com)
* @brief Header file containing a set of ANSI escape codes to print colored
* text in the terminal.
*
* When printing text in the terminal, it is possible to use ANSI escape codes
* to change the color of the text. This header file contains a set of
* pre-defined ANSI escape codes to print colored text in the terminal.
*
* @see https://en.wikipedia.org/wiki/ANSI_escape_code
* @version 1.0
* @date 2023-05-13
* @copyright Copyright (c) 2023
*
*/
#ifndef COLORS_H
#define COLORS_H
namespace colors {
using Color = const char*;
constexpr Color PURPLE = "\033[95m";
constexpr Color CYAN = "\033[96m";
constexpr Color DARK_CYAN = "\033[36m";
constexpr Color BLUE = "\033[94m";
constexpr Color GREEN = "\033[92m";
constexpr Color YELLOW = "\033[93m";
constexpr Color RED = "\033[91m";
constexpr Color WHITE = "\033[97m";
constexpr Color BOLD = "\033[1m";
constexpr Color UNDERLINE = "\033[4m";
constexpr Color END = "\033[0m";
} // namespace colors
#endif // COLORS_H
\ No newline at end of file
#include "core.hpp"
namespace videoanalyser {
namespace core {
Frame::Frame() : cv::Mat() {}
Frame::Frame(const cv::Mat& m) : cv::Mat(m) {}
Frame::Frame(const Frame& f) : cv::Mat(f) {}
Frame& Frame::operator=(const Mat& m) {
Mat::operator=(m);
return *this;
}
Frame& Frame::operator=(const Frame& f) {
Mat::operator=(f);
return *this;
}
Frame Frame::clone() const { return Frame(cv::Mat::clone()); }
Frame& Frame::downsample(int factor) {
cv::pyrDown(*this, *this, cv::Size(size().width / factor, size().height / factor));
return *this;
}
Frame& Frame::convert_color(int code) {
cv::cvtColor(*this, *this, code);
return *this;
}
Frame Frame::difference(Frame& f) {
Frame diff = this->clone();
for (int i = 0; i < this->rows; i++) {
for (int j = 0; j < this->cols; j++) {
if (f.at<cv::Vec3b>(i, j)[0] != this->at<cv::Vec3b>(i, j)[0] ||
f.at<cv::Vec3b>(i, j)[1] != this->at<cv::Vec3b>(i, j)[1] ||
f.at<cv::Vec3b>(i, j)[2] != this->at<cv::Vec3b>(i, j)[2]) {
// Different pixels
diff.at<cv::Vec3b>(i, j)[0] = 0;
diff.at<cv::Vec3b>(i, j)[1] = 0;
diff.at<cv::Vec3b>(i, j)[2] = 0;
} else {
// Identical pixels
diff.at<cv::Vec3b>(i, j)[0] = 255;
diff.at<cv::Vec3b>(i, j)[1] = 255;
diff.at<cv::Vec3b>(i, j)[2] = 255;
}
}
}
return diff;
}
Frame& Frame::crop(cv::Size rect_size, cv::Point2f center) {
cv::getRectSubPix(*this, rect_size, center, *this);
return *this;
}
Frame& Frame::warp(cv::Mat rotationMatrix) {
cv::warpAffine(*this, *this, rotationMatrix, this->size(), cv::INTER_CUBIC);
return *this;
}
std::pair<Frame, Frame> 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));
int i_odd_frame = 0;
int i_even_frame = 0;
for (int i = 0; i < this->rows; i++) {
for (int j = 0; j < this->cols; j++) {
if (i % 2 == 0) {
even_frame.at<cv::Vec3b>(i_even_frame, j)[0] = this->at<cv::Vec3b>(i, j)[0];
even_frame.at<cv::Vec3b>(i_even_frame, j)[1] = this->at<cv::Vec3b>(i, j)[1];
even_frame.at<cv::Vec3b>(i_even_frame, j)[2] = this->at<cv::Vec3b>(i, j)[2];
} else {
odd_frame.at<cv::Vec3b>(i_odd_frame, j)[0] = this->at<cv::Vec3b>(i, j)[0];
odd_frame.at<cv::Vec3b>(i_odd_frame, j)[1] = this->at<cv::Vec3b>(i, j)[1];
odd_frame.at<cv::Vec3b>(i_odd_frame, j)[2] = this->at<cv::Vec3b>(i, j)[2];
}
}
if (i % 2 == 0) {
i_even_frame++;
} else {
i_odd_frame++;
}
}
return std::make_pair(odd_frame, even_frame);
}
} // namespace core
} // namespace videoanalyser
\ No newline at end of file
/**
* @file core.hpp
* @author Matteo Spanio (dev2@audioinnova.com)
* @brief This file contains the core functionalities of the project.
* @version 1.2
* @date 2023-06-03
*
* @copyright Copyright (c) 2023
*
*/
#ifndef CORE_H
#define CORE_H
#include <opencv2/calib3d.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <optional>
#include <string>
#include <tuple>
#include <variant>
#include <vector>
namespace videoanalyser {
using Error = std::string;
template <typename T>
using Result = std::variant<T, Error>;
namespace core {
/**
* @class Frame
* @brief Class that extends the OpenCV Mat class, adding some useful methods
* frequently used in the project.
*
*/
class Frame : public cv::Mat {
public:
Frame();
Frame(const cv::Mat& m);
Frame(const Frame& f);
Frame& operator=(const cv::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& convert_color(int code);
/**
* @brief Compute the number of different pixels between two frames.
*
* @param f The frame to compare with.
* @return A black and white frame, where black pixels represent a
* difference, while white pixels represent an equality.
*/
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(cv::Size rect_size, cv::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;
};
} // namespace core
} // namespace videoanalyser
#endif // CORE_H
\ No newline at end of file
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