Last active
August 9, 2022 14:54
-
-
Save syaifulnizamyahya/12deb740a31880923b1512e16d054362 to your computer and use it in GitHub Desktop.
Finding correct aspect ratio of flat paper captured from camera
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Get3dRectFrom2d.cpp : This file contains the 'main' function. Program execution begins and ends there. | |
| // | |
| #include "pch.h" | |
| #include <iostream> | |
| #include <opencv2/opencv.hpp> | |
| #include <opencv2/aruco.hpp> | |
| #define CAMERA_WINDOW "Simple ArUco" | |
| using namespace std; | |
| using namespace cv; | |
| static bool readCameraParameters(string filename, Mat &camMatrix, Mat &distCoeffs) { | |
| FileStorage fs(filename, FileStorage::READ); | |
| if (!fs.isOpened()) | |
| return false; | |
| fs["camera_matrix"] >> camMatrix; | |
| fs["distortion_coefficients"] >> distCoeffs; | |
| return true; | |
| } | |
| int main() | |
| { | |
| Mat camMatrix, distCoeffs; | |
| string cameraSettings = "camera.txt"; | |
| bool estimatePose = false; | |
| bool showRejected = true; | |
| if (readCameraParameters(cameraSettings, camMatrix, distCoeffs)) | |
| { | |
| estimatePose = true; | |
| } | |
| Ptr<aruco::Dictionary> dictionary = | |
| aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(aruco::DICT_4X4_50)); | |
| Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create(); | |
| float markerLength = 3.75f; | |
| float markerSeparation = 0.5f; | |
| double totalTime = 0; | |
| int totalIterations = 0; | |
| VideoCapture inputVideo(0); | |
| if (!inputVideo.isOpened()) | |
| { | |
| cout << "cannot open camera"; | |
| } | |
| double prevW = -1, prevH = -1; | |
| double increment = 0.1; | |
| while (inputVideo.grab()) | |
| { | |
| Mat image, imageCopy; | |
| inputVideo.retrieve(image); | |
| double tick = (double)getTickCount(); | |
| vector< int > ids; | |
| vector< vector< Point2f > > corners, rejected; | |
| vector< Vec3d > rvecs, tvecs; | |
| // detect markers and estimate pose | |
| aruco::detectMarkers(image, dictionary, corners, ids, detectorParams, rejected); | |
| if (estimatePose && ids.size() > 0) | |
| aruco::estimatePoseSingleMarkers(corners, markerLength, camMatrix, distCoeffs, rvecs, | |
| tvecs); | |
| double currentTime = ((double)getTickCount() - tick) / getTickFrequency(); | |
| totalTime += currentTime; | |
| totalIterations++; | |
| if (totalIterations % 30 == 0) { | |
| cout << "Detection Time = " << currentTime * 1000 << " ms " | |
| << "(Mean = " << 1000 * totalTime / double(totalIterations) << " ms)" << endl; | |
| } | |
| // draw results | |
| image.copyTo(imageCopy); | |
| if (ids.size() > 0) { | |
| aruco::drawDetectedMarkers(imageCopy, corners, ids); | |
| if (estimatePose) { | |
| for (unsigned int i = 0; i < ids.size(); i++) | |
| aruco::drawAxis(imageCopy, camMatrix, distCoeffs, rvecs[i], tvecs[i], | |
| markerLength * 0.5f); | |
| } | |
| } | |
| if (ids.size() == 4) | |
| { | |
| if (true) | |
| { | |
| // process the image | |
| array<Point2f, 4> srcCorners; // corner that we want | |
| array<Point2f, 4> dstCorners; // destination corner | |
| vector<Point> marker0; // marker corner | |
| vector<Point> marker1; // marker corner | |
| vector<Point> marker2; // marker corner | |
| vector<Point> marker3; // marker corner | |
| //id 8 14 18 47 | |
| for (size_t i = 0; i < ids.size(); i++) | |
| { | |
| // first corner | |
| if (ids[i] == 8) | |
| { | |
| srcCorners[0] = corners[i][0]; // get the first point | |
| //srcCornersSmall[0] = corners[i][2]; | |
| marker0.push_back(corners[i][0]); | |
| marker0.push_back(corners[i][1]); | |
| marker0.push_back(corners[i][2]); | |
| marker0.push_back(corners[i][3]); | |
| } | |
| // second corner | |
| else if (ids[i] == 14) | |
| { | |
| srcCorners[1] = corners[i][1]; // get the second point | |
| //srcCornersSmall[1] = corners[i][3]; | |
| marker1.push_back(corners[i][0]); | |
| marker1.push_back(corners[i][1]); | |
| marker1.push_back(corners[i][2]); | |
| marker1.push_back(corners[i][3]); | |
| } | |
| // third corner | |
| else if (ids[i] == 18) | |
| { | |
| srcCorners[2] = corners[i][2]; // get the thirt point | |
| //srcCornersSmall[2] = corners[i][0]; | |
| marker2.push_back(corners[i][0]); | |
| marker2.push_back(corners[i][1]); | |
| marker2.push_back(corners[i][2]); | |
| marker2.push_back(corners[i][3]); | |
| } | |
| // fourth corner | |
| else if (ids[i] == 47) | |
| { | |
| srcCorners[3] = corners[i][3]; // get the fourth point | |
| //srcCornersSmall[3] = corners[i][1]; | |
| marker3.push_back(corners[i][0]); | |
| marker3.push_back(corners[i][1]); | |
| marker3.push_back(corners[i][2]); | |
| marker3.push_back(corners[i][3]); | |
| } | |
| } | |
| // create a black image with the same size of cam image | |
| Mat mask = Mat::zeros(imageCopy.size(), CV_8UC1); | |
| Mat dstImage = Mat::zeros(imageCopy.size(), CV_8UC1); | |
| // draw white fill on marker corners | |
| { | |
| int num = (int)marker0.size(); | |
| if (num != 0) | |
| { | |
| const Point * pt4 = &(marker0[0]); | |
| fillPoly(mask, &pt4, &num, 1, Scalar(255, 255, 255), 8); | |
| } | |
| } | |
| { | |
| int num = (int)marker1.size(); | |
| if (num != 0) | |
| { | |
| const Point * pt4 = &(marker1[0]); | |
| fillPoly(mask, &pt4, &num, 1, Scalar(255, 255, 255), 8); | |
| } | |
| } | |
| { | |
| int num = (int)marker2.size(); | |
| if (num != 0) | |
| { | |
| const Point * pt4 = &(marker2[0]); | |
| fillPoly(mask, &pt4, &num, 1, Scalar(255, 255, 255), 8); | |
| } | |
| } | |
| { | |
| int num = (int)marker3.size(); | |
| if (num != 0) | |
| { | |
| const Point * pt4 = &(marker3[0]); | |
| fillPoly(mask, &pt4, &num, 1, Scalar(255, 255, 255), 8); | |
| } | |
| } | |
| // draw the mask | |
| imshow("black white lines", mask); | |
| // we dont have the correct size/aspect ratio | |
| double width = 256.0f, height = 256.0f; | |
| dstCorners[0] = Point2f(0.0f, 0.0f); | |
| dstCorners[1] = Point2f(width, 0.0f); | |
| dstCorners[2] = Point2f(width, height); | |
| dstCorners[3] = Point2f(0.0f, height); | |
| // get perspectivetransform | |
| Mat M = getPerspectiveTransform(srcCorners, dstCorners); | |
| // warp perspective | |
| Mat dst; | |
| Size dsize = Size(cvRound(dstCorners[2].x), cvRound(dstCorners[2].y)); | |
| warpPerspective(mask, dst, M, dsize); | |
| // show warped image | |
| imshow("perspective transformed", dst); | |
| // get width and length of the first marker | |
| // start from (0,0) and cross | |
| int cx = 0, cy = 0; // track our current coordinate | |
| Scalar v, vx, vy; // pixel value at coordinate | |
| bool cont = true; | |
| while (cont) | |
| { | |
| v = dst.at<uchar>(cx, cy); // get pixel value at current coordinate | |
| if (cx > 1 && cy > 1) | |
| { | |
| vx = dst.at<uchar>(cx - 1, cy); | |
| vy = dst.at<uchar>(cx, cy - 1); | |
| } | |
| // if pixel not black, continue crossing | |
| if ((int)v.val[0] != 0) | |
| { | |
| cx++; | |
| cy++; | |
| } | |
| // current pixel is black | |
| // if previous y pixel is not black, means that we need to walk the pixel right | |
| else if ((int)((Scalar)dst.at<uchar>(cx, cy - 1)).val[0] != 0) | |
| { | |
| cx = cx + 1; | |
| } | |
| // if previous x pixel is not black, means that we need to walk the pixel down | |
| else if ((int)((Scalar)dst.at<uchar>(cx - 1, cy)).val[0] != 0) | |
| { | |
| cy = cy + 1; | |
| } | |
| // the rest is the same with previous 2, only with higher previous pixel to check | |
| // need to do this because sometimes pixels is jagged | |
| else if ((int)((Scalar)dst.at<uchar>(cx, cy - 2)).val[0] != 0) | |
| { | |
| cx = cx + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx - 2, cy)).val[0] != 0) | |
| { | |
| cy = cy + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx, cy - 3)).val[0] != 0) | |
| { | |
| cx = cx + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx - 3, cy)).val[0] != 0) | |
| { | |
| cy = cy + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx, cy - 4)).val[0] != 0) | |
| { | |
| cx = cx + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx - 4, cy)).val[0] != 0) | |
| { | |
| cy = cy + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx, cy - 5)).val[0] != 0) | |
| { | |
| cx = cx + 1; | |
| } | |
| else if ((int)((Scalar)dst.at<uchar>(cx - 5, cy)).val[0] != 0) | |
| { | |
| cy = cy + 1; | |
| } | |
| else | |
| { | |
| cx = cx - 1; | |
| cy = cy - 1; | |
| cont = false; | |
| } | |
| // reached the end of the picture | |
| if (cx >= dst.cols) | |
| { | |
| cont = false; | |
| } | |
| else if (cy >= dst.rows) | |
| { | |
| cont = false; | |
| } | |
| } | |
| if (cx == cy) | |
| { | |
| //we have perfect square | |
| } | |
| if (cx > cy) | |
| { | |
| // wide | |
| width = (height * ((double)cx / (double)cy)); | |
| } | |
| else | |
| { | |
| // tall | |
| height = (width * ((double)cy / (double)cx)); | |
| } | |
| // we dont want the size varied too much every frame, | |
| // so limits the increment or decrement for every frame | |
| // initialize first usage | |
| if (prevW<0) | |
| { | |
| prevW = width; | |
| } | |
| if (prevH<0) | |
| { | |
| prevH = height; | |
| } | |
| if (width > prevW + increment) | |
| { | |
| width = prevW + increment; | |
| } | |
| else if (width < prevW - increment) | |
| { | |
| width = prevW - increment; | |
| } | |
| prevW = width; | |
| if (height > prevH + increment) | |
| { | |
| height = prevH + increment; | |
| } | |
| else if (height < prevH - increment) | |
| { | |
| height = prevH - increment; | |
| } | |
| prevH = height; | |
| // show resized image | |
| Size s(width, height); | |
| Mat resized; | |
| resize(dst, resized, s); | |
| imshow("resized", resized); | |
| } | |
| } | |
| if (showRejected && rejected.size() > 0) | |
| aruco::drawDetectedMarkers(imageCopy, rejected, noArray(), Scalar(100, 0, 255)); | |
| imshow("out", imageCopy); | |
| if (waitKey(1) == 27) { | |
| break; | |
| } | |
| } | |
| cout << "Hello World!\n"; | |
| cin.ignore(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment