OpenCVのndarrayとPyQt5のQImage、QPixmapの相互変換

OpenCVではndarrayが画像として扱われて、PyQt5ではQImageやQPixmapを画像として扱われているため、PyQt5でOpenCVを扱う場合はそれらのデータを変換する必要があります。単純な方法としては、QPixmap→ファイル→ndarrayといったように、変換元のデータを一度ファイル出力して、変換先データをファイルから読み込むという方法が考えられます。しかし、この方法では、データを大量に扱う場合にファイルの書き込み待ち時間などが気になってしまいます。そこで、OpenCVとPyQt5のデータをファイル出力せずに相互変換する方法を調べました。

テスト画像

下記の20x20のPNG画像でテストしました。

QImageをOpenCV用のndarrayに変換

テスト画像から作成されたQImageは20x20の4チャンネルで、データの大きさは20204=1600バイトです。qimage.bits().asstring()でメモリ上のデータを取得できるので、必要なデータ数(今回は1600バイト)を指定してQImageからbyte配列を取得します。その後、np.frombuffer()でbyte配列からndarrayを作成して、reshapeを使って縦、横、チャンネルの3次元配列に変換することでOpenCV用のndarrayになります。QImageからndarrayに変換できたか確認するために、cv2.imwrite()で画像を書きだしたらテスト画像と同じ画像が保存されたので正常に変換したことを確認できました。

import cv2
import numpy as np
from PyQt5 import QtGui

def qimage_to_cv(qimage):
    w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
    bytes_ = qimage.bits().asstring(w * h * d // 8)
    arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8))
    return arr

if __name__ == "__main__":
    qimage = QtGui.QImage('test_image.png')
    cv_image_from_qimage = qimage_to_cv(qimage)
    cv2.imwrite('cv_image_from_qimage.png', cv_image_from_qimage)

QPixmapをOpenCV用のndarrayに変換

QPixmapからメモリ上のデータを取得する方法が見つからなかったため、QPixmap.toImage()を使用して、QPixmapをQImageに変換して、そのQImageを上記の方法と同様に変換することでndarrayに変換できます。

ちなみにQApplicationが存在しない状態でQPixmapを作成した場合、

QPixmap: Must construct a QGuiApplication before a QPixmap

というエラーが表示されますのでQApplication()を作成する必要があります。

import cv2
import numpy as np
from PyQt5 import QtGui

def qimage_to_cv(qimage):
    w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
    bytes_ = qimage.bits().asstring(w * h * d // 8)
    arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8))
    return arr

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    qpixmap = QtGui.QPixmap('test_image.png')
    cv_image_from_qpixmap = qimage_to_cv( qpixmap.toImage() )
    cv2.imwrite('cv_image_from_qpixmap.png', cv_image_from_qpixmap)

OpenCV用のndarrayをQImage、QPixmapに変換

OpenCV用のndarrayは色の順番がBGRで、PyQt5はRGBなので、cv2.cvtColor()を使用して色の順番を変更します。その色の順番を変更したndarrayから縦横サイズ、チャンネルを取得して、QImage()でデータと縦横サイズ、1行当たりのバイト数、フォーマットを指定してQImageを作成します。QPixmapはQPixmap.fromImage()でQImageをQPixmapに変換することで得られます。

import cv2
import sys
import numpy as np
from PyQt5 import QtWidgets, QtGui

def cv_to_pixmap(cv_image):
    height, width, bytesPerComponent = cv_image.shape
    bytesPerLine = bytesPerComponent * width
    cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB, cv_image)
    image = QtGui.QImage(cv_image.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)
    return image

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    cv_image = cv2.imread('test_image.png')
    qimage = cv_to_pixmap(cv_image)
    qpixmap = QtGui.QPixmap.fromImage(qimage)
    qimage.save('qimage_from_cv_image.png')
    qpixmap.save('qpixmap_from_cv_image.png')

ソースコード

## -*- coding: utf-8 -*-
import cv2
import sys
import numpy as np
from PyQt5 import QtWidgets, QtGui

def cv_to_pixmap(cv_image):
    height, width, bytesPerComponent = cv_image.shape
    bytesPerLine = bytesPerComponent * width
    cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB, cv_image)

    image = QtGui.QImage(cv_image.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)
    return image

def qimage_to_cv(qimage):
    w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
    bytes_ = qimage.bits().asstring(w * h * d // 8)
    arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8))
    return arr

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    qimage = QtGui.QImage('test_image2.png')
    cv_image_from_qimage = qimage_to_cv(qimage)
    cv2.imwrite('cv_image_from_qimage.png', cv_image_from_qimage)

    qpixmap = QtGui.QPixmap('test_image2.png')
    cv_image_from_qpixmap = qimage_to_cv( qpixmap.toImage() )
    cv2.imwrite('cv_image_from_qpixmap.png', cv_image_from_qpixmap)

    cv_image = cv2.imread('test_image.png')
    qimage = cv_to_pixmap(cv_image)
    qpixmap = QtGui.QPixmap.fromImage(qimage)
    qimage.save('qimage_from_cv_image.png')
    qpixmap.save('qpixmap_from_cv_image.png')

記事の共有

関連記事

コメント

comments powered by Disqus