Search code examples
c++opencvdrawaruco

How to draw a rectangle on the Aruco marker in opencv?


I have a program to draw and detect the aruco markers, and write the marker id on it. I need a rectangle to appear on each marker instead of the marker id, i could draw a rectangle but in a fixed position not on the marker, here is the code:

#include <opencv2\highgui.hpp>
#include <opencv2\aruco.hpp>
#include <opencv2\core.hpp>
#include <opencv2\imgcodecs.hpp>
#include <opencv2\imgproc.hpp>
#include <opencv2\calib3d.hpp>
#include <sstream>
#include <fstream>
#include <iostream>
using namespace cv;
using namespace std;


int main(int argc, char *argv[]) {
    cv::VideoCapture inputVideo;
    inputVideo.open(0);
    Mat outputMarker;
    auto  markerDict = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME::DICT_4X4_50);
    for (int i = 0; i < 50; i++) {
        aruco::drawMarker(markerDict, i, 500, outputMarker, 1);
        ostringstream convert;
        String imageName = "4x4marker_";
        convert << imageName << i << ".jpg";
        imwrite(convert.str(), outputMarker);

        while (inputVideo.grab()) {
            cv::Mat image, imageCopy;
            inputVideo.retrieve(image);
            image.copyTo(imageCopy);

            std::vector<int> ids;
            std::vector<std::vector<cv::Point2f> > corners;
            cv::aruco::detectMarkers(image, markerDict, corners, ids);

            // if at least one marker detected
            if (ids.size() > 0)
                cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);
            int x = 0;
            int y = 3;


            rectangle(imageCopy, Point(imageCopy.cols/2, imageCopy.rows/2),
                Point(x,y),Scalar::all(255), -1, 8, 0);



            cv::imshow("out", imageCopy);
            char key = (char)cv::waitKey(5);
            if (key == 27)
                break;
        }
    }
}

An example marker for easing code testing.

Here


Solution

  • When you use the detectMarkers function, it returns the corners of each detection. In your case, you are putting it into the std::vector<std::vector<cv::Point2f> > corners. To draw the rectangles you ask for, you can do something similar to (the following code is an example and has not been tested):

    for (size_t i = 0; i< corners.size(); +i)
    {
      cv::Point2f p0(image.cols,image.rows);
      cv::Ponit2f p1(0,0);
      for (auto p: corners[i])
      {
        if (p.x < p0.x)
          p0.x = p.x;
        if (p.y < p0.y)
          p0.y = p.y;
        if (p.x > p1.x)
          p1.x = p.x;
        if (p.y > p1.y)
          p1.y = p.y;
      }
      rectangle(imageCopy, p0, p1,Scalar::all(255), -1, 8, 0);
    }
    

    However, you probably want to draw a polygon that will better fit the marker, as the marker projection on the image will not be a rectangle if the marker is not perfectly perpendicular to the camera optical axis and there's no distortion. For that, you can use fillPoly, or if you don't want it filled line.

    Here is a fully functional example tested with OpenCV 4.3.0-pre and 3.2.0 in Ubuntu and 3.4.9 in Windows:

    #include <iostream>
    
    #include <opencv2/aruco.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    
    int main(int argc, char** argv)
    {
      // Check cv versionmake
      std::cout << "Using OpenCV version: " << CV_VERSION << std::endl;
    
      // Create video input
      cv::VideoCapture inputVideo;
      int input_source(0);
      if (argc > 1)
        input_source = std::atoi(argv[1]);
      if (!inputVideo.open(input_source))
      {
        std::cerr << "Error opening input video soruce: " << input_source << std::endl;
        return EXIT_FAILURE;
      }
    
      // Create marker dictionary
      auto marker_dict = cv::aruco::getPredefinedDictionary(cv::aruco::PREDEFINED_DICTIONARY_NAME::DICT_4X4_50);
    
      // Get imshow ready
      cv::namedWindow("Display window", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
      cv::resizeWindow("Display window", 800, 600);
    
      // Grab images until escape is pressed
      int key = 0;
      while (key != 27 && inputVideo.grab())
      {
        // Retrieve image
        cv::Mat image;
        inputVideo.retrieve(image);
    
        // Get image output ready
        cv::Size image_size = image.size();
        cv::Mat out_image(image_size.height, 3 * image_size.width, CV_8UC3);
        cv::Mat left(out_image, cv::Rect(0, 0, image_size.width, image_size.height));
        image.copyTo(left);
        cv::Mat mid(out_image, cv::Rect(image_size.width, 0, image_size.width, image_size.height));
        image.copyTo(mid);
        cv::Mat right(out_image, cv::Rect(2 * image_size.width, 0, image_size.width, image_size.height));
        image.copyTo(right);
    
        // Add names to images
        int corner_offset = 50;
        cv::putText(left, "Original image", cv::Point(corner_offset, corner_offset), cv::FONT_HERSHEY_DUPLEX, 1.0,
                    CV_RGB(0, 0, 0), 2);
        cv::putText(mid, "Image with OpenCV drawing", cv::Point(corner_offset, corner_offset), cv::FONT_HERSHEY_DUPLEX, 1.0,
                    CV_RGB(0, 0, 0), 2);
        cv::putText(right, "Image with custom drawing", cv::Point(corner_offset, corner_offset), cv::FONT_HERSHEY_DUPLEX,
                    1.0, CV_RGB(0, 0, 0), 2);
    
        // Detect markers
        std::vector<int> ids;
        std::vector<std::vector<cv::Point2f> > corners;
        cv::aruco::detectMarkers(image, marker_dict, corners, ids);
    
        // Draw markers using opencv tool
        cv::aruco::drawDetectedMarkers(mid, corners, ids);
    
        // Draw markers custom
        for (size_t i = 0; i < corners.size(); ++i)
        {
          // Convert to integer ponits
          int num = static_cast<int>(corners[i].size());
          std::vector<cv::Point> points;
          for (size_t j = 0; j < corners[i].size(); ++j)
            points.push_back(cv::Point(static_cast<int>(corners[i][j].x), static_cast<int>(corners[i][j].y)));
          const cv::Point* pts = &(points[0]);
    
          // Draw
          cv::fillPoly(right, &pts, &num, 1, cv::Scalar(255, 0, 0));
    
          // Draw contour
          for (size_t j = 0; j < corners[i].size(); ++j)
          {
            size_t next = (j + 1) % corners[i].size();
            cv::line(right, corners[i][j], corners[i][next], cv::Scalar(0, 255, 0), 5);
          }
        }
    
        // Display
        cv::imshow("Display window", out_image);
        key = cv::waitKey(5);
      }
    
      return EXIT_SUCCESS;
    }
    

    This is the output you will get with this code: enter image description here