/* Questo script esegue l'analisi di un video fornito per rilevare le discontinuità che vengono trovate. Tutte le informazioni necessarie all'agoritmo si possono individuare nei file XML all'interno della cartella config. @author Maniero Mirco, Ylenia Grava @version 2.0 @date 01-04-2019 */ #include #include #include #include #include #include #include #include #include #include #include #include #include // uuid class #include // generators #include // streaming operators etc. #include #include #include #include #include #include #include "rapidxml-1.13/rapidxml.hpp" #include "utility.h" #include "forAudioAnalyser.h" namespace fs = std::__fs::filesystem; using namespace cv; using namespace rapidxml; using namespace std; using json = nlohmann::json; /* ------------------------------------------------------------------------------ VARIABLES ------------------------------------------------------------------------------ */ const char* window = "Select ROI"; bool callback, rotated = false; bool drag = false, pinchRollerSelection = false, savingPinchRoller = false, pinchRollerRect = false; bool savingBrand = false; cv::Point point1, point2, point1b, point2b,point1c, point2c, point3, point4, point5, M; cv::Mat myFrame; int x_l, x_r, y_u, y_d, xM, yM, y_m1, y_m2, BD; // Processing rectangle coordinates int xc_l, xc_r, yc_u, yc_d; // PinchRoller rectangle coordinates int processingSelection = true; Point2f vertices[4]; double mA, qA, mB, qB, mC, qC, mD, qD; // Rotated rectangle variables int rotationEnabled = false; int x_r_orig, x_l_orig, y_u_orig, y_d_orig; float mediaPrevFrame = 0; bool firstBrand = true; // The first frame containing brands on tape must be saved int firstTime = 1; // First analysis float firstBrandInstant = 0; // config.json parameters bool brands; std::string irregularityFileInputPath; std::string outputPath; std::string videoPath; float speed; float thresholdPercentual; float thresholdPercentualPinchRoller; // JSON files json configurationFile; json irregularityFileInput; json irregularityFileOutput1; json irregularityFileOutput2; // RotatedRect identifying the processing area RotatedRect rect; /* ------------------------------------------------------------------------------ PREPROCESSING ------------------------------------------------------------------------------ */ // void mouseHandler(int event, int x, int y, int flags, void* param) { // // Instructions // if (processingSelection && event == EVENT_LBUTTONUP && drag) // std::cout << "\033[32mProcessing area identified.\033[0m\nYou can modify the rectangle by clicking within its area and dragging the mouse.\nAfterwards, you can press CTRL and then use the mouse to rotate the rectangle.\n\033[36mPress 'y' to confirm\033[0m or any other key to exit the program.\n" << std::endl; // if (pinchRollerSelection && event == EVENT_LBUTTONUP && drag) // std::cout << "\033[32mPinchRoller area identified.\n\033[36mPress 'y' to confirm\033[0m or any other key to exit the program.\n" << std::endl; // // Initial drawing of a rectangle // if (processingSelection || pinchRollerSelection) { // if (event == EVENT_LBUTTONDOWN && !drag) { // point1 = cv::Point(x, y); // drag = true; // if (pinchRollerSelection) { // xc_l = x; // yc_u = y; // } else { // x_l = x; // y_u = y; // } // } // if (event == EVENT_MOUSEMOVE && drag) { // // Mouse dragged, ROI being selected // cv::Mat img1 = myFrame.clone(); // if (img1.empty()) { // std::cout << "Empty frame." << std::endl; // } // point2 = cv::Point(x, y); // cv::rectangle(img1, point1, point2, cv::Scalar(255, 255, 0), 2); // cv::imshow(window, img1); // } // if (event == EVENT_LBUTTONUP && drag) { // cv::Mat img2 = myFrame.clone(); // point2 = cv::Point(x, y); // drag = false; // cv::rectangle(img2, point1, point2, cv::Scalar(255, 255, 0), 2); // cv::imshow(window, img2); // callback = true; // processingSelection = false; // if (pinchRollerSelection) { // xc_r = x; // yc_d = y; // pinchRollerSelection = false; // pinchRollerRect = true; // } else { // x_r = x; // y_d = y; // } // x_l_orig = x_l; // x_r_orig = x_r; // y_u_orig = y_u; // y_d_orig = y_d; // } // } else if (!pinchRollerRect) { // PinchRoller rectangle cannot be rotated or modified and after its definition it is not possible to modify again the processing rectangle // // If CTRL key is pressed, the user wants to rotate the rectangle // if (flags == cv::EVENT_FLAG_CTRLKEY && !rotationEnabled) { // rotationEnabled = true; // std::cout << "Rotation enabled." << std::endl; // // Finding original rectangle center M // xM = (x_l_orig + x_r_orig)/2; // yM = (y_u_orig + y_d_orig)/2; // M = cv::Point(xM,yM); // } // if (rotationEnabled) { // if (event == EVENT_LBUTTONDOWN && !drag) { // drag = true; // point1 = cv::Point(x,y); // } // if (event == EVENT_MOUSEMOVE && drag) { // cv::Mat img1 = myFrame.clone(); // point2 = cv::Point(x,y); // RotatedRect rRect = RotatedRect(Point2f(xM, yM), Size2f(x_r_orig - x_l_orig, y_d_orig - y_u_orig), point2.x - point1.x); // rRect.points(vertices); // for (int i = 0; i < 4; i++) // cv::line(img1, vertices[i], vertices[(i + 1) % 4], Scalar(255, 255, 0)); // cv::imshow(window, img1); // } // if (event == EVENT_LBUTTONUP && drag) { // rotated = true; // drag = false; // cv::Mat img2 = myFrame.clone(); // point2 = cv::Point(x, y); // RotatedRect rRect = RotatedRect(Point2f(xM,yM), Size2f(x_r_orig - x_l_orig, y_d_orig - y_u_orig), point2.x - point1.x); // Point2f vertices[4]; // rRect.points(vertices); // for (int i = 0; i < 4; i++) // cv::line(img2, vertices[i], vertices[(i + 1) % 4], Scalar(255, 255, 0), 2); // cv::imshow(window, img2); // double m1, q1, m2, q2, m3, q3, m4, q4; // findRectBound(vertices[0], vertices[1], vertices[2], vertices[3], m1, m2, m3, m4, q1, q2, q3, q4); // // Finding vertices order // int ordinate[4] = {(int)vertices[0].y, (int)vertices[1].y, (int)vertices[2].y, (int)vertices[3].y}; // if (*std::min_element(ordinate, ordinate + 4) == (int)vertices[0].y) { // y_u = vertices[0].y; // y_d = vertices[2].y; // mA = m1; qA = q1; mD = m4; qD = q4; mB = m2; qB = q2; mC = m3; qC = q3; // if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[1].y) { // y_m1 = vertices[1].y; // y_m2 = vertices[3].y; // BD = true; // } else { // y_m1 = vertices[3].y; // y_m2 = vertices[1].y; // BD = false; // } // } else if (*std::min_element(ordinate, ordinate + 4) == (int)vertices[1].y) { // y_u = vertices[1].y; // y_d = vertices[3].y; // mA = m2; qA = q2; mD = m1; qD = q1; mB = m3; qB = q3; mC = m4; qC = q4; // if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[2].y) { // y_m1 = vertices[2].y; // y_m2 = vertices[0].y; // BD = true; // } else { // y_m1 = vertices[0].y; // y_m2 = vertices[2].y; // BD = false; // } // } else if (*std::min_element(ordinate, ordinate + 4) == (int)vertices[2].y) { // y_u = vertices[2].y; // y_d = vertices[0].y; // mA = m3; qA = q3; mD = m2; qD = q2; mB = m4; qB = q4; mC = m1; qC = q1; // if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[3].y) { // y_m1 = vertices[3].y; // y_m2 = vertices[1].y; // BD = true; // } else { // y_m1 = vertices[1].y; // y_m2 = vertices[3].y; // BD = false; // } // } else { // y_u = vertices[3].y; // y_d = vertices[1].y; // mA = m4; qA = q4; mD = m3; qD = q3; mB = m1; qB = q1; mC = m2; qC = q2; // if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[0].y) { // y_m1 = vertices[0].y; // y_m2 = vertices[2].y; // BD = true; // } else { // y_m1 = vertices[2].y; // y_m2 = vertices[0].y; // BD = false; // } // } // float ascisse [4] = {vertices[0].x, vertices[1].x, vertices[2].x, vertices[3].x}; // float min_ascissa = ascisse[3]; // float max_ascissa = ascisse[0]; // for (int j = 0; j < 4; j++) { // if (ascisse[j] > max_ascissa) { // max_ascissa = ascisse[j]; // } // if (ascisse[j] < min_ascissa) { // min_ascissa = ascisse[j]; // } // } // x_r = max_ascissa; // x_l = min_ascissa; // rotationEnabled = false; // std::cout << "End rotation.\n" << std::endl; // } // } else if (!rotated) { // The rectangle can be modified only before its rotation // // Here the user wants to modify the rectangle // if (event == EVENT_LBUTTONDOWN && !drag) { // point3 = cv::Point(x,y); // drag = true; // } // if (event == EVENT_MOUSEMOVE && drag && !processingSelection) { // // Mouse dragged, ROI being selected // cv::Mat img4 = myFrame.clone(); // point4 = cv::Point(x, y); // point2b = cv::Point(point4.x, y_d); //x mouse, y_d // point1b = cv::Point(point4.x, y_u); //x mouse, y_u // point1c = cv::Point(x_l, point4.y); //x_l, y mouse // point2c = cv::Point(x_r, point4.y); //x_r, y mouse // if ((point3.y >= y_u) && (point3.y <= y_d) && (point3.x >= x_l) && (point3.x <= x_r)) { // Within the rectangle // if (point3.x >= (x_l + x_r)/2) { // Right rectangle side // if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) { // if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_r)) { // cv::rectangle(img4, point1, point2c, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point2 = point2c; // } else { // cv::rectangle(img4, point1, point2b, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point2 = point2b; // } // } else { // if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_r)) { // cv::rectangle(img4, point1c, point2, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point1 = point1c; // } else { // cv::rectangle(img4, point1, point2b, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point2 = point2b; // } // } // } else { // Left side of the rectangle // if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) { // if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_l)) { // cv::rectangle(img4, point1, point2c, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point2 = point2c; // } else { // cv::rectangle(img4, point1b, point2, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point1 = point1b; // } // } else { // if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_l)) { // cv::rectangle(img4, point1c, point2, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point1 = point1c; // } else { // cv::rectangle(img4, point1b, point2, cv::Scalar(255,0,0), 2); // cv::imshow(window, img4); // point1 = point1b; // } // } // } // } // } // if (event == EVENT_LBUTTONUP && drag) { // cv::Mat img5 = myFrame.clone(); // point5 = cv::Point(x, y); // drag = false; // point2b = cv::Point(point5.x, y_d); // point1b = cv::Point(point5.x, y_u); // point1c = cv::Point(x_l, point5.y); // point2c = cv::Point(x_r, point5.y); // if ((point3.y >= y_u) && (point3.y <= y_d) && (point3.x >= x_l) && (point3.x <= x_r)) { // Within the rectangle. // if (point3.x >= (x_l + x_r)/2) { // Right side of the rectangle // if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) { // if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_r)) { // cv::rectangle(img5, point1, point2c, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point2 = point2c; // y_d = point2.y; // } else { // cv::rectangle(img5, point1, point2b, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point2 = point2b; // x_r = point2.x; // } // } else { // if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_r)) { // cv::rectangle(img5, point1c, point2, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point1 = point1c; // y_u = point1.y; // } else { // cv::rectangle(img5, point1, point2b, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point2 = point2b; // x_r = point2.x; // } // } // } else { // Left side of the rectangle // if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) { // if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_l)) { // cv::rectangle(img5, point1, point2c, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point2 = point2c; // y_d = point2.y; // } else { // cv::rectangle(img5, point1b, point2, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point1 = point1b; // x_l = point1.x; // } // } else { // if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_l)) { // cv::rectangle(img5, point1c, point2, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point1 = point1c; // y_u = point1.y; // } else { // cv::rectangle(img5, point1b, point2, cv::Scalar(255, 0, 0), 2); // cv::imshow(window, img5); // point1 = point1b; // x_l = point1.x; // } // } // } // } // x_l_orig = x_l; // x_r_orig = x_r; // y_u_orig = y_u; // y_d_orig = y_d; // } // } // } // } // int selectRect() { // cv::VideoCapture videoCapture(videoPath); // if (!videoCapture.isOpened()) { // std::cerr << "\033[31m" << "Video unreadable." << std::endl; // return -1; // } // // Get total number of frames // int totalFrames = videoCapture.get(CAP_PROP_FRAME_COUNT); // // Get a frame where the tape will surely be in its reading position (20 seconds since the beginning) // if (500 <= totalFrames) // // Set frame position // videoCapture.set(CAP_PROP_POS_FRAMES, 500); // else { // std::cout << "Video too short! Exiting..." << std::endl; // return -1; // } // videoCapture >> myFrame; // cv::namedWindow(window, WINDOW_AUTOSIZE); // cv::imshow(window, myFrame); // cv::setMouseCallback(window, mouseHandler, 0); // std::cout << "\nDraw with the mouse the rectangle that contains the desired processing area.\n"; // if (speed == 15) // std::cout << "At 15 ips, it should cover \033[1mthe entire\033[0m reading head.\n"; // else // std::cout << "At 7.5 ips, it should cover \033[1mhalf\033[0m the reading head.\n"; // std::cout << "\033[33mATTENTION: please, start drawing from the top-left corner of the rectangle, and end on the bottom-right corner.\033[0m" << std::endl; // char c = waitKey(0); // if (c != 'y') { // return -1; // } // if (callback) { // if (myFrame.empty()) { // std::cout << "Empty frame." << std::endl; // return -1; // } // if (rotated) { // point1.x = x_l; // point1.y = y_u; // point2.x = x_r; // point2.y = y_d; // for (int i = 0; i < 4; i++) // cv::line(myFrame, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 2); // } else { // cv::rectangle(myFrame, point1, point2, cv::Scalar(0, 0, 255), 2); // } // cv::imshow(window, myFrame); // // Writing rectangle coordinates on a file // ofstream coordFile; // coordFile.open("../config/coordinate.txt"); // coordFile << x_r << "\n"; // coordFile << x_l << "\n"; // coordFile << y_u << "\n"; // coordFile << y_d << "\n"; // coordFile.close(); // } else { // std::cout << "Rectangle not found." << std::endl; // return -1; // } // return 0; // } // int selectPinchRoller() { // pinchRollerSelection = true; // cv::imshow(window, myFrame); // cv::setMouseCallback(window, mouseHandler, 0); // std::cout << "Draw the rectangle that contains the pinchRoller area.\n" << std::endl; // char c = waitKey(0); // if (c != 'y') { // return -1; // } // if (callback) { // if (myFrame.empty()) { // std::cout << "Empty frame." << std::endl; // return -1; // } // cv::rectangle(myFrame, point1, point2, cv::Scalar(0, 255, 0), 2); // cv::imshow(window, myFrame); // } else { // std::cout << "Rectangle not found." << std::endl; // return -1; // } // return 0; // } // int defineProcessingArea() { // if (selectRect() != 0) { // return -1; // } // std::cout << "Proceeding with the second rectangle." << std::endl; // callback = false; // if (selectPinchRoller() != 0) { // return -1; // } // std::cout << "\033[36mPress 'y' to confirm everything and proceed with the analysis, \033[35m'm' to modify the ROIs\033[0m or any other key to exit." << std::endl; // char c_bis = waitKey(0); // destroyWindow(window); // if (c_bis == 'y') { // return 0; // } else if (c_bis == 'm') { // cv::namedWindow(window, WINDOW_AUTOSIZE); // cv::imshow(window, myFrame); // cv::setMouseCallback(window, mouseHandler, 0); // return 1; // } else { // return -1; // } // } bool frameDifference(cv::Mat prevFrame, cv::Mat currentFrame, int msToEnd) { // Processing area int areaPixels = rect.size.width * rect.size.height; float differentPixelsThreshold = areaPixels * thresholdPercentual / 100; // // PinchRoller area // int pixelsNumberPinchRoller = (xc_r - xc_l) * (yc_d - yc_u); // float differentPixelsThresholdPinchRoller = (thresholdPercentualPinchRoller * pixelsNumberPinchRoller)/100; /***************** Extract matrices corresponding to the processing area *********************/ // CODE FROM https://answers.opencv.org/question/497/extract-a-rotatedrect-area/ // matrices we'll use Mat M, rotatedPrevFrame, croppedPrevFrame, rotatedCurrentFrame, croppedCurrentFrame; // get angle and size from the bounding box float angle = rect.angle; Size rect_size = rect.size; // thanks to http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/ if (rect.angle < -45.) { angle += 90.0; swap(rect_size.width, rect_size.height); } // get the rotation matrix M = getRotationMatrix2D(rect.center, angle, 1.0); // perform the affine transformation warpAffine(prevFrame, rotatedPrevFrame, M, prevFrame.size(), INTER_CUBIC); warpAffine(currentFrame, rotatedCurrentFrame, M, currentFrame.size(), INTER_CUBIC); // crop the resulting image getRectSubPix(rotatedPrevFrame, rect_size, rect.center, croppedPrevFrame); getRectSubPix(rotatedCurrentFrame, rect_size, rect.center, croppedCurrentFrame); // imshow("Current frame", currentFrame); // imshow("Cropped Current Frame", croppedCurrentFrame); // waitKey(); // END CODE FROM https://answers.opencv.org/question/497/extract-a-rotatedrect-area/ cv::Mat differenceFrame = difference(croppedPrevFrame, croppedCurrentFrame); int blackPixelsPinchRoller = 0; int decEnd = (msToEnd % 1000) / 100; int secEnd = (msToEnd - (msToEnd % 1000)) / 1000; int minEnd = secEnd / 60; secEnd = secEnd % 60; /******************************* PinchRoller analysis ****************************************/ // // In the last minute of the video, check for pinchRoller position for endTape event // if ((msToEnd < 60000) && pinchRollerRect) { // for (int i = yc_u; i < yc_d; i++) { // for (int j = xc_l; j < xc_r; j++) { // if (differenceFrame.at(i, j)[0] == 0) { // // There is a black pixel, then there is a difference between previous and current frames // blackPixelsPinchRoller++; // } // } // } // if (blackPixelsPinchRoller > differentPixelsThresholdPinchRoller) { // savingPinchRoller = true; // return true; // } else { // savingPinchRoller = false; // } // } /****************************** Segment analysis ****************************************/ int blackPixels = 0; float mediaCurrFrame; int totColoreCF = 0; for (int i = 0; i < croppedCurrentFrame.rows; i++) { for (int j = 0; j < croppedCurrentFrame.cols; j++) { totColoreCF += croppedCurrentFrame.at(i, j)[0] + croppedCurrentFrame.at(i, j)[1] + croppedCurrentFrame.at(i, j)[2]; if (differenceFrame.at(i, j)[0] == 0) { blackPixels++; } } } // if (!rotated) { // for (int i = y_u; i < y_d; i++) { // for (int j = x_l; j < x_r; j++) { // totColoreCF = totColoreCF + currentFrame.at(i, j)[0] + currentFrame.at(i, j)[1] + currentFrame.at(i, j)[2]; // areaPixels++; // if (differenceFrame.at(i, j)[0] == 0) { // blackPixels++; // } // } // } // } else { // int x_r_max = (y_u - qA)/mA; // int x_l_max = (y_u - qD)/mD; // for (int j = y_u; j < y_m1; j++) { // double mD_pos = -mD; // x_r_max = (j - qA)/mA; // x_l_max = (qD - j)/mD_pos; // for (int i = x_l_max; i < x_r_max; i++) { // totColoreCF = totColoreCF + currentFrame.at(i, j)[0] + currentFrame.at(i, j)[1] + currentFrame.at(i, j)[2]; // areaPixels++; // if (differenceFrame.at(j, i)[0] == 0) { // blackPixels++; // } // } // } // // Use B and D straight lines // for (int j = y_m1; j < y_m2; j++) { // if (BD) { // If the right point is higher than the left one (eg: y0 y1 y3 y2) // x_r_max = (j - qB)/mB; // x_l_max = (j - qD)/mD; // for (int i = x_l_max; i < x_r_max; i++) { // totColoreCF = totColoreCF + currentFrame.at(i, j)[0] + currentFrame.at(i, j)[1] + currentFrame.at(i, j)[2]; // areaPixels++; // if (differenceFrame.at(j, i)[0] == 0) { // blackPixels++; // } // } // } else { // AC case for eg: y0 y3 y1 y2 // x_r_max = (j - qA)/mA; // x_l_max = (j - qC)/mC; // for (int i = x_l_max; i < x_r_max; i++) { // totColoreCF = totColoreCF + currentFrame.at(i, j)[0] + currentFrame.at(i, j)[1] + currentFrame.at(i, j)[2]; // areaPixels++; // if (differenceFrame.at(j, i)[0] == 0) { // blackPixels++; // } // } // } // } // // Using B and C straight lines // for (int j = y_m2; j < y_d; j++) { // x_r_max = (j - qB)/mB; // x_l_max = (j - qC)/mC; // for (int i = x_l_max; i < x_r_max; i++) { // totColoreCF = totColoreCF + currentFrame.at(i, j)[0] + currentFrame.at(i, j)[1] + currentFrame.at(i, j)[2]; // areaPixels++; // if (differenceFrame.at(j, i)[0] == 0) { // blackPixels++; // } // } // } // } mediaCurrFrame = totColoreCF/areaPixels; float tsh = areaPixels * thresholdPercentual / 100; if (blackPixels > tsh) { if (brands) { if (mediaPrevFrame > (mediaCurrFrame + 10) || mediaPrevFrame < (mediaCurrFrame - 10)) { // They are not similar for color average // Update mediaPrevFrame mediaPrevFrame = mediaCurrFrame; firstBrandInstant = msToEnd; return true; } // If the above condition is not verified, update anyway mediaPrevFrame mediaPrevFrame = mediaCurrFrame; // At the beginning of the video, wait at least 1 second 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 && (firstBrandInstant - msToEnd > 1000)) { firstBrand = false; savingBrand = true; return true; } } else { return true; } } return false; } int processing(cv::VideoCapture videoCapture, std::string fileName) { // Video duration int frameNumbers_v = videoCapture.get(CAP_PROP_FRAME_COUNT); float fps_v = videoCapture.get(CAP_PROP_FPS); // FPS can be non-integers!!! float videoLength = (float) frameNumbers_v / fps_v; // [s] int videoLength_ms = videoLength * 1000; int savedFrames = 0, unsavedFrames = 0; float lastSaved = -160; int savingRate = 0; // [ms] // Whenever we find an Irregularity, we want to skip a lenght equal to the reading head (3 cm = 1.18 inches) if (speed == 7.5) savingRate = 157; // Time taken to cross 3 cm at 7.5 ips else if (speed == 15) savingRate = 79; // Time taken to cross 3 cm at 15 ips // The first frame of the video won't be processed cv::Mat prevFrame; videoCapture >> prevFrame; firstBrandInstant = videoLength_ms - videoCapture.get(CAP_PROP_POS_MSEC); while (videoCapture.isOpened()) { cv::Mat frame; videoCapture >> frame; if (!frame.empty()) { int ms = videoCapture.get(CAP_PROP_POS_MSEC); int msToEnd = videoLength_ms - ms; if (ms == 0) // With OpenCV library, this happens at the last few frames of the video before realising that "frame" is empty. break; int secToEnd = msToEnd / 1000; int minToEnd = (secToEnd / 60) % 60; secToEnd = secToEnd % 60; std::string secStrToEnd = std::to_string(secToEnd), minStrToEnd = std::to_string(minToEnd); if (minToEnd < 10) minStrToEnd = "0" + minStrToEnd; if (secToEnd < 10) secStrToEnd = "0" + secStrToEnd; std::cout << "\rIrregularities: " << savedFrames << ". "; std::cout << "Remaining video time [mm:ss]: " << minStrToEnd << ":" << secStrToEnd << std::flush; if ((ms - lastSaved > savingRate) && frameDifference(prevFrame, frame, msToEnd)) { // An Irregularity is found! // De-interlacing frame cv::Mat oddFrame(frame.rows/2, frame.cols, CV_8UC3); cv::Mat evenFrame(frame.rows/2, frame.cols, CV_8UC3); separateFrame(frame, oddFrame, evenFrame); // Finding an image containing the whole tape Point2f pts[4]; rect.points(pts); cv::Mat subImageNastro(frame, cv::Rect(100, min(pts[1].y, pts[2].y), frame.cols - 100, static_cast(rect.size.height))); // De-interlacing the image with the whole tape cv::Mat oddSubImage(subImageNastro.rows/2, subImageNastro.cols, CV_8UC3); int evenSubImageRows = subImageNastro.rows/2; if (subImageNastro.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, subImageNastro.cols, CV_8UC3); separateFrame(subImageNastro, oddSubImage, evenSubImage); std::string timeLabel = getTimeLabel(ms); std::string safeTimeLabel = getSafeTimeLabel(ms); saveIrregularityImage(safeTimeLabel, fileName, oddFrame, oddSubImage); // Append Irregularity information to JSON boost::uuids::uuid uuid = boost::uuids::random_generator()(); irregularityFileOutput1["Irregularities"] += {{ "IrregularityID", boost::lexical_cast(uuid) }, { "Source", "v" }, { "TimeLabel", timeLabel } }; irregularityFileOutput2["Irregularities"] += {{ "IrregularityID", boost::lexical_cast(uuid) }, { "Source", "v" }, { "TimeLabel", timeLabel }, { "ImageURI", pathTape224 + "/"+ fileName + "_" + safeTimeLabel + ".jpg" } }; lastSaved = ms; savedFrames++; } else { unsavedFrames++; } prevFrame = frame; } else { std::cout << "\nEmpty frame!" << std::endl; videoCapture.release(); break; } } ofstream myFile; myFile.open("log.txt", ios::app); myFile << "Saved frames are: " << savedFrames << std::endl; myFile.close(); return 0; } bool findProcessingArea(json configurationFile) { // Returned variable bool found = false; // Read parameters from JSON int minDist, angleThresh, scaleThresh, posThresh, minDistTape, angleThreshTape, scaleThreshTape, posThreshTape; try { minDist = configurationFile["MinDist"]; angleThresh = configurationFile["AngleThresh"]; scaleThresh = configurationFile["ScaleThresh"]; posThresh = configurationFile["PosThresh"]; minDistTape = configurationFile["MinDistTape"]; angleThreshTape = configurationFile["AngleThreshTape"]; scaleThreshTape = configurationFile["ScaleThreshTape"]; posThreshTape = configurationFile["PosThreshTape"]; } catch (nlohmann::detail::type_error e) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\n" << e.what() << std::endl; return -1; } // Obtain grayscale version of myFrame Mat myFrameGrayscale; cvtColor(myFrame, myFrameGrayscale, COLOR_BGR2GRAY); // Downsample myFrameGrayscale in half pixels Mat myFrameGrayscaleHalf; pyrDown(myFrameGrayscale, myFrameGrayscaleHalf, Size(myFrame.cols/2, myFrame.rows/2)); // Get input shape in grayscale Mat templateImage = imread("../input/readingHead.png", IMREAD_GRAYSCALE); // Downsample tapeShape in half pixels Mat templateImageHalf; pyrDown(templateImage, templateImageHalf, Size(templateImage.cols/2, templateImage.rows/2)); // Select the image to process Mat processingImage = myFrameGrayscaleHalf; // Select the template to be detected Mat templateShape = templateImageHalf; // Algorithm and parameters Ptr alg = createGeneralizedHoughGuil(); alg -> setMinDist(minDist); alg -> setLevels(360); alg -> setDp(2); alg -> setMaxBufferSize(1000); alg -> setAngleStep(1); alg -> setAngleThresh(angleThresh); alg -> setMinScale(0.8); alg -> setMaxScale(1.2); alg -> setScaleStep(0.1); alg -> setScaleThresh(scaleThresh); alg -> setPosThresh(posThresh); alg -> setCannyLowThresh(100); alg -> setCannyHighThresh(300); alg -> setTemplate(templateShape); vector positions, positions2; TickMeter tm; int oldPosThresh = posThresh; tm.start(); // Parameters are quite slack, therefore more than one match should be expected. // By inspecting different angles (only between +10 and -10 degrees of maximum inclination) or increasing the position threshold, // the algorithm should eventually identify only one region. while (positions.size() != 1) { std::cout << "Proceeding with positive angles." << endl; alg -> setMinAngle(0); alg -> setMaxAngle(10); alg -> detect(processingImage, positions); if (positions.size() == 1) break; std::cout << "Proceeding with negative angles." << endl; alg -> setMinAngle(350); alg -> setMaxAngle(360); alg -> detect(processingImage, positions); if (positions.size() == 1) break; std::cout << "Increasing position threshold." << endl; oldPosThresh += 10; alg -> setPosThresh(oldPosThresh); } tm.stop(); std::cout << "Reading head detection time : " << tm.getTimeMilli() << " ms" << endl; Point2f pos(positions[0][0], positions[0][1]); float scale = positions[0][2]; float angle = positions[0][3]; rect.center = pos * 2; // * 2 since the processed image is half the original one rect.size = Size2f(templateShape.cols * scale * 2, templateShape.rows * scale * 2); // * 2 for the same reason rect.angle = angle; Point2f pts[4]; rect.points(pts); // Red for the reading head line(myFrame, pts[0], pts[1], Scalar(0, 0, 255), 2); line(myFrame, pts[1], pts[2], Scalar(0, 0, 255), 2); line(myFrame, pts[2], pts[3], Scalar(0, 0, 255), 2); line(myFrame, pts[3], pts[0], Scalar(0, 0, 255), 2); // Defining the processing area for identifying the tape under the reading head. // // Parameters for extracting a rectangle containing the found rectangle completely (also if it is slightly rotated) // and with twice its height (since the tape is immediatley below the found rectangle). int tapeProcessingAreaX = min(pts[0].x, pts[1].x); int tapeProcessingAreaY = min(pts[1].y, pts[2].y) + (max(pts[0].y, pts[3].y) - min(pts[1].y, pts[2].y)) * 2/3; // Shift down the area int tapeProcessingAreaWidth = max(pts[3].x-pts[1].x, pts[2].x-pts[0].x); int tapeProcessingAreaHeight = max(pts[3].y-pts[1].y, pts[0].y-pts[2].y); Rect tapeProcessingAreaRect(tapeProcessingAreaX, tapeProcessingAreaY, tapeProcessingAreaWidth, tapeProcessingAreaHeight); // Obtain the new image Mat tapeProcessingArea = myFrame(tapeProcessingAreaRect); // Obtain grayscale version of tapeProcessingArea Mat tapeProcessingAreaGrayscale = myFrameGrayscale(tapeProcessingAreaRect); // Read template image - it is smaller than before, therefore there is no need to downsample templateImage = imread("../input/tapeArea.png", IMREAD_GRAYSCALE); // Reset algorithm and set parameters alg = createGeneralizedHoughGuil(); alg -> setMinDist(minDistTape); alg -> setLevels(360); alg -> setDp(2); alg -> setMaxBufferSize(1000); alg -> setAngleStep(1); alg -> setAngleThresh(angleThreshTape); alg -> setMinScale(0.9); alg -> setMaxScale(1.1); alg -> setScaleStep(0.05); alg -> setScaleThresh(scaleThreshTape); alg -> setPosThresh(posThreshTape); alg -> setCannyLowThresh(100); alg -> setCannyHighThresh(300); alg -> setTemplate(templateImage); oldPosThresh = posThreshTape; tm.start(); for (int i = 0; i < 10; i++) { std::cout << "Proceeding with positive angles." << endl; alg -> setMinAngle(0); alg -> setMaxAngle(10); alg -> detect(tapeProcessingAreaGrayscale, positions2); if (positions2.size() == 1) { found = true; break; } std::cout << "Proceeding with negative angles." << endl; alg -> setMinAngle(350); alg -> setMaxAngle(360); alg -> detect(tapeProcessingAreaGrayscale, positions2); if (positions2.size() == 1) { found = true; break; } std::cout << "Increasing position value." << endl; oldPosThresh += 10; alg -> setPosThresh(oldPosThresh); } tm.stop(); std::cout << "Tape detection time : " << tm.getTimeMilli() << " ms" << endl; for (int i = 0; i < positions2.size(); i++) { Point2f pos2(positions2[i][0], positions2[i][1]); scale = positions2[i][2]; angle = positions2[i][3]; rect.center = pos2; rect.size = Size2f(templateShape.cols * scale, templateShape.rows * scale); rect.angle = angle; rect.points(pts); // Update points with tape processing area coordinates pts[0] = Point2f(pts[0].x+tapeProcessingAreaX, pts[0].y+tapeProcessingAreaY); pts[1] = Point2f(pts[1].x+tapeProcessingAreaX, pts[1].y+tapeProcessingAreaY); pts[2] = Point2f(pts[2].x+tapeProcessingAreaX, pts[2].y+tapeProcessingAreaY); pts[3] = Point2f(pts[3].x+tapeProcessingAreaX, pts[3].y+tapeProcessingAreaY); // Update rect rect = RotatedRect(pts[0], pts[1], pts[2]); line(myFrame, pts[0], pts[1], Scalar(0, 255, 0), 2); line(myFrame, pts[1], pts[2], Scalar(0, 255, 0), 2); line(myFrame, pts[2], pts[3], Scalar(0, 255, 0), 2); line(myFrame, pts[3], pts[0], Scalar(0, 255, 0), 2); } imshow("Tape area(s)", myFrame); waitKey(); return found; } int main(int argc, char** argv) { /**************************************** CONFIGURATION FILE ****************************************/ // Read configuration file std::ifstream iConfig("../config/config.json"); iConfig >> configurationFile; // Initialise parameters try { brands = configurationFile["Brands"]; irregularityFileInputPath = configurationFile["IrregularityFileInput"]; outputPath = configurationFile["OutputPath"]; videoPath = configurationFile["PreservationAudioVisualFile"]; speed = configurationFile["Speed"]; thresholdPercentual = configurationFile["ThresholdPercentual"]; thresholdPercentualPinchRoller = configurationFile["ThresholdPercentualPinchRoller"]; } catch (nlohmann::detail::type_error e) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\n" << e.what() << std::endl; return -1; } // Input JSON check std::ifstream iJSON(irregularityFileInputPath); if (iJSON.fail()) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nIrregularityFileInput.json cannot be found or opened." << std::endl; return -1; } std::string fileName, extension; if (findFileName(videoPath, fileName, extension) == -1) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThe PreservationAudioVisualFile cannot be found or opened." << std::endl; return -1; } if (speed != 7.5 && speed != 15) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nSpeed parameter must be 7.5 or 15 ips." << std::endl; return -1; } if (thresholdPercentual < 0 || thresholdPercentual > 100) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThresholdPercentual parameter must be a percentage value." << std::endl; return -1; } if (thresholdPercentualPinchRoller < 0 || thresholdPercentualPinchRoller > 100) { std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThresholdPercentual parameter must be a percentage value." << std::endl; return -1; } if (speed == 15) thresholdPercentual += 4; std::cout << "\nParameters from config.json file:" << std::endl; std::cout << " Brands: " << brands << std::endl; std::cout << " Speed: " << speed << std::endl; std::cout << " ThresholdPercentual: " << thresholdPercentual << std::endl; std::cout << " ThresholdPercentualPinchRoller: " << thresholdPercentualPinchRoller << std::endl; // Read input JSON iJSON >> irregularityFileInput; /**************************************** DEFINE THE PROCESSING AREA ****************************************/ // bool processingAreaDefined = false; // while (!processingAreaDefined) { // // Reset variables // savingPinchRoller = false; // pinchRollerRect = false; // processingSelection = true; // rotated = false; // int defineProcArea = defineProcessingArea(); // if (defineProcArea == -1) { // std::cout << "Exit." << std::endl; // return -1; // } else if (defineProcArea == 1) { // std::cout << "Modifying the area again." << std::endl; // } else { // processingAreaDefined = true; // } // } /******************************************* TAPE AREA DETECTION *******************************************/ cv::VideoCapture videoCapture(videoPath); if (!videoCapture.isOpened()) { std::cerr << "\033[31m" << "Video unreadable." << std::endl; return -1; } // Get total number of frames int totalFrames = videoCapture.get(CAP_PROP_FRAME_COUNT); // Set frame position to half video length videoCapture.set(CAP_PROP_POS_FRAMES, totalFrames/2); // Get frame and show it videoCapture >> myFrame; // Find the processing area corresponding to the tape area over the reading head bool found = findProcessingArea(configurationFile); // Reset frame position videoCapture.set(CAP_PROP_POS_FRAMES, 0); /**************************** WRITE USEFUL INFORMATION TO LOG FILE ***************************/ // Get now time std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::string ts = std::ctime(&t); ofstream myFile; myFile.open("log.txt", ios::app); myFile << endl << fileName << endl; myFile << "tsh: " << thresholdPercentual << " tshp: " << thresholdPercentualPinchRoller << std::endl; myFile << ts; // No endline character for avoiding middle blank line. if (found) { cout << "Processing area found!" << endl; myFile << "Processing area found!" << endl; myFile.close(); } else { cout << "Processing area not found. Try changing JSON parameters." << endl; myFile << "Processing area not found." << endl; myFile.close(); return 1; // Program terminated early } /********************************* MAKE REQUIRED DIRECTORIES *********************************/ makeDirectories(fileName, outputPath, brands); /**************************************** PROCESSING *****************************************/ std::cout << "\n\033[32mStarting processing...\033[0m\n" << std::endl; // Processing timer time_t startTimer, endTimer; startTimer = time(NULL); processing(videoCapture, fileName); endTimer = time(NULL); float min = (endTimer - startTimer) / 60; float sec = (endTimer - startTimer) % 60; std::string result("Processing elapsed time: " + std::to_string((int)min) + ":" + std::to_string((int)sec)); cout << endl << result << endl; myFile.open("log.txt", ios::app); myFile << result << std::endl << std::endl; myFile.close(); /**************************************** IRREGULARITY FILES ****************************************/ std::ofstream outputFile1; std::string outputFile1Name = outputPath + "IrregularityFileOutput1.json"; outputFile1.open(outputFile1Name); outputFile1 << irregularityFileOutput1 << std::endl; // Irregularities to extract for the AudioAnalyser and to the TapeIrregularityClassifier extractIrregularityImagesForAudio(outputPath, videoPath, irregularityFileInput, irregularityFileOutput2); std::ofstream outputFile2; std::string outputFile2Name = outputPath + "IrregularityFileOutput2.json"; outputFile2.open(outputFile2Name); outputFile2 << irregularityFileOutput2 << std::endl; return 0; }