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

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
27行目: 27行目:
<br><br>
<br><br>


== QImageクラス / QBitmapクラス / QPixmapクラス ==
== QImageクラス / QPixmapクラス / QBitmapクラス ==
QImageクラス、QBitmapクラス、QPixmapクラスの3つのクラスは、Qtで画像を扱う時に使用される重要なクラスである。<br>
QImageクラス、QBitmapクラス、QPixmapクラスの3つのクラスは、Qtで画像を扱う時に使用される重要なクラスである。<br>
それぞれのクラスは異なる用途と特徴を持つ。<br>
それぞれのクラスは異なる用途と特徴を持つ。<br>
60行目: 60行目:
* QImageクラス
* QImageクラス
*: 画像処理や詳細な編集が必要な場合に使用する。
*: 画像処理や詳細な編集が必要な場合に使用する。
* QPixmapクラス
*: GUIでの高速な画像表示が必要な場合に使用する。
* QBitmapクラス
* QBitmapクラス
*: モノクロの単純な画像やマスクが必要な場合に使用する。
*: モノクロの単純な画像やマスクが必要な場合に使用する。
* QPixmapクラス
*: GUIでの高速な画像表示が必要な場合に使用する。
<br>
<br>
また、これらのクラス間では相互変換が可能である。<br>
また、これらのクラス間では相互変換が可能である。<br>

2024年9月17日 (火) 23:13時点における版

概要

Qtにおける画像処理は、主にQImageクラスを中心に行われる。
このクラスは、画像データの読み込み、保存、操作などの基本的な機能を提供している。

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

画像の読み込みと保存は、QImageクラスのloadメソッドとsaveメソッドを使用する。
これにより、JPEG、PNG、GIF等の様々な形式の画像ファイルを扱うことができる。

ピクセル単位の操作も可能であり、個々のピクセルの色情報を取得・変更することができる。
これは、画像の細かい編集や分析に役立つ。

基本的な画像変換機能も充実しており、
例えば、画像のサイズ変更、回転、反転等を簡単に行うことができる。
これらの操作は、QImageクラスのメソッドを使用するだけで実現できる。

より高度な処理が必要な場合、QPainterクラスを使用してQImageクラス上に直接描画することができる。
これにより、画像に図形やテキストを追加、あるいは、複雑な視覚効果を適用したりすることが可能である。

また、QImageReaderクラスを使用することにより、画像の読み込み時にフィルタを適用することもできる。
これは、画像の前処理や特定の効果を適用する場合に便利である。

しかし、非常に複雑な画像処理や高度な機能が必要な場合は、OpenCV等の専門的な外部ライブラリとQtを組み合わせて使用することも一般的である。
これにより、Qtの使いやすいGUI機能とOpenCVの強力な画像処理アルゴリズムを組み合わせることができる。

Qtでの画像処理は、これらの機能を組み合わせることにより、基本的な画像編集から複雑な画像分析まで幅広いタスクに対応できる。


QImageクラス / QPixmapクラス / QBitmapクラス

QImageクラス、QBitmapクラス、QPixmapクラスの3つのクラスは、Qtで画像を扱う時に使用される重要なクラスである。
それぞれのクラスは異なる用途と特徴を持つ。

QImageクラス

QImageクラスは、ピクセルデータの直接操作に適している。
主な特徴として、デバイスに依存しない画像表現を提供しており、個々のピクセルへのアクセスや操作が容易である。

画像処理、フィルタリング、色変換等の操作に最適である。
また、様々な色深度と形式 (1-bit、8-bit、32-bit等) をサポートしている。

QImageクラスは、画像ファイルの読み込み、保存、画像データの詳細な操作が必要な場合に使用する。

QPixmapクラス

QPixmapクラスは、画面表示に最適化された画像表現を提供している。
このクラスは、ハードウェアアクセラレーションを利用できるため、GUIアプリケーションでの画像表示に非常に効率的である。

QPixmapクラスは、デバイスに依存する形式で画像を保持するため、画面への描画が高速である。
ただし、個々のピクセルへのアクセスはQImageクラスほど容易ではない。

QPixmapクラスは、主にボタンのアイコンやウインドウの背景等、UIコンポーネントでの画像表示に使用する。

QBitmapクラス

QBitmapクラスは、QPixmapクラスの特殊なケースであり、1ビット深度 (モノクロ) の画像を表現している。
各ピクセルは、オン / オフの2つの状態のみを持つ。

QBitmapクラスは、主にマスク、カーソル、アイコン等の単純な画像要素の作成に使用する。

メモリ使用量が少なく、単純な形状やパターンの表現に適している。

クラスの使い分け

  • QImageクラス
    画像処理や詳細な編集が必要な場合に使用する。
  • QPixmapクラス
    GUIでの高速な画像表示が必要な場合に使用する。
  • QBitmapクラス
    モノクロの単純な画像やマスクが必要な場合に使用する。


また、これらのクラス間では相互変換が可能である。
例えば、QImageクラスで画像を処理した後、QPixmapクラスに変換して画面に表示するといった使い方ができる。

これらのクラスを使い分けることにより、効率的で柔軟な画像処理と表示が可能になる。


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);
 }