「Qtの基礎 - 画像処理」の版間の差分
245行目: | 245行目: | ||
class MyGraphicsView : public QGraphicsView | class MyGraphicsView : public QGraphicsView | ||
{ | { | ||
protected: | protected: | ||
void mousePressEvent(QMouseEvent *event) | void mousePressEvent(QMouseEvent *event) | ||
// 画像の座標を送信するシグナルを定義する | // 画像の座標を送信するシグナルを定義する | ||
Q_SIGNALS: | |||
void mousePressed(QPoint p); | void mousePressed(QPoint p); | ||
2021年3月11日 (木) 17:23時点における版
概要
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);
}
ズームイン・ズームアウトの機能は、以下の通りである。
scaleViewメソッドは、ズームイン・ズームアウトの限界値を設定して、変換行列に拡大率を設定する。
既にズームが適用されている場合のために、予め、ズームの中心を移動している。
ズームインする場合、scaleViewメソッドの第1引数に正の値を指定する。
ズームアウトする場合、scaleViewメソッドの第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));
}