main.cpp 21.7 KB
Newer Older
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
1
/**
Matteo's avatar
Matteo committed
2
 * @mainpage MPAI CAE-ARP Video Analyser
Matteo's avatar
Matteo committed
3
 * @file main.cpp
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
4
5
6
 *  MPAI CAE-ARP Video Analyser.
 *
 *	Implements MPAI CAE-ARP Video Analyser Technical Specification.
Matteo's avatar
Matteo committed
7
8
 *	It identifies Irregularities on the Preservation Audio-Visual File,
 *providing:
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
9
10
11
 *	- Irregularity Files;
 *	- Irregularity Images.
 *
Matteo's avatar
Matteo committed
12
13
 * @warning Currently, this program is only compatible with the Studer A810
 *and videos recorded in PAL standard.
Matteo's avatar
update    
Matteo committed
14
 *
Matteo's avatar
Matteo committed
15
16
17
18
19
 * @todo
 *  - 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).
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
20
 *
Matteo's avatar
Matteo committed
21
22
 *  @author Nadir Dalla Pozza <nadir.dallapozza@unipd.it>
 *  @author Matteo Spanio <dev2@audioinnova.com>
Matteo's avatar
Matteo committed
23
 *	@copyright 2023, Audio Innova S.r.l.
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
24
25
 *	@credits Niccolò Pretto, Nadir Dalla Pozza, Sergio Canazza
 *	@license GPL v3.0
Matteo's avatar
Matteo committed
26
 *	@version 1.2
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
27
28
 *	@status Production
 */
29
#include <stdlib.h>
30
31
#include <sys/timeb.h>

Matteo's avatar
Matteo committed
32
#include <boost/program_options.hpp>
Matteo's avatar
Matteo committed
33
34
35
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
Matteo's avatar
Matteo committed
36
37
38
39
#include <filesystem>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
40
#include <opencv2/core/core.hpp>
Matteo's avatar
Matteo committed
41
#include <opencv2/highgui.hpp>
Matteo's avatar
Matteo committed
42
43
#include <variant>

Matteo's avatar
Matteo committed
44
#include "forAudioAnalyser.h"
Matteo's avatar
Matteo committed
45
46
47
48
49
50
51
52
53
54
#include "lib/Irregularity.hpp"
#include "lib/IrregularityFile.hpp"
#include "lib/colors.hpp"
#include "lib/core.hpp"
#include "lib/detection.hpp"
#include "lib/files.hpp"
#include "lib/io.hpp"
#include "lib/time.hpp"
#include "utility.hpp"

55
using namespace std;
Matteo's avatar
Matteo committed
56
using namespace colors;
57
using json = nlohmann::json;
Matteo's avatar
Matteo committed
58
59
60
using videoanalyser::core::Frame;
using videoanalyser::io::pprint;
using videoanalyser::io::print_error_and_exit;
61
62
namespace fs = std::filesystem;
namespace po = boost::program_options;
Matteo's avatar
Matteo committed
63
namespace va = videoanalyser;
64

Matteo's avatar
Matteo committed
65
bool g_end_tape_saved = false;
Matteo's avatar
Matteo committed
66
bool g_first_brand = true;  // The first frame containing brands on tape must be saved
Matteo's avatar
Matteo committed
67
68
69
70
71
72
73
float g_first_instant = 0;
float g_mean_prev_frame_color = 0;  // Average frame color

static fs::path g_output_path{};
static fs::path g_irregularity_images_path{};
static json g_irregularity_file_1{};
static json g_irregularity_file_2{};
Matteo's avatar
Matteo committed
74

75
struct Args {
Matteo's avatar
Matteo committed
76
    fs::path
Matteo's avatar
Matteo committed
77
78
79
80
        working_path; /**< The working path where all input files are stored and where all output files will be saved */
    string files_name; /**< The name of the preservation files to be considered */
    bool brands;       /**< True if tape presents brands on its surface */
    float speed;       /**< The speed at which the tape was read */
Matteo's avatar
Matteo committed
81

Matteo's avatar
Matteo committed
82
    Args(fs::path working_path, string files_name, bool brands, float speed) {
Matteo's avatar
Matteo committed
83
        if (speed != 7.5 && speed != 15) throw invalid_argument("Speed must be 7.5 or 15");
Matteo's avatar
Matteo committed
84
85
        this->working_path = working_path;
        this->files_name = files_name;
Matteo's avatar
Matteo committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
        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"
                "By default, the configuartion parameters are loaded from "
                "config/config.json file,\n"
                "but, alternately, you can pass command line arguments to "
                "replace them");
            desc.add_options()("help,h", "Display this help message")(
                "working-path,w", po::value<string>()->required(),
                "Specify the Working Path, where all input files are stored")(
                "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::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
            if (vm.count("help")) {
Matteo's avatar
Matteo committed
118
                std::cout << desc << "\n";
Matteo's avatar
Matteo committed
119
120
121
122
                std::exit(EXIT_SUCCESS);
            }
            po::notify(vm);
        } catch (po::invalid_command_line_syntax& e) {
Matteo's avatar
Matteo committed
123
            print_error_and_exit("Invalid command line syntax: " + string(e.what()));
Matteo's avatar
Matteo committed
124
        } catch (po::required_option& e) {
Matteo's avatar
Matteo committed
125
            print_error_and_exit("Missing required option: " + string(e.what()));
Matteo's avatar
Matteo committed
126
        } catch (nlohmann::detail::type_error e) {
Matteo's avatar
Matteo committed
127
            print_error_and_exit("config.json error: " + string(e.what()));
Matteo's avatar
Matteo committed
128
129
130
131
132
        }

        return Args(fs::path(vm["working-path"].as<string>()), vm["files-name"].as<string>(), vm["brands"].as<bool>(),
                    vm["speed"].as<float>());
    }
133
};
134

Matteo's avatar
Matteo committed
135
/**
Matteo's avatar
update    
Matteo committed
136
137
 * @brief Get the next frame object.
 *
Matteo's avatar
Matteo committed
138
139
 * Whenever we find an Irregularity, we want to skip a lenght equal to the
 * Studer reading head (3 cm = 1.18 inches).
Matteo's avatar
update    
Matteo committed
140
 *
Matteo's avatar
Matteo committed
141
142
143
144
 * Note the following considerations:
 * - since we are analysing video at 25 fps a frame occurs every 40 ms
 * - at 15 ips we cross 3 cm of tape in 79 ms (2 frames)
 * - at 7.5 ips we cross 3 cm of tape in 157 ms (4 frames)
Matteo's avatar
update    
Matteo committed
145
 *
Matteo's avatar
Matteo committed
146
147
148
 * 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
Matteo's avatar
update    
Matteo committed
149
 *
Matteo's avatar
Matteo committed
150
151
152
153
154
155
156
157
158
159
160
161
162
163
 * @param cap VideoCapture object
 * @param speed tape reading speed
 * @return Frame
 */
Frame get_next_frame(VideoCapture& cap, float speed, bool skip = false) {
    if (skip) {
        int ms_to_skip = speed == 15 ? 79 : 157;
        cap.set(CAP_PROP_POS_MSEC, cap.get(CAP_PROP_POS_MSEC) + ms_to_skip);
    }
    Frame frame;
    cap >> frame;
    return frame;
}

Matteo's avatar
Matteo committed
164
float rotated_rect_area(RotatedRect rect) { return rect.size.width * rect.size.height; }
165

Matteo's avatar
Matteo committed
166
/**
Matteo's avatar
Matteo committed
167
 * @fn bool find_processing_areas(Frame frame, SceneObject tape, SceneObject capstan)
Matteo's avatar
Matteo committed
168
169
170
 * @brief Identifies the Regions Of Interest (ROIs) on the video,
 * which are:
 * - The reading head;
Matteo's avatar
Matteo committed
171
172
 * - The tape area under the tape head (computed on the basis of the detected
 * reading head);
Matteo's avatar
Matteo committed
173
 * - The capstan.
Matteo's avatar
Matteo committed
174
175
176
 * @param frame the frame to be analysed
 * @param tape the tape object
 * @param capstan the capstan object
Matteo's avatar
Matteo committed
177
178
179
 * @return true if some areas have been detected;
 * @return false otherwise.
 */
Matteo's avatar
Matteo committed
180
181
va::Result<pair<va::detection::Roi, va::detection::Roi>> find_processing_areas(Frame frame, SceneObject tape,
                                                                               SceneObject capstan) {
Matteo's avatar
Matteo committed
182
183
184
185
186
187
    va::detection::SceneElement tape_element{
        va::detection::ElementType::TAPE,
        tape.minDist,
        {tape.threshold.percentual, tape.threshold.angle, tape.threshold.scale, tape.threshold.pos}};

    auto tape_roi_result = va::detection::find_roi(frame, va::detection::Algorithm::GHT, tape_element);
Matteo's avatar
Matteo committed
188
189
190
    if (std::holds_alternative<va::Error>(tape_roi_result))
        return va::Error("Error while finding tape roi: " + std::get<va::Error>(tape_roi_result));
    auto rect_tape = std::get<va::detection::Roi>(tape_roi_result);
Matteo's avatar
Matteo committed
191
192
193
194
195
196

    va::detection::SceneElement capstan_element{
        va::detection::ElementType::CAPSTAN,
        capstan.minDist,
        {capstan.threshold.percentual, capstan.threshold.angle, capstan.threshold.scale, capstan.threshold.pos}};
    auto capstan_roi_result = va::detection::find_roi(frame, va::detection::Algorithm::SURF, capstan_element);
Matteo's avatar
Matteo committed
197
198
199
    if (std::holds_alternative<va::Error>(capstan_roi_result))
        return va::Error("Error while finding capstan roi: " + std::get<va::Error>(capstan_roi_result));
    auto rect_capstan = std::get<va::detection::Roi>(capstan_roi_result);
Matteo's avatar
Matteo committed
200

Matteo's avatar
Matteo committed
201
202
    cv::rectangle(frame, rect_tape.boundingRect(), cv::Scalar(0, 255, 0), 2);
    cv::rectangle(frame, rect_capstan.boundingRect(), cv::Scalar(255, 0, 0), 2);
Matteo's avatar
Matteo committed
203
    cv::imwrite(g_output_path.string() + "/my_tape_areas.jpg", frame);
Matteo's avatar
Matteo committed
204

Matteo's avatar
Matteo committed
205
    return make_pair(rect_tape, rect_capstan);
206
207
}

Matteo's avatar
Matteo committed
208
209
210
/**
 * @fn RotatedRect check_skew(RotatedRect roi)
 * @brief Check if the region of interest is skewed and correct it
Matteo's avatar
Matteo committed
211
 *
Matteo's avatar
Matteo committed
212
213
214
 * @param roi the region of interest
 * @return RotatedRect the corrected region of interest
 */
Matteo's avatar
Matteo committed
215
RotatedRect check_skew(RotatedRect roi) {
Matteo's avatar
Matteo committed
216
217
218
219
220
221
    // get angle and size from the bounding box
    // thanks to http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/
    cv::Size rect_size = roi.size;
    float angle = roi.angle;
    if (roi.angle < -45.) {
        angle += 90.0;
Matteo's avatar
Matteo committed
222
        std::swap(rect_size.width, rect_size.height);
Matteo's avatar
Matteo committed
223
224
    }
    return RotatedRect(roi.center, rect_size, angle);
Matteo's avatar
Matteo committed
225
226
227
}

/**
Matteo's avatar
Matteo committed
228
229
230
231
232
 * @fn Frame get_difference_for_roi(Frame previous, Frame current, RotatedRect
 * roi)
 * @brief Look for differences in two consecutive frames in a specific region of
 * interest
 *
Matteo's avatar
Matteo committed
233
234
235
236
237
238
 * @param previous the reference frame
 * @param current the frame to compare with the reference
 * @param roi the region of interest
 * @return Frame the difference matrix between the two frames
 */
Frame get_difference_for_roi(Frame previous, Frame current, RotatedRect roi) {
Matteo's avatar
Matteo committed
239
    cv::Mat rotation_matrix = cv::getRotationMatrix2D(roi.center, roi.angle, 1.0);
Matteo's avatar
Matteo committed
240
241
242
243

    return previous.warp(rotation_matrix)
        .crop(roi.size, roi.center)
        .difference(current.warp(rotation_matrix).crop(roi.size, roi.center));
Matteo's avatar
Matteo committed
244
}
245

Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
246
/**
Matteo's avatar
Matteo committed
247
 * @fn bool is_frame_different(cv::Mat prev_frame, cv::Mat current_frame, int
Matteo's avatar
Matteo committed
248
249
 * ms_to_end, SceneObject capstan, SceneObject tape, Args args, va::detection::Roi rect_tape, va::detection::Roi
 * rect_capstan)
Matteo's avatar
Matteo committed
250
251
252
 * @brief Compares two consecutive video frames and establish if there
 * potentially is an Irregularity. The comparison is pixel-wise and based on
 * threshold values set on config.json file.
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
253
 *
Matteo's avatar
Matteo committed
254
255
256
 * @param prev_frame the frame before the current one;
 * @param current_frame the current frame;
 * @param ms_to_end the number of milliseconds left before the end of the video.
Matteo's avatar
Matteo committed
257
 * Useful for capstan analysis.
Matteo's avatar
Matteo committed
258
259
260
261
262
 * @param capstan the capstan object;
 * @param tape the tape object;
 * @param args the command line arguments;
 * @param rect_tape the tape area under the tape head;
 * @param rect_capstan the capstan area.
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
263
264
265
 * @return true if a potential Irregularity has been found;
 * @return false otherwise.
 */
Matteo's avatar
Matteo committed
266
bool is_frame_different(cv::Mat prev_frame, cv::Mat current_frame, int ms_to_end, SceneObject capstan, SceneObject tape,
Matteo's avatar
Matteo committed
267
                        Args args, va::detection::Roi rect_tape, va::detection::Roi rect_capstan) {
Matteo's avatar
Matteo committed
268
    bool result = false;
Matteo's avatar
Matteo committed
269
    int num_different_pixels = 0;
Matteo's avatar
Matteo committed
270
271
272
273
274

    /*********************** Capstan analysis ************************/

    // In the last minute of the video, check for pinchRoller position for
    // endTape event
Matteo's avatar
Matteo committed
275
    if (!g_end_tape_saved && ms_to_end < 60000) {
Matteo's avatar
Matteo committed
276
        RotatedRect corrected_capstan_roi = check_skew(rect_capstan);
Matteo's avatar
Matteo committed
277
        Frame difference_frame = get_difference_for_roi(Frame(prev_frame), Frame(current_frame), corrected_capstan_roi);
Matteo's avatar
Matteo committed
278
279
280
281
282
283

        for (int i = 0; i < difference_frame.rows; i++) {
            for (int j = 0; j < difference_frame.cols; j++) {
                if (difference_frame.at<cv::Vec3b>(i, j)[0] == 0) {
                    // There is a black pixel, then there is a difference
                    // between previous and current frames
Matteo's avatar
Matteo committed
284
                    num_different_pixels++;
Matteo's avatar
Matteo committed
285
286
287
288
                }
            }
        }

Matteo's avatar
Matteo committed
289
        float capstan_pixel_threshold = rotated_rect_area(rect_capstan) * capstan.threshold.percentual / 100;
Matteo's avatar
Matteo committed
290
291
        if (num_different_pixels > capstan_pixel_threshold) {
            g_end_tape_saved = true;  // Never check again for end tape instant
Matteo's avatar
Matteo committed
292
293
294
            return true;
        }
    }
295

Matteo's avatar
Matteo committed
296
    /********************* Tape analysis *********************/
297

Matteo's avatar
Matteo committed
298
    RotatedRect corrected_tape_roi = check_skew(rect_tape);
Matteo's avatar
Matteo committed
299
300
    Frame cropped_current_frame =
        Frame(current_frame)
Matteo's avatar
Matteo committed
301
            .warp(cv::getRotationMatrix2D(corrected_tape_roi.center, corrected_tape_roi.angle, 1.0))
Matteo's avatar
Matteo committed
302
303
            .crop(corrected_tape_roi.size, corrected_tape_roi.center);
    Frame difference_frame = get_difference_for_roi(Frame(prev_frame), Frame(current_frame), corrected_tape_roi);
304

Matteo's avatar
Matteo committed
305
    /********************** Segment analysis ************************/
306

Matteo's avatar
Matteo committed
307
308
309
    num_different_pixels = 0;
    float mean_current_frame_color;
    int current_frame_color_sum = 0;
Matteo's avatar
Matteo committed
310

Matteo's avatar
Matteo committed
311
312
313
314
315
    for (int i = 0; i < cropped_current_frame.rows; i++) {
        for (int j = 0; j < cropped_current_frame.cols; j++) {
            current_frame_color_sum += cropped_current_frame.at<cv::Vec3b>(i, j)[0] +
                                       cropped_current_frame.at<cv::Vec3b>(i, j)[1] +
                                       cropped_current_frame.at<cv::Vec3b>(i, j)[2];
Matteo's avatar
Matteo committed
316
            if (difference_frame.at<cv::Vec3b>(i, j)[0] == 0) {
Matteo's avatar
Matteo committed
317
                num_different_pixels++;
Matteo's avatar
Matteo committed
318
319
320
            }
        }
    }
Matteo's avatar
Matteo committed
321
    float tape_area_pixels_sq = rotated_rect_area(rect_tape);
Matteo's avatar
Matteo committed
322
    mean_current_frame_color = current_frame_color_sum / tape_area_pixels_sq;
Matteo's avatar
Matteo committed
323
324
325

    /*********************** Decision stage ************************/

Matteo's avatar
Matteo committed
326
327
    float tape_pixel_threshold = tape_area_pixels_sq * tape.threshold.percentual / 100;
    if (num_different_pixels > tape_pixel_threshold) {  // The threshold must be passed
Matteo's avatar
Matteo committed
328
329

        /***** AVERAGE_COLOR-BASED DECISION *****/
Matteo's avatar
Matteo committed
330
331
        if (g_mean_prev_frame_color > (mean_current_frame_color + 7) ||
            g_mean_prev_frame_color < (mean_current_frame_color - 7)) {  // They are not similar for color average
Matteo's avatar
Matteo committed
332
333
334
335
            result = true;
        }

        /***** BRANDS MANAGEMENT *****/
Matteo's avatar
Matteo committed
336
337
338
339
        // 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
Matteo's avatar
Matteo committed
340
341
        if (args.brands && g_first_brand && g_first_instant - ms_to_end > 5000) {
            g_first_brand = false;
Matteo's avatar
Matteo committed
342
            result = true;
Matteo's avatar
Matteo committed
343
344
        }
    }
345

Matteo's avatar
Matteo committed
346
    g_mean_prev_frame_color = mean_current_frame_color;
347

Matteo's avatar
Matteo committed
348
349
    return result;
}
350

Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
351
/**
Matteo's avatar
Matteo committed
352
 * @fn void processing(cv::VideoCapture video_capture, SceneObject capstan,
Matteo's avatar
Matteo committed
353
 * SceneObject tape, Args args, va::detection::Roi rect_tape, va::detection::Roi rect_capstan)
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
354
 * @brief video processing phase, where each frame is analysed.
Matteo's avatar
Matteo committed
355
356
357
 * It saves the IrregularityImages and updates the IrregularityFiles if an
 * Irregularity is found
 *
Matteo's avatar
update    
Matteo committed
358
 * @note To be able to work with the "old" neural network (by Ilenya),
Matteo's avatar
Matteo committed
359
360
361
362
363
364
 * 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.
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
365
 *
Matteo's avatar
Matteo committed
366
 * @param video_capture the input Preservation Audio-Visual File;
Matteo's avatar
update    
Matteo committed
367
368
369
 * @param capstan the capstan SceneObject;
 * @param tape the tape SceneObject;
 * @param args the command line arguments.
Matteo's avatar
Matteo committed
370
371
 * @param rect_tape the tape Roi;
 * @param rect_capstan the capstan Roi.
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
372
 */
Matteo's avatar
Matteo committed
373
374
void processing(cv::VideoCapture video_capture, SceneObject capstan, SceneObject tape, Args args,
                va::detection::Roi rect_tape, va::detection::Roi rect_capstan) {
Matteo's avatar
Matteo committed
375
376
377
378
    const int video_length_ms =
        ((float)video_capture.get(CAP_PROP_FRAME_COUNT) / video_capture.get(CAP_PROP_FPS)) * 1000;
    int video_current_ms = video_capture.get(CAP_PROP_POS_MSEC);
    int num_saved_frames = 0;
Matteo's avatar
update    
Matteo committed
379
    bool irregularity_found = false;
Matteo's avatar
Matteo committed
380
381

    // The first frame of the video won't be processed
Matteo's avatar
Matteo committed
382
383
    cv::Mat prev_frame = get_next_frame(video_capture, args.speed, irregularity_found);
    g_first_instant = video_length_ms - video_current_ms;
384

Matteo's avatar
Matteo committed
385
386
387
    while (video_capture.isOpened()) {
        Frame frame = get_next_frame(video_capture, args.speed, irregularity_found);
        video_current_ms = video_capture.get(CAP_PROP_POS_MSEC);
Matteo's avatar
Matteo committed
388
389

        if (frame.empty()) {
Matteo's avatar
Matteo committed
390
            std::cout << endl << "Empty frame!" << endl;
Matteo's avatar
Matteo committed
391
            video_capture.release();
Matteo's avatar
Matteo committed
392
393
394
            return;
        }

Matteo's avatar
Matteo committed
395
        int ms_to_end = video_length_ms - video_current_ms;
Matteo's avatar
Matteo committed
396
397
398
399
400
        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;

        // Display program status
Matteo's avatar
Matteo committed
401
402
403
        int sec_to_end = ms_to_end / 1000;
        int min_to_end = (sec_to_end / 60) % 60;
        sec_to_end = sec_to_end % 60;
Matteo's avatar
Matteo committed
404
405
        string sec_str_to_end = (sec_to_end < 10 ? "0" : "") + to_string(sec_to_end);
        string min_str_to_end = (min_to_end < 10 ? "0" : "") + to_string(min_to_end);
Matteo's avatar
Matteo committed
406

Matteo's avatar
Matteo committed
407
        std::cout << "\rIrregularities: " << num_saved_frames << ".   ";
Matteo's avatar
Matteo committed
408
        std::cout << "Remaining video time [mm:ss]: " << min_str_to_end << ":" << sec_str_to_end << flush;
Matteo's avatar
Matteo committed
409

Matteo's avatar
Matteo committed
410
411
        irregularity_found =
            is_frame_different(prev_frame, frame, ms_to_end, capstan, tape, args, rect_tape, rect_capstan);
Matteo's avatar
update    
Matteo committed
412
        if (irregularity_found) {
Matteo's avatar
Matteo committed
413
            auto [odd_frame, _] = frame.deinterlace();
Matteo's avatar
Matteo committed
414

Matteo's avatar
update    
Matteo committed
415
            string irregularityImageFilename =
Matteo's avatar
Matteo committed
416
417
                to_string(num_saved_frames) + "_" + getTimeLabel(video_current_ms, "-") + ".jpg";
            cv::imwrite(g_irregularity_images_path / irregularityImageFilename, odd_frame);
Matteo's avatar
Matteo committed
418
419

            // Append Irregularity information to JSON
Matteo's avatar
update    
Matteo committed
420
            Irregularity irreg = Irregularity(Source::Video, getTimeLabel(video_current_ms, ":"));
Matteo's avatar
Matteo committed
421
422
423
            g_irregularity_file_1["Irregularities"] += irreg.to_JSON();
            g_irregularity_file_2["Irregularities"] +=
                irreg.set_image_URI(g_irregularity_images_path.string() + "/" + irregularityImageFilename).to_JSON();
Matteo's avatar
Matteo committed
424

Matteo's avatar
Matteo committed
425
            num_saved_frames++;
Matteo's avatar
Matteo committed
426
        }
Matteo's avatar
Matteo committed
427
        prev_frame = frame;
Matteo's avatar
Matteo committed
428
    }
429
430
}

Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
431
/**
Matteo's avatar
Matteo committed
432
 * @fn int main(int argc, char** argv)
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
433
434
435
436
437
438
439
440
 * @brief main program, organised as:
 * - Get input from command line or config.json file;
 * - Check input parameters;
 * - Creation of output directories;
 * - Regions Of Interest (ROIs) detection;
 * - Irregularities detection;
 * - Saving of output IrregularityFiles.
 *
Matteo's avatar
Matteo committed
441
442
443
444
445
 * @todo The main function should be splitted into 2 steps, and each step should be callable from the command line, so
 * that the user can choose to run only the first step, only the second step, or both:
 * - First step: generate irregularity file output 1;
 * - Second step: generate irregularity file output 2.
 *
Nadir Dalla Pozza's avatar
Nadir Dalla Pozza committed
446
447
448
449
 * @param argc Command line arguments count;
 * @param argv Command line arguments.
 * @return int program status.
 */
450
int main(int argc, char** argv) {
Matteo's avatar
Matteo committed
451
452
453
454
    const string CONFIG_FILE = "config/config.json";
    const string A_IRREG_FILE_1 = "AudioAnalyser_IrregularityFileOutput1.json";
    const string V_IRREG_FILE_1 = "VideoAnalyser_IrregularityFileOutput1.json";
    const string V_IRREG_FILE_2 = "VideoAnalyser_IrregularityFileOutput2.json";
Matteo's avatar
Matteo committed
455
456

    Args args = argc > 1 ? Args::from_cli(argc, argv) : Args::from_file(CONFIG_FILE);
Matteo's avatar
Matteo committed
457
458
    SceneObject capstan = SceneObject::from_file(CONFIG_FILE, ROI::CAPSTAN);
    SceneObject tape = SceneObject::from_file(CONFIG_FILE, ROI::TAPE);
Matteo's avatar
Matteo committed
459

Matteo's avatar
Matteo committed
460
    const fs::path VIDEO_PATH = args.working_path / "PreservationAudioVisualFile" / args.files_name;
Matteo's avatar
Matteo committed
461
    const auto [FILE_NAME, FILE_FORMAT] = files::get_filename_and_extension(VIDEO_PATH);
Matteo's avatar
Matteo committed
462
    const fs::path AUDIO_IRR_FILE_PATH = args.working_path / "temp" / FILE_NAME / A_IRREG_FILE_1;
Matteo's avatar
Matteo committed
463

Matteo's avatar
Matteo committed
464
    std::cout << "Video to be analysed: " << endl;
Matteo's avatar
Matteo committed
465
466
    std::cout << "\tFile name:  " << FILE_NAME << endl;
    std::cout << "\tExtension:  " << FILE_FORMAT << endl;
Matteo's avatar
Matteo committed
467

Matteo's avatar
Matteo committed
468
    if (FILE_FORMAT.compare("avi") != 0 && FILE_FORMAT.compare("mp4") != 0 && FILE_FORMAT.compare("mov") != 0)
Matteo's avatar
Matteo committed
469
        print_error_and_exit("Input error: The input file must be an AVI, MP4 or MOV file.");
Matteo's avatar
Matteo committed
470
    ifstream iJSON(AUDIO_IRR_FILE_PATH);
Matteo's avatar
Matteo committed
471
    if (iJSON.fail())
Matteo's avatar
Matteo committed
472
        print_error_and_exit("config.json error" + AUDIO_IRR_FILE_PATH.string() + " cannot be found or opened.");
Matteo's avatar
Matteo committed
473

Matteo's avatar
Matteo committed
474
475
    json audio_irr_file;
    iJSON >> audio_irr_file;
476

Matteo's avatar
Matteo committed
477
    // Adjust input paramenters (considering given ones as pertinent to a speed reference = 7.5)
Matteo's avatar
Matteo committed
478
479
480
    if (args.speed == 15) {
        tape.threshold.percentual += args.brands ? 6 : 20;
    } else if (!args.brands)
Matteo's avatar
Matteo committed
481
        tape.threshold.percentual += 21;
Nadir Dalla Pozza's avatar
Update.    
Nadir Dalla Pozza committed
482

Matteo's avatar
Matteo committed
483
    g_output_path = args.working_path / "temp" / FILE_NAME;
Matteo's avatar
Matteo committed
484
    fs::create_directory(g_output_path);
Matteo's avatar
update    
Matteo committed
485

Matteo's avatar
Matteo committed
486
487
    g_irregularity_images_path = g_output_path / "IrregularityImages";
    fs::create_directory(g_irregularity_images_path);
Nadir Dalla Pozza's avatar
Update.    
Nadir Dalla Pozza committed
488

Matteo's avatar
Matteo committed
489
    cv::VideoCapture video_capture(VIDEO_PATH);  // Open video file
Matteo's avatar
Matteo committed
490
    if (!video_capture.isOpened()) print_error_and_exit("Video error: Video file cannot be opened.");
491

Matteo's avatar
Matteo committed
492
493
494
495
    video_capture.set(CAP_PROP_POS_FRAMES,
                      video_capture.get(CAP_PROP_FRAME_COUNT) / 2);  // Set frame position to half video length
    cv::Mat middle_frame = get_next_frame(video_capture, args.speed);
    video_capture.set(CAP_PROP_POS_FRAMES, 0);  // Reset frame position
496

Matteo's avatar
Matteo committed
497
    std::cout << "\tResolution: " << middle_frame.cols << "x" << middle_frame.rows << "\n\n";
498

Matteo's avatar
Matteo committed
499
500
501
502
    auto processing_areas = find_processing_areas(middle_frame, tape, capstan);
    if (std::holds_alternative<va::Error>(processing_areas))
        print_error_and_exit("Processing area not found: Try changing JSON parameters.");
    auto [rect_tape, rect_capstan] = std::get<pair<va::detection::Roi, va::detection::Roi>>(processing_areas);
503

Matteo's avatar
Matteo committed
504
    pprint("Processing...", CYAN);
Matteo's avatar
Matteo committed
505
    processing(video_capture, capstan, tape, args, rect_tape, rect_capstan);
506

Matteo's avatar
Matteo committed
507
    files::save_file(g_output_path / V_IRREG_FILE_1, g_irregularity_file_1.dump(4));
508

Matteo's avatar
Matteo committed
509
    // Irregularities to extract for the AudioAnalyser and to the TapeIrregularityClassifier
Matteo's avatar
Matteo committed
510
    extract_irregularity_images_for_audio(g_output_path, VIDEO_PATH, audio_irr_file, g_irregularity_file_2);
Matteo's avatar
Matteo committed
511
    files::save_file(g_output_path / V_IRREG_FILE_2, g_irregularity_file_2.dump(4));
512

Matteo's avatar
Matteo committed
513
    return EXIT_SUCCESS;
514
}