简体   繁体   English

SDL2如何在调整窗口大小后缩放鼠标坐标?

[英]SDL2 how to scale mouse coordinates after window resizing?

I am using OpenCV ans SDL2 libraries for a C++ application. 我正在为C ++应用程序使用OpenCV ans SDL2库。

Opencv is used to catch the frame from the webcam and the frame is then converted to be shown on a SDL window. Opencv用于从网络摄像头捕获帧,然后将该帧转换为在SDL窗口中显示。 SDL is used to catch inputs from the mouse. SDL用于捕获鼠标的输入。

At every click pn the SDL window, a circle is drawn in the position of the click over the webcam frame. 在SDL窗口中,每次单击时,都会在网络摄像头框架上的单击位置绘制一个圆圈。

The problem is the following: when resizing the window manually when the application is running (from the OS not from the code), the points that were already drawn will be plotted correctly but if I left-click to draw another point in the resized window, the point will be plotted completely out of bounds of the window. 问题如下:在运行应用程序时手动调整窗口大小(从OS而不是从代码),将正确绘制已经绘制的点,但是如果我单击鼠标左键以在调整大小的窗口中绘制另一个点,该点将完全绘制在窗口范围之外。

This is the main function: 这是主要功能:

#ifndef __OPENCV__
#define __OPENCV__
#include "opencv2/opencv.hpp"
#endif
#include <iostream>
#include <unistd.h>
#include <vector>
#include <SDL.h>
#include <SDL_events.h>

using namespace cv;
using namespace std;


int mouseCallback(SDL_MouseButtonEvent ev, Mat frame);


int main(int argc, char* argv[])
{
    /* Initialise SDL */
    if( SDL_Init( SDL_INIT_VIDEO ) < 0)
    {
        fprintf( stderr, "Could not initialise SDL: %s\n", SDL_GetError() );
        exit( -1 );
    }
    const String sourceReference = argv[1];
    int camNum;
    string sensorName;
    try
    {
        camNum = stoi(sourceReference); // throws std::length_error
    }
    catch (const std::exception& e)// reference to the base of a polymorphic object
    {
        std::cout<<"Exception: " << e.what()<<endl; // information from length_error printed
        return -1;
    }
    cout<<"camera initializing\n";
    VideoSettings cam(camNum + CAP_V4L);
    cout<<"camera initialized\n";
    /* or
    VideoCapture captUndTst;
    captUndTst.open(sourceCompareWith);*/
    cout<<"Ch3ck c4m3ra is 0p3n3d\n";
    if ( !cam.isOpened())
    {
        cout << "Could not open reference " << sourceReference << endl;
        return -1;
    }
    Mat frame;
    cam>>frame;
    SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, frame.cols, frame.rows,
        SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);

    SDL_SetWindowTitle(win, "Camera");
    SDL_Renderer * renderer = SDL_CreateRenderer(win, -1, 0);
    SDL_Event genericEvent;

    SDL_Surface* frameSurface;
    SDL_Texture* frameTexture;
    while(cam.isOpened())
    {
        while( SDL_PollEvent(&genericEvent) )
        {
            switch( genericEvent.type )
            {
                case SDL_MOUSEBUTTONDOWN:
                    mouseCallback(genericEvent.button, frame);
                    break;
                /* SDL_QUIT event (window close) */
                case SDL_QUIT:
                    return 0;
                    break;
                default:
                    break;
            }
        }
        cam>>frame;
        draw(frame_out);

        //Convert to SDL_Surface
        frameSurface = SDL_CreateRGBSurfaceFrom((void*)frame_out.data,
            frame_out.size().width, frame_out.size().height,
            24, frame_out.cols *3,
            0xff0000, 0x00ff00, 0x0000ff, 0);

        if(frameSurface == NULL)
        {
            SDL_Log("Couldn't convert Mat to Surface.");
            return -2;
        }

        //Convert to SDL_Texture
        frameTexture = SDL_CreateTextureFromSurface(renderer, frameSurface);
        if(frameTexture == NULL)
        {
            SDL_Log("Couldn't convert Mat(converted to surface) to Texture."); //<- ERROR!!
            return -1;
        }
        //imshow("Camera", frame_out);
        SDL_RenderCopy(renderer, frameTexture, NULL, NULL);
        SDL_RenderPresent(renderer);
        /* A delay is needed to show (it actually wait for an input)*/
        if(waitKey(delay)>delay){;}

    }
    SDL_DestroyTexture(frameTexture);
    SDL_FreeSurface(frameSurface);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(win);
    return 0;
}


int mouseCallback(SDL_MouseButtonEvent ev, Mat frame)
{

    if(ev.button == SDL_BUTTON_LEFT)
    {
        mouseLeft( ev.x, ev.y);
    }
}

When clicking the left button of the mouse the function called is mouseCallback that is calling a function mouseLeft defined as: 当单击鼠标mouseCallback ,调用的函数是mouseCallback ,它调用的函数mouseLeft定义为:

void mouseLeft(int x, int y)
{
  Point pt;// = *((Point*)param);
  pt.x = x;
  pt.y = y;
  pts.push_back(pt);
  int ii=0;
  int size = pts.size()-1;
  while(ii<size && size>0)
  {
      if (euclideanDist(pt, pts.at(ii))<minDistance)
      {
          //cout<<pts->size()<<endl;
          pts.erase (pts.begin()+ii);

          // std::vector::end returns an iterator to the element following
          //the last element of the container, not to the last element.
          pts.erase(pts.end()-1);
          size-=2;
      }
      else
      {
          ;
      }
      ii++;

  }
}

pts is a vector of Points (it is originally a member of a class but can be declared globally for this example). pts是Points的向量(它最初是类的成员,但在此示例中可以全局声明)。 When mouseLeft is called if the point of click is close enough to any point in pts then it is removed from the vector, otherwise a new point is pushed back. 如果单击点与pts任意点足够接近,则调用mouseLeft时, mouseLeft其从向量中删除,否则将向后推新的点。

The function draw is drawing the points stored in pts plus lines connecting the points: 函数draw是绘制存储在pts中的pts以及连接这些点的线:

void draw(Mat &frame)
{
  vector<Point> new_pts = pts; //avoid modifying the elements we are drawing
  int sizeVector = pts.size();
  if (sizeVector==1)
  {
      try
      {
        circle( frame, pts.at(0), radius, Scalar( 0, 0, 0), FILLED, 8,0);
      }
        catch (const out_of_range& e)
        {
            cout<<"Exception with command "<<endl;
            cout<<"circle( frame, pts.at(0), radius, Scalar( 0, 0, 0), FILLED, 8,0)"<<endl;
        }
  }
  else
  {
    int ii=0;
    while(ii<(sizeVector-1) && sizeVector>0)
    {
        try
        {
          circle( frame, pts.at(ii),   radius, Scalar( 0, 0, 0), FILLED, 8,0);
          circle( frame, pts.at(ii+1), radius, Scalar( 0, 0, 0), FILLED, 8,0);
          line(frame, pts.at(ii), pts.at(ii+1), Scalar( 0, 0, 0 ), 4, 8 );
        }
        catch (const out_of_range& e)
        {
          cout<<"Exception with commands: "<<endl;
          cout<<"\tcircle( frame, pts.at(ii),   radius, Scalar( 0, 0, 0), FILLED, 8,0)"<<endl;
          cout<<"\tcircle( frame, pts.at(ii+1), radius, Scalar( 0, 0, 0), FILLED, 8,0);"<<endl;
          cout<<"\tline(frame, pts.at(ii), pts.at(ii+1), Scalar( 0, 0, 0 ), 4, 8 );"<<endl;
        }

        if(pts.size()<=0) break;
        ii++;

        sizeVector= pts.size();
    }
  }
}

The same problem happens if instead of creating the window with SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, frame.cols, frame.rows, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 如果不是使用SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, frame.cols, frame.rows, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);创建窗口, SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, frame.cols, frame.rows, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);发生相同的问题SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, frame.cols, frame.rows, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); I double its dimesions such as: SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, 2*frame.cols, 2*frame.rows, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 我将其尺寸加倍,例如: SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, 2*frame.cols, 2*frame.rows, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);

Note that the code posted is a readaptation of the original one to keep it simple and understandable. 请注意,发布的代码是对原始代码的重新改编,以使其保持简单易懂。 If anything is missing or not clear please tell me so that I can edit the question properly. 如果有任何遗漏或不清楚的地方,请告诉我,以便我可以正确地编辑问题。

The problem was the following: basically the mouse coordinates catched from SDL are correct: when I make the window bigger the coordinates can even have large value. 问题如下:从SDL捕获的鼠标坐标基本上是正确的:当我将窗口放大时,坐标甚至可以具有较大的值。

The problem is that those coordinates were plotted on the frame taken from the cam by openCV which has always the same dimensions (basically the resolution of the cam). 问题在于,这些坐标是通过openCV绘制在从凸轮获取的框架上的,该框架始终具有相同的尺寸(基本上是凸轮的分辨率)。

Before drawing the point on the frame I have to rescale the coordinates of the mouse-click within the dimensions of the image taken from the cam. 在框架上绘制点之前,我必须在从凸轮获取的图像尺寸内重新调整鼠标单击的坐标。

I found a solution by basically storing the initial dimension of the window in two variables using after the window is defined: 我找到了一个解决方案,方法是在定义窗口之后,将窗口的初始尺寸基本上存储在两个变量中:

SDL_Window* win = SDL_CreateWindow("Camera", 100, 100, frame.cols, frame.rows,
    SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
int oldWidth = frame.cols, oldHeight= frame.rows;
int width, height;
float scaleX=1, scaleY=1;

then adding the following event in the already existing switch-case: 然后在已经存在的切换案例中添加以下事件:

            case SDL_WINDOWEVENT:
            {
                if (genericEvent.window.event==SDL_WINDOWEVENT_RESIZED)
                {
                    //oldWidth = width;
                    //oldHeight = height;
                    SDL_GetWindowSize(win, &width, &height);
                    scaleX =  (float)(width)/ float(oldWidth);
                    scaleY = (float)(height) / (float)(oldHeight);
                }
                break;
            }

and the modifying the `mouseCallback as 然后将`mouseCallback修改为

int mouseCallback(SDL_MouseButtonEvent ev, float scaleX, float scaleY, Mat frame)
{
    if(ev.button == SDL_BUTTON_LEFT)
    {
        cout<<scaleX<<" "<<scaleY<<endl;
        int scaled_x =  static_cast<int> ((float)(ev.x)/scaleX);
        int scaled_y = static_cast<int> ((float)(ev.y)/ scaleY);
        std::cout<<"scaled x: "<<scaled_x<<", scaled y: "<<scaled_y<<endl;
        mouseLeft( scaled_x,scaled_y);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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