LoginSignup
15
13

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-15

はじめに

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

作成したデモ

(1)OpenCVでカメラから画像を受け取りリアルタイムで、ヒストグラムを表示するプログラム
参考:
PysimpleGUIを用いたOpenCVのカメラ画像表示

(2)OpenCVで静止画を読み込み、閾値を設定するプログラム
参考:
PySimpleGUIで画像処理ビューアーを作る
PythonでUSBカメラの映像を取り込み、ヒストグラム(RGB)をリアルタイムに表示する方法
PythonでGUI画面を作る方法::PySimpleGUIでグリッド表示機能のついたキャプチャ画面を作る

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

動作環境

Windows10 64bit
Anaconda
python 3.7
OpenCV 4.2.0 (conda-forge からインストール)

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

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

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
Startボタンを押すとカメラが作動します。
カメラ番号は各自の環境で変更してください。
op02.JPG
家の中が汚いので天井を写しています。
Stopで停止して、Exitでプログラムは終了します。
このプログラムに画像から、ヒストグラムを作成してそれを表示するプログラムを追加します。

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() だけではメモリが解放されない場合がある
これを行うわないと画像の更新がどんどん遅くなっていきます。

実行例

op03.JPG
Live画像はカラーですが、ヒストグラムはグレースケールです。
リアルタイムにヒストグラムが変化します。

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

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

op05.JPG
File Browseを押してRead Fileを押すと画像が読み込まれてヒストグラムが表示されます。スライダーを動かすと閾値が動き右側画像の白黒が変わります。

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

"""
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
op07.JPG
ラジオボタンを4 ROI Setに変更
op08.JPG

まとめ

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

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13