Получить изображение (прямоугольник на плоскости) с коррекцией перспективы с камеры (2d изображение) - PullRequest
0 голосов
/ 03 января 2019

Я хочу получить исходное изображение с моей камеры.

Это изображение, которое получает моя камера.Изображение, которое я хочу, это фиолетовый прямоугольник.This is my input

Я хочу обрезать фиолетовый прямоугольник и исправить предполагаемый.Это изображение, которое я ожидаю получить.This is what I want to get

Размер изображения неизвестен.Это может быть широкий или высокий.

Как я могу сделать это в OpenCV?Любые советы, руководства?Обратите внимание, что для каждого маркера у меня уже есть координаты каждого угла маркера (эта информация может помочь)

Редактировать.Некоторый прогресс.

Я узнал, что мне нужны функции getPerspectiveTransform и warpPerspective.

Я использую оба метода с этим.

    if (ids.size() == 4)
    {
        array<Point2f, 4> srcCorners;           // corner that we want 
        array<Point2f, 4> srcCornersSmall; 
        array<Point2f, 4> dstCorners;           // destination 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];
            }
            // second corner
            else if (ids[i] == 14)
            {
                srcCorners[1] = corners[i][1];      // get the second point
                srcCornersSmall[1] = corners[i][3];
            }
            // third corner
            else if (ids[i] == 18)
            {
                srcCorners[2] = corners[i][2];      // get the thirt point
                srcCornersSmall[2] = corners[i][0];
            }
            // fourth corner
            else if (ids[i] == 47)
            {
                srcCorners[3] = corners[i][3];      // get the fourth point
                srcCornersSmall[3] = corners[i][1];
            }
        }

        dstCorners[0] = Point2f(0.0f, 0.0f);
        dstCorners[1] = Point2f(256.0f, 0.0f);
        dstCorners[2] = Point2f(256.0f, 256.0f);
        dstCorners[3] = Point2f(0.0f, 256.0f);

        // get perspectivetransform
        Mat M = getPerspectiveTransform(srcCorners, dstCorners);

        // warp perspective
        Mat dst;
        Size dsize = Size(cvRound(dstCorners[2].x), cvRound(dstCorners[2].y));
        warpPerspective(imageCopy, dst, M, dsize);

        // show
        imshow("perspective transformed", dst);

    }

В то время как я получаю желаемое (почти) изображение, оно не в правильном соотношении ширина / высота.

Это вывод, который я получаю.

enter image description here

Как исправить соотношение высоты и ширины?

1 Ответ

0 голосов
/ 07 января 2019

Наконец-то понял.Идея состоит в том, чтобы нарисовать маркер в виде белого прямоугольника на черном изображении.Затем обрежьте изображение, которое мы хотим, и нарисуйте его в новом изображении.Так как правильный размер для нового изображения неизвестен, мы просто устанавливаем размер как квадрат.Новое изображение должно быть черным изображением с белыми прямоугольниками в углу.Начиная с (0,0), мы затем пересекаем изображение и проверяем значение пикселя.Значение пикселя должно быть белым.Если значение пикселя черное, мы находимся за пределами белого поля.Проследите значение пикселя вдоль x и y, потому что белое поле может быть высоким или широким.Как только мы находим нижнюю правую часть белого ящика, мы получаем размер белого ящика.Измените эту белую коробку на квадрат.Используйте ту же функцию для изменения масштаба изображения.

Это изображение, снятое камерой

enter image description here

Нарисуйте маркер в виде белого поля вчерное изображение.

enter image description here

Обрезать и деформировать в квадрат.

enter image description here

Получите ширину и высоту белого поля в левом верхнем углу.Как только у нас появится функция масштабирования, примените ее.

enter image description here

Если кому-то интересно, вот коды.

// 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;
}

Меня больше интересует математическое решение, но пока этого достаточно.Если вы, ребята, знаете гораздо лучший подход (быстрее), дайте мне знать.

...