Python KivyでOpencvやPillowで画像を表示する方法

PythonのGUIライブラリKivyでOpenCVで読み込んだ画像を表示する方法です。
またPillowでの表示する方法についても説明します

結果画像
![test.jpg](https://qiita-image-store.s3.amazonaws.com/0/11002/1b3a1cab-b74f-c024-e392-53e9ca2d8869.jpeg)




# 補足
OpenCVで画像を表示する方法については@IMUさんのツィートを参考にしてこちらでの検証結果を追加したものになります。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ようやくKivy上でOpenCVで読み込んだ画像開けたぁぁぁ！<br>ただ、エンディアンの違いなのか上下反転してるからKivyに渡すたびに変換が必要そう <a href="https://t.co/IDXVwTfGqe">pic.twitter.com/IDXVwTfGqe</a></p>&mdash; IMU (@imu_prographics) <a href="https://twitter.com/imu_prographics/status/915077073072357376?ref_src=twsrc%5Etfw">2017年10月3日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

今回使用した画像ですが[ぱたくそ](https://www.pakutaso.com/)から借りました。

[ハーブから吸蜜する働き蜂](https://www.pakutaso.com/20171037278post-13573.html)

# サンプルコードについて

今回の内容をGithubにあげました。

Github:[https://github.com/okajun35/kivy_show_opencv_pillow](https://github.com/okajun35/kivy_show_opencv_pillow)

# OpenCVで読み込んだ画像を表示する

大きく分けて次の2つの方法があると思います。

+ 一時的に画像ファイルに書き出したして`kivy.uix.image()` で表示する
+ メモリ上に読み込んだ画像` をblit_buffer()` を使用して表示する

## 一時的に画像ファイルに書き出したして kivy.uix.image() で表示する
この方法はさほど難しくなくOpenCVで読み込んだ画像を`cv2.imwrite()`を使用して保存します。
その後その保存した画像を`kivy.uix.image()`を使用して表示します。

KivyのAppクラスには`user_data_dir`でconfig.iniで設定されたデータを保存するパスを格納しています。
`user_data_dir`を使用して画像を保存する場所を指定して保存します。
あとはImageを使用して保存された画像を表示します。
保存された画像に関してはAppクラスには`on_end()`といって終了時に呼ぶメソッドがあるので、
`on_end()`内で保存した画像を削除します。

参考

+ [APIリファレンス>Application （翻訳中）](https://pyky.github.io/kivy-doc-ja/api-kivy.app.html)
+ [Python Kivyの使い方①＞16．画像の表示](https://qiita.com/dario_okazaki/items/7892b24fcfa787faface#16%E7%94%BB%E5%83%8F%E3%81%AE%E8%A1%A8%E7%A4%BA)


## メモリ上に読み込んだ画像をblit_buffer()を使用して表示する

一時的に画像ファイルに書き出さずメモリ上のやり取りで済ませたい場合は、`blit_buffer()`を使用することでTextureオブジェクトにバイト列の情報を吐き出すことが可能です。
`blit_buffer()`を使用すればOpenCVで読み込んだ画像を表示することが可能ですが次の3点に注意することが必要です。


+ OpenCVの画像の幅と高さは画像オブジェクトのshape[]に入っているが、**shapeに格納されている情報は「高さ」、「横幅」、「チャンネル」の順で、Kivyのsize()は「横幅」、「高さ」**なので順番が違う。
+ OpenCVの画像の色の並びは**BGR**でKivyの`blit_buffer`での色の並びはデフォルトでは**RGB**なので並びが違う。
+ OpenCVの原点座標は**左上**だが、Kivyの原点座標は**左下**なのでそのままだと画像が上下反転される。

この3点の対応ですがOpenCVで対応するのとKivyで対応する2パターンがあります。

### OpenCVで対応する方法

コードは以下の通りです。


```python

import numpy as np
import cv2

from PIL import Image

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics.texture import Texture
from kivy.graphics import Rectangle


class MyApp(App):
        title = "opencv on kivy"

        def build(self):
            img = cv2.imread('kai_058Kazukiya17103_TP_V.jpg',1)

            if img is None:
                print('load image')
                sys.exit(1)
        
            # 表示確認用
            cv2.imshow('opencv_normal', img)


            widget = Widget()

            ''' pattern1 (openCVの機能で表示)  '''
            img2 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # openCVの色の並びはBGRなのでRGBに直す
            img2 = cv2.flip(img2, 0)    # Kivyの座標の原点は左下なので上下反転する
            
            # OpenCVの座標shapeは「高さ」、「幅」、「チャンネル」の順番 Kivyのsizeは(幅、高さ)なので逆にする必要がある
            texture = Texture.create(size=(img2.shape[1], img2.shape[0]))
            texture.blit_buffer(img2.tostring())

            with widget.canvas: # 描画
                Rectangle(texture=texture ,pos=(0, 0), size=(img2.shape[1], img2.shape[0]))

```

まず、読み込んだ画像を`cvtColor()`の「COLOR_BGR2RGB」で色の並びをBGRからRGBに変更します。
次に`flip(<読み込んだ画像>, 0)` で画像を上下反転します。
次にKivyでテクスチャーで表示しますが、`Texture.create()`でテクスチャの設定をします。
その際、`size=(img2.shape[1], img2.shape[0])`で画像の横幅と高さを指定します。
あとは`blit_buffer()`で画像情報をバイト列で吐き出します。

後はcanvasで、画像と同じ大きさの矩形を描画してそのテクスチャーに`blit_buffer()`を行ったテクスチャーを指定することで表示できるようになります。


### Kivyで対応する方法

OpenCVで表示の下準備をすることもできますが、Kivyで表示するのであればKivyで表示の準備をした方がよいかと思います。kivyで表示する場合は以下の方法になります。

```python
''' pattern2 (kivyの機能のみで表示)  '''

texture = Texture.create(size=(img.shape[1], img.shape[0]), colorfmt='bgr', bufferfmt='ubyte') # BGRモードで用意,ubyteはデフォルト引数なので指定なくてもよい
texture.blit_buffer(img.tostring(),colorfmt='bgr', bufferfmt='ubyte')  # ★ここもここもBGRで指定しないとRGBになって色の表示がおかしくなる
texture.flip_vertical()    # 画像を上下反転する

with widget.canvas:
    Rectangle(texture=texture ,pos=(0, 0), size=(img.shape[1], img.shape[0]))

```

OpenCVの場合と違うのはテクスチャーを作成する`create()`で指定する際に`colorfmt='bgr'`とすることで画像の並びをBGRに対応するようにします。※colorfmtのデフォルト値は'rgb'です。
また`blit_buffer()`実行時にも`colorfmt='bgr'`を指定します。こうすることで画像の色の並びがBGRの画像でも表示できます。
さらに`flip_vertical()`で画像を上下反転させます。
以上でOpenCVで処理していたのと同じように画像を表示させることができます。


### 参考　グレースケール画像を表示させる場合
![result.png](https://qiita-image-store.s3.amazonaws.com/0/11002/ecc2475e-484e-a863-5acc-ca57b2844e5a.png)


OpenCVで処理したグレースケール画像を表示させる場合ですが、Kivyではカラーチャンネルが1個の画像は表示できません。表示させる場合はOpenCV側で疑似的に3チャンネルに変換して表示させます。

```python

# 画像をグレイスケールに変換
gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
gray_to_rgb_img = cv2.cvtColor(gray_img, cv2.COLOR_GRAY2RGB)    # bilt_bufferでは1チェンネルのグレイ画像は表示できないので３チャンネルの画像に変換する

```
# Pillowで読み込んだ画像を表示する

![test2.jpg](https://qiita-image-store.s3.amazonaws.com/0/11002/67cdefcc-4669-947b-264b-75c5ae9a1ebf.jpeg)

Pillowでの表示はOpenCVほど難しくはないです。
以下が該当部分のコードです。

```python
pillow_img = Image.open('kai_058Kazukiya17103_TP_V.jpg', 'r')
texture = Texture.create(size=pillow_img.size) 

texture.blit_buffer(pillow_img.tobytes())

texture.flip_vertical()    # 
with widget.canvas:
    Rectangle(texture=texture ,pos=(0, 0), size=pillow_img.size)
```

OpenCVと違いPillowの場合は画像の色の並びがRGBであることと画像の「横幅」、「高さ」の並びもKivyと一緒なので変換はいりません。ただし座標の原点座標がPillowは左上に対してKivyは左下が原点なので`flip_vertical()`を使用して画像を上下反転させる必要があります。