简体   繁体   中英

OpenCV C++ How to use mouseclickleft to store coordinates and draw circles

Currently I am working on a program that is able to draw a spline based on four points. As can be seen below, I currently have it so that you need to manually enter these points into the code. I am hoping to change that using mouse clicks. What I intend to do is have the user be able to click on the image, which draws a dot immediately. This will be able to be done as many times as the user wants, but it will draw a spline when every four points are drawn. I am running into two problems. Firstly when trying to implement a simple mouse click to just draw a circle in onMouse , it is not working. Secondly, I am wondering how I will be able to store the coordinates of each mouse click as they are happening, so I can create a loop that plugs these coordinates into the below matrix equations to draw a spline while the program is running. I have an idea of how I can store the first, but I am not sure how to handle multiple coordinates as well as how to have a definite variable name for each that I can put into the equations. Apologies for the long post, I have some big aspirations for this project despite having limited experience, so I would appreciate some guidance.

#include <iostream>
#include<opencv2/core/core.hpp> //Mat is defined there
#include<opencv2/imgproc/imgproc.hpp>  //resize an image
#include<opencv2/highgui/highgui.hpp> //input or output: imread(), imshow()

using namespace std;
using namespace cv;

Mat img;

void onMouse(int event, int x, int y, int flags, void* param)
{
    if (event == EVENT_LBUTTONDOWN)
    {
        printf("(%d, %d)\n", x, y);



        int r = 1;
        circle(img, Point(x, y), r, Scalar(0, 255, 100), 5);

    }
}


int main(int argc, char** argv)
{
    
    
    img.create(600, 800, CV_8UC3);
    img = Scalar(255, 255, 255);
    

    

    double a_values[3][2] = { { 2.0, 1 }, { 0.0, 3.0 }, { -2.0, -4.0 } };
    //A.create(3, 2, CV_64FC1); //create a matrix 3x2 with double value

    Mat A = Mat(3, 2, CV_64FC1, a_values); //Constructor: pass the values directly using a 2D array.
    printf("matrix A:\n");
    cout << A << endl;

    

    Mat B;
    B.create(2, 2, CV_64FC1); //2x2 matrix
    B.ptr<double>(0)[0] = 1.0;
    B.ptr<double>(0)[1] = 2.0;
    B.ptr<double>(1)[0] = 0.0;
    B.ptr<double>(1)[1] = -2.0;
    printf("matrix B:\n");
    cout << B << endl;

    Mat C = A * B;  //Matrix product

    printf("matrix C:\n");
    cout << C << endl;

    Mat B_inv = B.inv();
    printf("matrix B inverse:\n");
    cout << B_inv << endl;

    double m_values[4][4] = { { 0, 0, 0, 1 }, { 1, 1, 1, 1 }, { 0, 0, 1, 0 }, { 3, 2, 1, 0 } };
    Mat M = Mat(4, 4, CV_64FC1, m_values);

    Mat M_inv = M.inv();
    printf("matrix M inverse:\n");
    cout << M_inv << endl;

    double h_values[4][4] = { { 2, -2, 1, 1 }, { -3, 3, -2, -1 }, { 0, 0, 1, 0 }, { 1, 0, 0, 0 } };
    Mat Hermite = Mat(4, 4, CV_64FC1, h_values);

    double point_values[4][2] = { { 200, 350 }, { 220, 400 }, { 600, 300 }, { 390, 300 } };
    Mat Points = Mat(4, 2, CV_64FC1, point_values);

    Mat Final = Hermite * Points;
    printf("Final matrix:\n");
    cout << Final << endl;
    /* If there are two points P1(30, 50) and P2(80, 120), I want to draw a spline between
        them and also make sure the speed of the spline at point P1 equals(500, 2) and at P2 equals(10, 1000). */

    
        //Draw 1st point
    circle(img, Point(200, 350), 1, Scalar(0, 0, 255), 3);

    //Draw 2nd point
    circle(img, Point(220, 400), 1, Scalar(0, 0, 255), 3);

    circle(img, Point(400, 450), 1, Scalar(0, 0, 255), 3);

    circle(img, Point(350, 500), 1, Scalar(0, 0, 255), 3);

    //Draw the spline between 1st and 2nd points
    //Use a loop on t [0, 1], for different t values, compute x(t), y(t); then use circle() to draw it
    // x(t) = axt3 + bxt2 + cxt + dx                               
    // y(t) = ayt3 + byt2 + cyt + dy


    double ax = (int)(Final.at<double>(0, 0));
    double ay = (int)(Final.at<double>(0, 1));
    double bx = (int)(Final.at<double>(1, 0));
    double by = (int)(Final.at<double>(1, 1));
    double cx = (int)(Final.at<double>(2, 0));
    double cy = (int)(Final.at<double>(2, 1));
    double dx = (int)(Final.at<double>(3, 0));
    double dy = (int)(Final.at<double>(3, 1));

    printf("ax:\n");
    cout << ax << endl;
    printf("dx:\n");
    cout << dx << endl;
    
    

    for (double t = 0.0; t <= 1.0; t += 0.001)
    {
        int x = ax * t * t * t + bx * t * t + cx * t + dx;
        int y = ay * t * t * t + by * t * t + cy * t + dy;
        circle(img, Point(x, y), 1, Scalar(0, 0, 0), 1);
    }

    

    while (1)
    {

        imshow("Spline", img);
        char c = waitKey(1);
        if (c == 27)
            break;

    }

    
    return 1;
    
    
}

You defined onMouse function, but you did not register it to any window. You need to create window with cv::namedWindow and then register your callback for mouse with cv::setMouseCallback . In your case, just add this before anything in your main function:

cv::namedWindow("Spline");
cv::setMouseCallback("Spline", onMouse);

Here is the simple program to draw circle/point to provided image. It will store point where you clicked and it will draw all points to the image.

#include <opencv2/opencv.hpp>

std::vector<cv::Point> points;

void onMouse(int action, int x, int y, int, void*) {
  if (action == cv::EVENT_LBUTTONDOWN) {
    points.push_back(cv::Point{x, y});
  }
}

int main(int argc, char** argv) {
  const auto mainWindow = "Main Window";
  cv::namedWindow(mainWindow);
  cv::setMouseCallback(mainWindow, onMouse);
  cv::Mat image {600, 800, CV_8UC3, cv::Scalar{255, 255, 255}};

  while (true) {
    for (const auto& point : points) {
      cv::circle(image, point, 5, cv::Scalar{0, 200, 0}, -1);
    }
    cv::imshow(mainWindow, image);
    cv::waitKey(25);
  }
  cv::waitKey();
  cv::destroyAllWindows();
  return 0;
}

EDIT: Be aware that this solution will re-render old points. You can improve this by drawing just new points. When you click, add that point to temporary vector and when you draw all points from temporary vector , clear that vector . For more improvement, you can check if vector is not empty and than call cv::imshow .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM