PysimpleGUIを用いたOpenCV画像処理表示

####はじめに
PysimpleGUIを使った画像処理表示のデモを作成したので、覚えとして記載します。

####作成したデモ
（1）OpenCVでカメラから画像を受け取りリアルタイムで、ヒストグラムを表示するプログラム
参考：
[PysimpleGUIを用いたOpenCVのカメラ画像表示](https://qiita.com/Gyutan/items/4990851d43cea810399b)

（2）OpenCVで静止画を読み込み、閾値を設定するプログラム
参考：
[PySimpleGUIで画像処理ビューアーを作る ](https://qiita.com/dario_okazaki/items/30f5418ce2c20988d496)
[PythonでUSBカメラの映像を取り込み、ヒストグラム（RGB）をリアルタイムに表示する方法]([PythonでUSBカメラの映像を取り込み、ヒストグラム（RGB）をリアルタイムに表示する方法 – Tomoblog (tomomai.com)](https://tomomai.com/python-histogram-usb-camera/))
[PythonでGUI画面を作る方法::PySimpleGUIでグリッド表示機能のついたキャプチャ画面を作る]([PythonでGUI画面を作る方法::PySimpleGUIでグリッド表示機能のついたキャプチャ画面を作る – Tomoblog (tomomai.com)](https://tomomai.com/pysimplegui_opencv/))


（3）OpenCVで静止画を読み込み、ROIを設定するプログラム
PysimpleGUIでは、画像表示にはCanvasを使う方法と、sg.Imageを使う方法がありますが、今回はsg.Imageを利用します。sg.Imageでは、png形式のBytesデータ渡さなければなりません。どのプログラムも、画像が読み込まれたから後の処理を行うため、Flagを用意して条件分岐しています。
参考：
[Python/OpenCVのROI抽出!領域の切り出しとコピー](https://watlab-blog.com/2019/05/27/opencv-roi-copy/)


####動作環境
Windows10　64bit
Anaconda
python 3.7
OpenCV 4.2.0 (conda-forge からインストール）


#### (1) OpenCVでカメラから画像を受け取りリアルタイムで、ヒストグラム表示

先ずOpenCVでリアルタイムで画像を受け取るプログラム

```Python
import cv2
import numpy as np
import PySimpleGUI as sg


sg.theme('LightBlue')

layout = [
        [sg.Text('Realtime movie', size=(40, 1), justification='center', font='Helvetica 20',key='-status-')],
        [sg.Text('Camera number: ', size=(8, 1)), sg.InputText(default_text='0',  size=(4, 1),key='-camera_num-')],
        [sg.Image(filename='', key='image')],
        [sg.Button('Start', size=(10, 1), font='Helvetica 14',key ='-start-'),
            sg.Button('Stop', size=(10, 1), font='Helvetica 14',key = '-stop-'),
            sg.Button('Exit', size=(10, 1), font='Helvetica 14', key='-exit-'), ]
        ]


window = sg.Window('Realtime movie',layout, location=(100, 100))


recording = False

while True:
    event, values = window.read(timeout=20)
    if event in (None, '-exit-'):
        break

    elif event == '-start-':
        window['-status-'].update('Live')
        camera_number = int(values['-camera_num-'])
        cap = cv2.VideoCapture(camera_number, cv2.CAP_DSHOW)
        # cap = cv2.VideoCapture(camera_number)
        recording = True

    elif event == '-stop-':
        window['-status-'].update("Stop")
        recording = False
        # 幅、高さ　戻り値Float
        W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        # print(H,W)
        img = np.full((H, W), 0)
        # ndarry to bytes
        imgbytes = cv2.imencode('.png', img)[1].tobytes()
        window['image'].update(data=imgbytes)
        cap.release()
        cv2.destroyAllWindows()

    if recording:
        ret, frame = cap.read()
        if ret is True:
            imgbytes = cv2.imencode('.png', frame)[1].tobytes() 
            window['image'].update(data=imgbytes)
            
window.close()

```

##### 実行例
![op01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/2be186e0-c677-3b1b-2b23-8a10c0e2b475.jpeg)
Startボタンを押すとカメラが作動します。
カメラ番号は各自の環境で変更してください。
![op02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/02983c66-3b85-770c-89c0-091a30e030f0.jpeg)
家の中が汚いので天井を写しています。
Stopで停止して、Exitでプログラムは終了します。
このプログラムに画像から、ヒストグラムを作成してそれを表示するプログラムを追加します。

```Python
import io
import cv2
import numpy as np
from matplotlib import pyplot as plt

import PySimpleGUI as sg


def draw_plot(img_f):

    histgram = cv2.calcHist([img_f], [0], None, [256], [0, 256])
    plt.figure(figsize=(5, 5))
    plt.plot(histgram)
    plt.xlim([0, 256])
    plt.title('Histgram')
    item = io.BytesIO()
    plt.savefig(item, format='png')
    plt.clf()
    
    ### RuntimeWarning: More than 20 figures have been opened. 
    # Figures created through the pyplot interface (`matplotlib.pyplot.figure`) 
    # are retained until explicitly closed and may consume too much memory. 
    # (To control this warning, see the rcParam `figure.max_open_warning`).
    # このエラーが出るため　plt.close('all')を追加
    plt.close('all')
    
    return item.getvalue()


sg.theme('LightBlue')

layout = [
        [sg.Text('Realtime movie', size=(40, 1), justification='center', font='Helvetica 20',key='-status-')],
        [sg.Text('Camera number: ', size=(8, 1)), sg.InputText(default_text='0',  size=(4, 1),key='-camera_num-')],
        [sg.Image(filename='', key='image'), sg.Image(filename='', key='-hist_img-')],
        [sg.Button('Start', size=(10, 1), font='Helvetica 14',key ='-start-'),
            sg.Button('Stop', size=(10, 1), font='Helvetica 14',key = '-stop-'),
            sg.Button('Exit', size=(10, 1), font='Helvetica 14', key='-exit-'), ]
        ]


window = sg.Window('Realtime movie with histgram',layout, location=(100, 100))

recording = False

while True:
    event, values = window.read(timeout=20)
    if event in (None, '-exit-'):
        break

    elif event == '-start-':
        window['-status-'].update('Live')
        camera_number = int(values['-camera_num-'])
        cap = cv2.VideoCapture(camera_number, cv2.CAP_DSHOW)
        # cap = cv2.VideoCapture(camera_number) 
        recording = True

    elif event == '-stop-':
        window['-status-'].update("Stop")
        recording = False
        # 幅、高さ　戻り値Float
        W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        # print(H,W)
        img = np.full((H, W), 0)
        # ndarry to bytes
        imgbytes = cv2.imencode('.png', img)[1].tobytes()
        window['image'].update(data=imgbytes)
        cap.release()
        cv2.destroyAllWindows()

    if recording:
        ret, frame = cap.read()
        if ret is True:
            imgbytes = cv2.imencode('.png', frame)[1].tobytes() 
            window['image'].update(data=imgbytes)
            
            histbytes = draw_plot(frame)
            window['-hist_img-'].update(data=histbytes)
            
window.close()

```

draw_plot関数ではヒストグラムと図を作成して、BytesIO()にPNG形式で書き込みます。
BytesIOに書き込んだら作った図は削除します。
[plt.close() だけではメモリが解放されない場合がある](https://qiita.com/Masahiro_T/items/bdd0482a8efd84cdd270)
これを行うわないと画像の更新がどんどん遅くなっていきます。

#####実行例
![op03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/e4f15799-06cf-85bc-54e9-0172525c4ee9.jpeg)
Live画像はカラーですが、ヒストグラムはグレースケールです。
リアルタイムにヒストグラムが変化します。

#### (2) OpenCVで静止画を読み込み、閾値設定

```python
import datetime
import io
from pathlib import Path

import cv2
import numpy as np
from matplotlib import pyplot as plt
import PySimpleGUI as sg


def draw_plot(img_f):
       
    histgram = cv2.calcHist([img_f], [0], None, [256], [0, 256])
    plt.figure(figsize=(5,2))
    plt.plot(histgram)
    plt.xlim([0, 256])
    
    item = io.BytesIO()
    plt.savefig(item, format='png') 
    plt.clf()
    plt.close('all')

    return item.getvalue()


original_image = [[sg.Image(filename='', key='-orginal_img-')]]
modify_image = [[sg.Image(filename='', key='-modify_img-')]]

#read 
read_layout = [[sg.Text("File"), sg.InputText(key='-input_file-', enable_events=True, ),
                sg.FileBrowse('FileBrowse', key='-file-', target="-input_file-",), sg.Button('Read File', key='-read_file-')],
                [sg.Button('Save',key='-save-'),sg.Cancel()],
                [sg.Slider((0, 255), 0, 1, orientation='h', key='-THRESH SLIDER-'),
                sg.InputText(default_text='0',size=(5,1),key='-thslid-')]]

# hist
hist_graph = [[sg.Image(filename='', key='-hist_img-')]]


layout_tot= [[sg.Frame(title='Read',layout=read_layout),sg.Frame(title='Histgram',layout=hist_graph)],
             [sg.Frame(title='Original',layout=original_image),sg.Frame(title='Results',layout=modify_image),]]


# flag
READ_File = False

sg.theme('Dark Blue 3')

window = sg.Window('Threshold', layout_tot,
                   location=(10, 10),alpha_channel=1.0,
                   no_titlebar=False,grab_anywhere=False).Finalize()

while True:
    
    event, values = window.read(timeout=20)

    if event in (None, 'Cancel'):
        break
    
    elif event == '-read_file-':

        read_path = Path(values['-input_file-'])
        # print(read_path)
        read_img = cv2.imread(str(read_path), cv2.IMREAD_GRAYSCALE)
        imgbytes = cv2.imencode('.png', read_img)[1].tobytes()
        mod_img = read_img.copy()
        histbytes = draw_plot(read_img)
        window['-orginal_img-'].update(data=imgbytes)
        window['-modify_img-'].update(data=imgbytes)
        window['-hist_img-'].update(data=histbytes)
        window['-THRESH SLIDER-'].update(128)
        READ_File = True
        
    if READ_File:    
        if event == '-save-':
            out_path = f'{read_path.stem}_r.png'
            cv2.imwrite(out_path, mod_img)
        
        # elif event == '-THRESH SLIDER-':
        elif values['-THRESH SLIDER-'] :
            _, mod_img  = cv2.threshold(read_img, int(values['-THRESH SLIDER-']), 255, cv2.THRESH_BINARY)
            mod_imgbytes = cv2.imencode('.png', mod_img)[1].tobytes()
            window['-modify_img-'].update(data=mod_imgbytes)
            window['-thslid-'].Update(int(values['-THRESH SLIDER-']))
        
window.close()

```

##### 実行例
![op04.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/cb0e5ed0-9f71-8f85-348c-d77ec2671573.jpeg)

![op05.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/e1050d76-3df7-bc06-f51d-57f3f9225558.jpeg)
File Browseを押してRead Fileを押すと画像が読み込まれてヒストグラムが表示されます。スライダーを動かすと閾値が動き右側画像の白黒が変わります。


#### (3) OpenCVで静止画を読み込み、ROI設定


```python
"""
ROI Set
"""
from pathlib import Path

import cv2
import numpy as np
from matplotlib import pyplot as plt
import PySimpleGUI as sg


def cordinate_one(g1_xy, wh):
    """
    g1_xy: tuple: Coordinate at Left-Top

    """
    g1_lt = g1_xy
    g1_rb = (g1_lt[0]+wh[0], g1_lt[1]+wh[1])

    return ((g1_lt, g1_rb), )


def cordinate_four(g1_xy, g4_xy, wh):
    """
　　4つのROIの座標を計算
    g1_xy: tuple:  the Coordinate at Left-Top of Left-Top ROI
    g4_xy: tuple:  the Coordinate at Left-Top of Right-Bottom ROT 
    wh : tuple
    
    """
    g1_lt = g1_xy
    g1_rb = (g1_lt[0]+wh[0], g1_lt[1]+wh[1])

    g4_lt = g4_xy
    g4_rb = (g4_lt[0]+wh[0], g4_lt[1]+wh[1])

    g2_lt = (g4_xy[0], g1_xy[1])
    g2_rb = (g2_lt[0]+wh[0], g2_lt[1]+wh[1])

    g3_lt = (g1_xy[0], g4_xy[1])
    g3_rb = (g3_lt[0]+wh[0], g3_lt[1]+wh[1])

    g_total = ((g1_lt, g1_rb), (g2_lt, g2_rb), (g3_lt, g3_rb), (g4_lt, g4_rb))
    return g_total


original_image = [[sg.Image(filename='', key='-orginal_img-')]]

read_layout = [[sg.Text("File"), sg.InputText(key='-input_file-', enable_events=True, ),
                sg.FileBrowse('FileBrowse', key='-file-', target="-input_file-"), sg.Button('Read File', key='-read_file-')],
               [],
               [sg.Button('Save',key='-save-'),sg.Cancel()]]

# ROI
# radio button
ROI = [[sg.Text("Size", size=(16, 1),key ='-imgsize-')],
       [sg.Radio('4 ROI Set', "RADIO1", default=False,
                     size=(8, 1), key='-roi4-')],
       [sg.Text("LT x"), sg.InputText(default_text='150', size=(5, 1), key='-roi4_x-'),
        sg.Text("LT y"), sg.InputText(default_text='150', size=(5, 1), key='-roi4_y-')],
       [sg.Text("4th LT x"), sg.InputText(default_text='350', size=(5, 1), key='-roi4_2x-'),
        sg.Text("4th LT y"), sg.InputText(
            default_text='300', size=(5, 1), key='-roi4_2y-'),
        sg.Text("WH"), sg.InputText(default_text='100', size=(5, 1), key='-roi4_wh-')],
        [sg.Radio('1 ROI Set', "RADIO1", default=True,
                     size=(8, 1), key='-roi1-')],
       [sg.Text("LT x"), sg.InputText(default_text='150', size=(5, 1), key='-roi1_x-'),
        sg.Text("LT y"), sg.InputText(
            default_text='150', size=(5, 1), key='-roi1_y-'),
        sg.Text("WH"), sg.InputText(default_text='300', size=(5, 1), key='-roi1_wh-')]]
        

layout_tot= [[sg.Frame(title='Read',layout=read_layout)],
             [sg.Frame(title='Image',layout=original_image),sg.Frame(title='ROI',layout=ROI)]]

sg.theme('Dark Blue 3')
window = sg.Window('ROI', layout_tot,
                   location=(10, 10),alpha_channel=1.0,
                   no_titlebar=False,grab_anywhere=False).Finalize()

read_data = False

while True:
    
    event, values = window.read(timeout=20)

    if event in (None, 'Cancel'):
        break
    
    elif event == '-read_file-':

        read_path = Path(values['-input_file-'])
        read_data = True
        read_img = cv2.imread(str(read_path), cv2.IMREAD_GRAYSCALE)
        imgbytes = cv2.imencode('.png', read_img)[1].tobytes()
        mod_img = read_img.copy()
        window['-orginal_img-'].update(data=imgbytes)
        window['-imgsize-'].update(f'Size HW:{read_img.shape}')

            
    if read_data:    
        if values['-roi4-']:
            try:
                f1_xy = (int(values['-roi4_x-']), int(values['-roi4_y-']))
                f4_xy = (int(values['-roi4_2x-']), int(values['-roi4_2y-']))
                wh = (int(values['-roi4_wh-']), int(values['-roi4_wh-']))
            except:
                #values['-roi4_wh-']が空欄になったときにエラーになるのを避けるため
                f1_xy = (150,150)
                f4_xy =(300,300)
                wh = (50,50)
            cord_lists = cordinate_four(g1_xy=f1_xy, g4_xy=f4_xy, wh=wh)
            
            mod_img = read_img.copy()
            for i ,j in cord_lists:
                rect_img = cv2.rectangle(mod_img,i,j,(255,255,255),1)
                
            rect_img_imgbytes = cv2.imencode('.png', rect_img)[1].tobytes()
            window['-orginal_img-'].update(data=rect_img_imgbytes)
        
        elif values['-roi1-']:
            try:
                f1_xy = (int(values['-roi1_x-']), int(values['-roi1_y-']))
                wh = (int(values['-roi1_wh-']), int(values['-roi1_wh-']))

            except:
                f1_xy = (150, 150)
                wh = (50, 50)
                
            cord_lists = cordinate_one(g1_xy=f1_xy, wh=wh)
            mod_img = read_img.copy()
            for i, j in cord_lists:
                rect_img = cv2.rectangle(mod_img,i,j,(255,255,255),1)    
            rect_img_imgbytes = cv2.imencode('.png', rect_img)[1].tobytes()
            window['-orginal_img-'].update(data=rect_img_imgbytes)
            
        elif event == '-save-':
            out_path = f'{read_path.stem}_r.png'
            cv2.imwrite(out_path, mod_img)
    
window.close()

```

#####実行例
![op06.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/02b6d936-6747-da68-0415-f4d3cf8dc263.jpeg)
![op07.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/02a15761-23c2-fd5d-b6af-5462cb9d3397.jpeg)
ラジオボタンを4 ROI Setに変更
![op08.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/3f2d4d42-23ef-8318-ee5d-4578749dc654.jpeg)


#### まとめ

いくつかの部品（動画リアルタイム表示、画像ファイルの読み込み、ヒストグラム計算、グラフ表示、スライダーによる二値化閾値設定、ROIの座標抽出）の使いかたのデモができました。これらを組み合わせてもう少し手の込んだものを作りたいです。