「Qtの基礎 - 画像処理」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

編集の要約なし
1行目: 1行目:
== 概要 ==
== 概要 ==
Qtでは画像を取り扱うクラスとしてQBitmap、QPixmap、QImageの3種類あり、それぞれ使い分けが必要である。<br>
Qtでは画像を取り扱うクラスとして、<code>QBitmap</code>クラス、<code>QPixmap</code>クラス、<code>QImage</code>クラスの3種類あり、それぞれ使い分けが必要である。<br>
<br>
<br>
ここでは、ウィジェット上に画像を表示する手順を記載する。<br>
ここでは、ウィジェット上に画像を表示する手順を記載する。<br>
7行目: 7行目:
== QGraphicsViewクラスを使用する ==
== QGraphicsViewクラスを使用する ==
画像を表示する機能を持っているウィジェットは無いため、ビュークラスを継承して派生クラスを作成する。<br>
画像を表示する機能を持っているウィジェットは無いため、ビュークラスを継承して派生クラスを作成する。<br>
(元々、QGraphicsViewクラスは、QGraphicsSceneクラスを表示するためのものである)<br>
(元々、<code>QGraphicsView</code>クラスは、<code>QGraphicsScene</code>クラスを表示するためのものである)<br>
<br>
<br>
ビュークラスを継承した派生クラスには、元の画像のデータを保持するメンバ変数を定義する。<br>
ビュークラスを継承した派生クラスには、元の画像のデータを保持するメンバ変数を定義する。<br>
31行目: 31行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
QGraphicsViewクラスのpaintEventメソッドは、再描画時に呼び出される仮想関数である。<br>
<code>QGraphicsView</code>クラスの<code>paintEvent</code>メソッドは、再描画時に呼び出される仮想関数である。<br>
paintEventメソッドをオーバーライドして描画部分を記述する。<br>
<code>paintEvent</code>メソッドをオーバーライドして描画部分を記述する。<br>
<br>
<br>
paintEventメソッドとsetImgメソッドの定義は、以下の通りである。<br>
paintEventメソッドとsetImgメソッドの定義は、以下の通りである。<br>
<br>
<br>
以下の例では、paintEventメソッドを使用してQImageクラスのインスタンス(描画用)を生成して、QPainter::drawImageメソッドで描画している。<br>
以下の例では、paintEventメソッドを使用して<code>QImage</code>クラスのインスタンス(描画用)を生成して、<code>QPainter::drawImage</code>メソッドで描画している。<br>
描画用の画像は、ウィジェットのサイズに合わせてリサイズしている。<br>
描画用の画像は、ウィジェットのサイズに合わせてリサイズしている。<br>
<br>
<br>
57行目: 57行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
OpenCVライブラリのcv::Matを使用する場合、OpenCVで画像を処理してその画像を表示するには、以下のように記述する。<br>
OpenCVライブラリの<code>cv::Mat</code>メソッドを使用する場合、OpenCVで画像を処理してその画像を表示するには、以下のように記述する。<br>
カラー画像の場合は、色配列をRGBからBGRに変換する必要がある。<br>
<u>カラー画像の場合は、色配列をRGBからBGRに変換する必要がある。</u><br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  void MyGraphicsView::paintEvent(QPaintEvent *event)
  void MyGraphicsView::paintEvent(QPaintEvent *event)
65行目: 65行目:
     widgetpainter.setWorldTransform(m_matrix);
     widgetpainter.setWorldTransform(m_matrix);
   
   
     // ここでのm_imgはcv::Mat型オブジェクト
     // メンバ変数m_imgはcv::Matクラスのインスタンス
     QImage qimg = QImage(m_img.ptr(), m_img.cols, m_img.rows, m_img.step, QImage::Format_RGB888);
     QImage qimg = QImage(m_img.ptr(), m_img.cols, m_img.rows, m_img.step, QImage::Format_RGB888);
     qimg = qimg.scaled(viewport()->width(), viewport()->height(), Qt::KeepAspectRatio,Qt::FastTransformation);
     qimg = qimg.scaled(viewport()->width(), viewport()->height(), Qt::KeepAspectRatio,Qt::FastTransformation);
91行目: 91行目:
<br><br>
<br><br>


== ズームイン・ズームアウト・移動 ==
== 画像の拡大・縮小・移動 ==
画像を表示する機能には、ズームイン・ズームアウト、移動等の制御が必要になる場合が多い。<br>
画像を表示する機能には、拡大・縮小、移動等の制御が必要になる場合が多い。<br>
これらの機能を実装するには、QTransformクラスを使用する。<br>
これらの機能を実装するには、<code>QTransform</code>クラスを使用する。<br>
<br>
<br>
まず、QTransformクラスのメンバ変数を2つ宣言する。<br>
まず、<code>QTransform</code>クラスのメンバ変数を2つ宣言する。<br>
QTransformクラスのデフォルトコンストラクタを呼び出す場合、単位行列で初期化される。<br>
<code>QTransform</code>クラスのデフォルトコンストラクタを呼び出す場合、単位行列で初期化される。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  QTransform m_matrix;
  QTransform m_matrix;
102行目: 102行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
次に、QGraphicsViewクラスのpaintEventメソッドをオーバーライドする。<br>
次に、<code>QGraphicsView</code>クラスの<code>paintEvent</code>メソッドをオーバーライドする。<br>
以下のように記述することで、QTransformクラスの情報が反映されるようになる。<br>
以下のように記述することで、<code>QTransform</code>クラスの情報が反映されるようになる。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  void MyGraphicsView::paintEvent(QPaintEvent *event)
  void MyGraphicsView::paintEvent(QPaintEvent *event)
116行目: 116行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
ズームイン・ズームアウトの機能は、以下の通りである。<br>
拡大・縮小の機能は、以下の通りである。<br>
scaleViewメソッドは、ズームイン・ズームアウトの限界値を設定して、変換行列に拡大率を設定する。<br>
既にズームが適用されている場合のために、予め、ズームの中心を移動している。<br>
<br>
<br>
ズームインする場合、scaleViewメソッドの第1引数に正の値を指定する。<br>
以下の例では、拡大・縮小の限界値を設定して、変換行列に拡大率を設定する。<br>
ズームアウトする場合、scaleViewメソッドの第1引数に負の値を指定する。<br>
既に拡大・縮小が適用されている場合のために、予め、ズームの中心を移動している。<br>
第2引数には、ズームイン・ズームアウトの中心となる点を指定する。<br>
<br>
第1引数には、拡大する場合は正の値、縮小する場合は負の値を指定する。<br>
第2引数には、拡大・縮小の中心となる点を指定する。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  void MyGraphicsView::scaleView(qreal factor, QPointF center)
  void MyGraphicsView::scaleView(qreal factor, QPointF center)
159行目: 159行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
controlImagePositionメソッドは、ズームアウトする時、余白が表示されないように画像位置を調整する。<br>
controlImagePositionメソッドは、拡大する時、余白が表示されないように画像位置を調整する。<br>
controlImagePositionメソッドの最後で、viewport()->update();を実行して画面を更新する。<br>
controlImagePositionメソッドの最後で、viewport()->update();を実行して画面を更新する。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
166行目: 166行目:
     qreal left, top, right, bottom;
     qreal left, top, right, bottom;
   
   
     //after check top-left, bottom right corner to avoid getting "out" during zoom/panning
     // after check top-left, bottom right corner to avoid getting "out" during zoom/panning
  m_matrix.map(0,0,&left,&top);
    m_matrix.map(0, 0, &left, &top);
   
   
  if(left > 0)
    if(left > 0)
  {
    {
      m_matrix.translate(-left,0);
      m_matrix.translate(-left, 0);
      left = 0;
      left = 0;
  }
    }
   
   
  if(top > 0)
    if(top > 0)
  {
    {
      m_matrix.translate(0,-top);
      m_matrix.translate(0, -top);
      top = 0;
      top = 0;
  }
    }
   
   
  QSize sizeImage = size();
    QSize sizeImage = size();
  m_matrix.map(sizeImage.width(),sizeImage.height(),&right,&bottom);
    m_matrix.map(sizeImage.width(), sizeImage.height(), &right, &bottom);
   
   
  if(right < sizeImage.width())
    if(right < sizeImage.width())
  {
    {
      m_matrix.translate(sizeImage.width()-right,0);
      m_matrix.translate(sizeImage.width() - right, 0);
      right = sizeImage.width();
      right = sizeImage.width();
  }
    }
   
   
  if(bottom < sizeImage.height())
    if(bottom < sizeImage.height())
  {
    {
      m_matrix.translate(0,sizeImage.height()-bottom);
      m_matrix.translate(0, sizeImage.height() - bottom);
      bottom = sizeImage.height();
      bottom = sizeImage.height();
  }
    }
   
   
  m_matrix_inv = m_matrix.inverted();
    m_matrix_inv = m_matrix.inverted();
   
   
     viewport()->update();
     viewport()->update();
203行目: 203行目:
<br>
<br>
以下の例では、移動はマウスの左ボタンを押してドラッグする場合に適用している。<br>
以下の例では、移動はマウスの左ボタンを押してドラッグする場合に適用している。<br>
また、ズームインしている場合にのみ適用している。<br>
また、拡大している場合にのみ適用している。<br>
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
  void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
335行目: 335行目:
     if(left > 0)
     if(left > 0)
     {
     {
       m_matrix.translate(-left,0);
       m_matrix.translate(-left, 0);
       left = 0;
       left = 0;
     }
     }
341行目: 341行目:
     if(top > 0)
     if(top > 0)
     {
     {
       m_matrix.translate(0,-top);
       m_matrix.translate(0, -top);
       top = 0;
       top = 0;
     }
     }
359行目: 359行目:
     if(bottom < limBottom)
     if(bottom < limBottom)
     {
     {
       m_matrix.translate(0, limBottom-bottom);
       m_matrix.translate(0, limBottom - bottom);
       bottom = limBottom;
       bottom = limBottom;
     }
     }

2021年3月11日 (木) 18:12時点における版

概要

Qtでは画像を取り扱うクラスとして、QBitmapクラス、QPixmapクラス、QImageクラスの3種類あり、それぞれ使い分けが必要である。

ここでは、ウィジェット上に画像を表示する手順を記載する。


QGraphicsViewクラスを使用する

画像を表示する機能を持っているウィジェットは無いため、ビュークラスを継承して派生クラスを作成する。
(元々、QGraphicsViewクラスは、QGraphicsSceneクラスを表示するためのものである)

ビュークラスを継承した派生クラスには、元の画像のデータを保持するメンバ変数を定義する。

 #pragma once
 
 #include <QtGui>
 
 class MyGraphicsView : public QGraphicsView
 {
    Q_OBJECT
 
    public:
       MyGraphicsView(QWidget *pWnd);
       ~MyGraphicsView(void);
 
       void setImg(QImage &img);
 
    private:
       void paintEvent(QPaintEvent *event);
       QImage m_img;
 };


QGraphicsViewクラスのpaintEventメソッドは、再描画時に呼び出される仮想関数である。
paintEventメソッドをオーバーライドして描画部分を記述する。

paintEventメソッドとsetImgメソッドの定義は、以下の通りである。

以下の例では、paintEventメソッドを使用してQImageクラスのインスタンス(描画用)を生成して、QPainter::drawImageメソッドで描画している。
描画用の画像は、ウィジェットのサイズに合わせてリサイズしている。

ウィジェットをウインドウやダイアログに貼り付けて、setImgメソッドを使用して画像データを送ることで描画される。

 void MyGraphicsView::paintEvent(QPaintEvent *event)
 {
    QPainter widgetpainter(viewport());
    widgetpainter.setWorldTransform(m_matrix);
 
    QImage qimg = m_img.scaled(viewport()->width(), viewport()->height(), Qt::KeepAspectRatio,Qt::FastTransformation);
    widgetpainter.drawImage(0, 0, qimg);
 }
 
 void MyGraphicsView::setImg(QImage &img)
 {
    m_img = QImage(img);
    viewport()->update();
 }


OpenCVライブラリのcv::Matメソッドを使用する場合、OpenCVで画像を処理してその画像を表示するには、以下のように記述する。
カラー画像の場合は、色配列をRGBからBGRに変換する必要がある。

 void MyGraphicsView::paintEvent(QPaintEvent *event)
 {
    QPainter widgetpainter(viewport());
    widgetpainter.setWorldTransform(m_matrix);
 
    // メンバ変数m_imgはcv::Matクラスのインスタンス
    QImage qimg = QImage(m_img.ptr(), m_img.cols, m_img.rows, m_img.step, QImage::Format_RGB888);
    qimg = qimg.scaled(viewport()->width(), viewport()->height(), Qt::KeepAspectRatio,Qt::FastTransformation);
    widgetpainter.drawImage(0, 0, qimg);
 }
 
 // 読み込み関数
 void MainWindow::onOpen()
 {
    QString strFName = QFileDialog::getOpenFileName(this, "Select image",  "C:\\", "Image File(*.*)");
    if(strFName.size() == 0)
    {
       return;
    }
 
    cv::Mat img = cv::imread(strFName.toStdString(), CV_LOAD_IMAGE_ANYCOLOR | CV_LOAD_IMAGE_ANYDEPTH);
 
    // RGBからBGRに変換する
    cv::cvtColor(img, img, CV_RGB2BGR);
 
    // graphicsViewはMyGraphicsViewクラスのメンバ変数
    graphicsView->setImg(img);
 }



画像の拡大・縮小・移動

画像を表示する機能には、拡大・縮小、移動等の制御が必要になる場合が多い。
これらの機能を実装するには、QTransformクラスを使用する。

まず、QTransformクラスのメンバ変数を2つ宣言する。
QTransformクラスのデフォルトコンストラクタを呼び出す場合、単位行列で初期化される。

 QTransform m_matrix;
 QTransform m_matrix_inv;


次に、QGraphicsViewクラスのpaintEventメソッドをオーバーライドする。
以下のように記述することで、QTransformクラスの情報が反映されるようになる。

 void MyGraphicsView::paintEvent(QPaintEvent *event)
 {
    QPainter widgetpainter(viewport());
    widgetpainter.setWorldTransform(m_matrix);
 
    QImage qimg = QImage(m_img.ptr(), m_img.cols, m_img.rows, m_img.step, QImage::Format_RGB888);
    qimg = qimg.scaled(viewport()->width(), viewport()->height(), Qt::KeepAspectRatio,Qt::FastTransformation);
    widgetpainter.drawImage(0, 0, qimg);
 }


拡大・縮小の機能は、以下の通りである。

以下の例では、拡大・縮小の限界値を設定して、変換行列に拡大率を設定する。
既に拡大・縮小が適用されている場合のために、予め、ズームの中心を移動している。

第1引数には、拡大する場合は正の値、縮小する場合は負の値を指定する。
第2引数には、拡大・縮小の中心となる点を指定する。

 void MyGraphicsView::scaleView(qreal factor, QPointF center)
 {
    factor /= 5;  // -0.1 <-> 0.1
    factor += 1;  // 0.9 <-> 1.1
 
    // limit zoom out ---
    if(m_matrix.m11() == 1 && factor < 1)
    {
       return;
    }
 
    if(m_matrix.m11() * factor < 1)
    {
       factor = 1 / m_matrix.m11();
    }
 
    // limit zoom in ---
    if(m_matrix.m11() > 100 && factor > 1)
    {
       return;
    }
 
    // inverse the transform
    int a = 0,
        b = 0;
 
    m_matrix_inv.map(center.x(), center.y(), &a, &b);
 
    m_matrix.translate(a - factor * a, b - factor * b);
    m_matrix.scale(factor, factor);
 
    controlImagePosition();
 }


controlImagePositionメソッドは、拡大する時、余白が表示されないように画像位置を調整する。
controlImagePositionメソッドの最後で、viewport()->update();を実行して画面を更新する。

 void MyGraphicsView::controlImagePosition()
 {
    qreal left, top, right, bottom;
 
    // after check top-left, bottom right corner to avoid getting "out" during zoom/panning
    m_matrix.map(0, 0, &left, &top);
 
    if(left > 0)
    {
       m_matrix.translate(-left, 0);
       left = 0;
    }
 
    if(top > 0)
    {
       m_matrix.translate(0, -top);
       top = 0;
    }
 
    QSize sizeImage = size();
    m_matrix.map(sizeImage.width(), sizeImage.height(), &right, &bottom);
 
    if(right < sizeImage.width())
    {
       m_matrix.translate(sizeImage.width() - right, 0);
       right = sizeImage.width();
    }
 
    if(bottom < sizeImage.height())
    {
       m_matrix.translate(0, sizeImage.height() - bottom);
       bottom = sizeImage.height();
    }
 
    m_matrix_inv = m_matrix.inverted();
 
    viewport()->update();
 }


以下の例では、移動はマウスの左ボタンを押してドラッグする場合に適用している。
また、拡大している場合にのみ適用している。

 void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
 {
    QPoint pnt = event->pos();
 
    if(m_matrix.m11() > 1 && event->buttons() == Qt::LeftButton)
    {
       QPointF pntf = (pnt - m_pntDownPos) / m_matrix.m11();
       m_pntDownPos = event->pos();
       m_matrix.translate(pntf.x(), pntf.y());
       controlImagePosition();
       viewport()->update();
    }
 
    viewport()->update();
 
    QWidget::mouseMoveEvent(event);
 }
 
 void MyGraphicsView::mousePressEvent(QMouseEvent *event)
 {
    m_pntDownPos = event->pos();
 
    QWidget::mousePressEvent(event);
 }



画像のクリック位置の座標を逆算する

画像上において、クリックする位置の座標を取得して、ピクセル値を取得する操作がある。

画像がウインドウの一辺に合わせて全体が表示されている場合は縦横比のみを考慮すればよいが、
拡大・縮小・移動している場合、座標を取得することは困難である。

Qtでは、拡大・縮小・移動の情報は変換行列が持っているため、それを元に逆算することで簡単に座標を取得できる。

以下の例では、画像をクリックする時に座標を渡してシグナルを発行している。
その後、ウィジェットの親ウィンドウ側でシグナルを受信して、該当ピクセルに対する処理を実行する。

 class MyGraphicsView : public QGraphicsView
 {
    protected:
       void mousePressEvent(QMouseEvent *event)
 
    // 画像の座標を送信するシグナルを定義する
    Q_SIGNALS:
       void mousePressed(QPoint p);
 
       // ...略
 }


 void MyGraphicsView::mousePressEvent(QMouseEvent *event)
 {
    // ウィンドウサイズと画像サイズの比を計算する
    double dScale = (double)viewport()->width() / (double)m_pimg->width;
 
    if(dScale > ((double)viewport()->height() / (double)m_pimg->height))
    {
       dScale = (double)viewport()->height() / (double)m_pimg->height;
    }
 
    // 画像座標を計算してシグナルを発行
    QPointF p = m_matrix_inv.map(event->pos());
 
    emit mousePressed(QPoint(p.x() / dScale, p.y() / dScale));
 }



拡大・縮小時の画像の解像度を上げる

上記のセクション群では、画像を拡大・縮小する場合、画像の解像度はビューポートのサイズのままである。
また、描画および再描画ごとに表示用の画像を作成しているため非効率である。

そこで、拡大・縮小するごとに表示用の画像の解像度を上げるようにする。

 void MyGraphicsView::scaleView( qreal factor, QPointF center )
 {
    factor /= 5;  // -0.1 <-> 0.1
    factor+=1;  // 0.9 <-> 1.1
 
    // limit zoom out ---
    if(m_matrix.m11() == 1 && factor < 1)
    {
       return;
    }
 
    if(m_matrix.m11() * factor < 1)
    {
       factor = 1 / m_matrix.m11();
    }
 
    // limit zoom in ---
    if(m_matrix.m11() > 100 && factor > 1)
    {
       return;
    }
 
    // inverse the transform
    int a = 0,
        b = 0;
 
    m_matrix_inv.map(center.x(), center.y(), &a, &b);
 
    m_matrix.translate(a-factor*a,b-factor*b);
    m_matrix.scale(factor,factor);
 
    // ここで表示用画像を作成、上記コードのOpenCVの場合
    m_qimg = QImage( m_img.ptr(), m_img.cols, m_img.rows, m_img.step, QImage::Format_RGB888 );
    if(viewport()->width() < m_qimg.width())
    {
       m_qimg = m_qimg.scaled(viewport()->width() * m_matrix.m11(), viewport()->height() * m_matrix.m11(),
                              Qt::KeepAspectRatio,Qt::FastTransformation);
    }
 
    controlImagePosition();
 }


 void MyGraphicsView::controlImagePosition()
 {
    qreal left, top, right, bottom;
    qreal limRight, limBottom;
 
    // after check top-left, bottom right corner to avoid getting "out" during zoom/panning
    m_matrix.map(0, 0, &left, &top);
 
    if(left > 0)
    {
       m_matrix.translate(-left, 0);
       left = 0;
    }
 
    if(top > 0)
    {
       m_matrix.translate(0, -top);
       top = 0;
    }
 
    QSize sizeImage = size() * (qreal)m_matrix.m11();
    m_matrix.map(sizeImage.width(), sizeImage.height(), &right, &bottom);
 
    limRight = sizeImage.width() / m_matrix.m11();
    limBottom = sizeImage.height() / m_matrix.m11()
 
    if(right < limRight)
    {
       m_matrix.translate(limRight - right, 0);
       right = limRight;
    }
 
    if(bottom < limBottom)
    {
       m_matrix.translate(0, limBottom - bottom);
       bottom = limBottom;
    }
 
    m_matrix_inv = m_matrix.inverted();
 
    viewport()->update();
 }


 void MyGraphicsView::mousePressEvent(QMouseEvent *event)
 {
    // ウィンドウサイズ・画像サイズの比を計算
    double dScale = (double)viewport()->width() / (double)m_pimg->width;
    if(dScale > ( (double)viewport()->height() / (double)m_pimg->height))
    {
       dScale = (double)viewport()->height() / (double)m_pimg->height;
    }
 
    dScale *= m_matrix.m11()
 
    // 画像座標を計算してシグナルを発行
    pnt = m_matrix_inv.map(event->pos());
    QPoint pntI = pnt / dScale;
 
    emit mousePressed(pntI);
 }