< Home
Stampa

Disegno di funzioni (parabola)

Sommario

In questa lezione vedremo come utilizzare OpenCV per mostrare su un diagramma cartesiano una funzione matematica. Nel nostro esempio disegneremo una parabola, altre tipologie di funzioni sono lasciate per esercizio allo studente.

Matematicamente una parabola è una funzione f(x) = ax2+bx+c. Vogliamo scrivere un programma che dati i tre coefficienti a,b,c mostrerà la parabola associata. Per rendere inoltre graficamente più immediata la visualizzazione del grafico, chiederemo inoltre all’utente l’ampiezza visibile dell’asse x per cui vuole rappresentata la parabola, quindi la calcoleremo per tutti i valori (fx) con x compreso in quella ampiezza. In ogni caso l’utente potrà fare zoom incrementando o decrementando la “risoluzione” del grafico.

Suddivisione del problema

Per svolgere questo tipo di esercizio suddivideremo il problema in parti:

  • nell’interfaccia utente (main) verrà richiesto all’utente i valori di input a,b,c e x massima. Quest’ultima determinerà il valore di zoom del nostro piano cartesiano.
  • occorrerà una funzione che disegna il piano cartesiano, i suoi assi, e i valori massimi di x ed y.
  • si scriverà poi una funzione che disegna la funzione vera e propria (drawFunction)
  • infine serviranno due piccole funzioni di servizio:
    • una che calcola data la x la corrispondente f(x) cioè la y (calc)
    • una che stampa il singolo punto della funzione a schermo (draw)

E’ bene precisare quindi fin da subito le problematiche da risolvere:

  • il piano cartesiano ha un sistema di riferimento dove l’origine (0,0) è al centro del piano cartesiano. Invece il Canvas OpenCV ha una matrice dove l’origine è in alto a sinistra. Ci serve quindi sempre una conversione da un sistema all’altro per rappresentare le coordinate cartesiane nel canvas. Ad esempio se abbiamo un canvas 400×400 l’origine si troverà nella posizione (200,200). In generale la formula che dobbiamo applicare è la seguente:
    X’ = x + 400/2
    Y’ = -y + 400/2

    dove X’ e Y’ sono le coordinate del punto nel canvas
  • con lo zoom la corrispondenza non è 1 a 1, ma bisogna moltiplicare le coordinate per il fattore zoom individuato. Quindi la formula precedente è in realtà:
    X’ = (x*zoom) + 400/2
    Y’ = -(y*zoom) + 400/2
  • Il fattore di zoom dipende dalla scelta dall’utente: se ad esempio l’utente sceglie come x massima il valore 10 (quindi con valori che vanno da -10 a +10, con dimensione 20), ed il canvas però è di 400 punti di larghezza, avremo uno zoom 400/20 = 20.

Risolti questi problemi passiamo al codice.

Disegno del piano cartesiano

Definiamo due costanti:

#define SIZE 800
#define SIGNS 20

SIZE è la dimensione del canvas, SIGNS indica il numero di segmenti che vogliamo mostrare sugli assi. Possiamo ora scrivere la funzione che mostra il piano cartesiano vuoto.

void drawCartesian(Mat canvas, float zoom)
{
  line(canvas, Point(0, SIZE / 2), Point(SIZE, SIZE / 2), Scalar(0, 120, 120), 1, LINE_4, 0);
  line(canvas, Point(SIZE / 2, 0), Point(SIZE / 2, SIZE), Scalar(0, 120, 120), 1, LINE_4, 0);
  int step = SIZE / SIGNS;
  for (int x = 1; x < SIGNS; x++)
  {
    line(canvas, Point(x * step, SIZE / 2 - step / 10), Point(x * step, SIZE / 2 + step / 10), Scalar(0, 120, 120), 1, LINE_4, 0);
  }
  for (int y = 1; y < SIGNS; y++)
  {
    line(canvas, Point(SIZE / 2 - step / 10, y * step), Point(SIZE / 2 + step / 10, y * step), Scalar(0, 120, 120), 1, LINE_4, 0);
  }
  char max[10];
  sprintf(max, "%d", (int)(SIZE / 2 / zoom));
  cout << max;
  putText(canvas,
          max,
          Point(SIZE - SIZE / 20, SIZE / 2 + SIZE / 40),
          FONT_HERSHEY_SIMPLEX,
          0.5,
          Scalar(0, 0, 0), 2);
  putText(canvas,
          max,
          Point(SIZE / 2 - SIZE / 20, SIZE / 20),
          FONT_HERSHEY_SIMPLEX,
          0.5,
          Scalar(0, 0, 0), 2);
}

nello specifico:

  • disegnamo i due assi;
  • disegnamo i segmenti;
  • calcoliamo x ed y massima a partire dallo zoom e la stampiamo come label

Calcolo e disegno

La funzione di disegno è la seguente:

void drawFunction(Mat canvas, float a, float b, float c, float zoom)
{
  int limit = SIZE / 2 / zoom;
  float x = -1 * limit;
  float step = 1.0 / zoom;
  while (x < limit)
  {
    float y = calc(x, a, b, c);
    draw(canvas, x * zoom, y * zoom);
    imshow("Movimento", canvas);
    waitKey(1);
    x += step;
  }
}

La funzione calcola i limiti di calcolo della funzione in base allo zoom. Se ad esempio abbiamo una SIZE=400 ed zoom 2x, limit varrà 100 (con valori quindi da -100 a +100). Inizializziamo x e lo step (cioè gli incrementi di x). Il ciclo while prevede come condizione di uscita x > limit.

Nel ciclo:

  • calcoliamo la y con la funzione calc.
  • disegnamo il punto (osservare che x ed y sono moltiplicate per lo zoom)
  • mostriamo l’immagine aggiornata
  • attendiamo 1ms
  • incrementiamo x di step

Qui la funzione calc:

float calc(float x, float a, float b, float c)
{
  float y = a * x * x + b * x + c;
  return y;
}

Qui la funzione draw:

void draw(Mat canvas, float x, float y)
{
  Point position = Point(x + SIZE / 2, -1 * y + SIZE / 2);
  circle(canvas,
         position,
         1,
         Scalar(0, 0, 0),
         FILLED,
         LINE_8);
}

Interfaccia utente e controllo

Ecco il codice:

float a,b,c,x;
  cout << "Inserisci il coefficiente a: ";
  cin >> a;
  cout << "Inserisci il coeffiente b: ";
  cin >> b;
  cout << "Inserisci il coefficiente c: ";
  cin >> c;
  cout << "Inserisci ampiezza asse x: ";
  cin >> x;
  float zoom = SIZE / 2 /x;
  bool end = false;
  while (!end)
  {
    Mat canvas(SIZE, SIZE, CV_8UC1, Scalar(255, 255, 255));
    drawCartesian(canvas, zoom);
    drawFunction(canvas, a, b, c, zoom);
    int c = waitKey(0);
    if (c == '+')
    {
      zoom *= 2;
    }
    if (c == '-')
    {
      zoom /= 2;
    }
    if (c == ' ')
    {
      end = true;
    }
  }

  return (0); 

Come si può vedere al termine del rendering della funzione, l’utente coi tasti + e – può modificare lo zoom della funzione.

Ecco un esempio di risultato.