Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

Dear PyGuiとMediaPipeを用いたGUIアプリを作成

先日、MediaPipeを用いてじゃんけんゲームのアプリを作成しました。
elsammit-beginnerblg.hatenablog.com

今回はこちらのMediaPipeとDear PyGUIを連携させてGUIアプリを作成しましたのでご紹介します。




ソースコード

コードに関してはgithubに掲載しておりますのでご参照ください。
github.com

■使い方

こちらを利用する場合には、Dear PyGUIとMediaPipe、OpenCVが必要になります。
それぞれ、
[Dear PyGUI]

Dear PyGuiを用いてPython GUIを作成してみる - Elsaの技術日記(徒然なるままに)

[MediaPipe]

pip install mediapipe

[OpenCV]

pip install opencv-python
pip install opencv-contrib-python

にてインストールが可能です。

準備が完了した後は、githubよりコードをダウンロードし、下記コマンドを実行。

python JankenMain.py

アプリを落とす場合には、
DearPyGUIで表示させているGUIアプリの閉じるボタンを押せばOKです。
(注)ポンの音声が出力されるまではアプリが落ちませんのでご注意ください。

■アプリを動作させてみる

アプリを動作させた結果はこちらに掲載してみました。
よろしかったら是非ご覧ください。
youtu.be

■概要

こちらのコードをざっくりご紹介しますと、
作成していたMediaPipeを用いたじゃんけんゲームをthread化し、
生成された手+CPUの手の結合画像をDear PyGUIに表示しているのみです。
※生成された手+CPUの手の結合画像とはこちらのことです。
f:id:Elsammit:20220123113829p:plain

先日、Dear PyGUIで動画再生アプリを作成していましたのでそちらを流用することで比較的簡単に作成することが出来ました。
elsammit-beginnerblg.hatenablog.com

GUI表示部と画像処理threadとのやり取りはコールバック関数を用いております。
pythonは関数にコールバック関数を引数に与え、使いたい箇所で実行すればよいので楽ですね!!

どこぞのプログラミング言語だと関数ポインタ駆使しないといけないので、
コールバック関数1つ定義するにも一苦労なんですよね。。

話しがそれてしまいました。
Pythonのコールバック関数の使い方はこちらに掲載されておりますので気になった方はご覧ください。
qiita.com

ただ、、、
thread化したことによりフレーム画像の同期がとれていないためか画像上に判定結果を載せようとするとチカチカして見えにくくなる。。。

ということで、
判定結果は別テキストボックスに出力するようにいたしました。
Dear PyGUIで文字サイズを変更するのに苦労しました。。
Dear PyGUIで文字サイズ変更方法に関してはまた別のブログにてまとめたいと思います。

■最後に

今回は自身が作成したアプリのご紹介でした。
細かい部分について割愛してしまったので、あとで躓いた点はまとめておきたいと思います。

それにしてもMediaPipeはすごいですね。
まだまだ面白い使い方ありそうなので色々試してみたいと思います。



MediaPipeでじゃんけんゲームを作成

先日MediaPipeで手の形状を検出してみました。
elsammit-beginnerblg.hatenablog.com

最後に手の形状を検出してじゃんけんの判定を行ってみました。

これを見てふと、、、
せっかくなのでCPUと対戦出来るじゃんけんゲーム作ったら面白いかな??
と思い立ち、作成することに。
最初はとりあえず、OpenCVのimshowでの表示を用いてじゃんけんゲームを作成してみました。




■できたアプリ

作成できたアプリはこちら!!
音声も自分で用意したものを流しております。
youtu.be

■コードはgithubにて公開中

コードはこちらに格納しております。
よろしければご覧ください。
https://github.com/elsammit/rockpaperscissors-usingmediapipe

こちらのコードですが、
git cloneで取ってきたソースコードにて、

python JankenMain.py

を実行すればOKです。
なお、MediaPipeライブラリのインストールが必要になります。
インストール手順は前回のブログにまとめておりますのでこちらを参考に。
elsammit-beginnerblg.hatenablog.com

■コードに関して

こちらのコードですが、クラスとして下記定義しております。
・handsクラス:MediaPipeライブラリを用いて検出したじゃんけんの手
・Pertnerクラス:CPUの出す手
・Judgeクラス:CPUの手とユーザの手の結果を元に勝ち負け判定
・Onseiクラス:「じゃんけん。ぽん。」音声出力

handsクラスで実施している内容は前回のブログで記載済みになりますので詳細は割愛します。
また、Onseiクラスに関してもメソッドコールで音声出力しているのみになります。
このため、今回はPartnerクラス内で実施している内容について以下でまとめていきたいと思います。

■CPUが出す手の処理

CPU側の処理は下記コードの通りになります。

import cv2
import threading
import JankenOnsei 
import random
import time
import Define

m_Define = Define.Define()
m_Onsei = JankenOnsei.Onsei()

class Pertner:
    looretCnt = 1000
    filePath = m_Define.HANDSHAPE_ROCK_IMG
    result_img = cv2.imread(m_Define.HANDSHAPE_ROCK_IMG)
    StartFlg = False
    JankenResult = False
    rsp = ""
    FinishFlg = False
    testFlg = False

    def StartOnseiThread(self):
        thread1 = threading.Thread(target=m_Onsei.jankenOnsei)
        thread1.start()

    def GetHandsMat(self, cnt):
        if cnt % 10 <= 2:
            self.filePath = m_Define.HANDSHAPE_ROCK_IMG
            self.rsp = m_Define.HANDSHAPE_ROCK
        elif cnt % 10 <= 5:
            self.filePath = m_Define.HANDSHAPE_SCISSORS_IMG
            self.rsp = m_Define.HANDSHAPE_SCISSOR
        elif cnt % 10 <= 9:
            self.filePath = m_Define.HANDSHAPE_PAPPERS_IMG
            self.rsp = m_Define.HANDSHAPE_PAPPER

    def PartnerHands(self):
        if m_Onsei.onseiStatus == 0:
            self.GetHandsMat(self.looretCnt)
            if self.looretCnt > 1000:
                self.looretCnt = 0
        elif m_Onsei.onseiStatus == 1:
            x = random.randint(0,5)
            self.GetHandsMat(x)
            self.JankenResult = True
            m_Onsei.onseiStatus += 1
        elif m_Onsei.onseiStatus == 2:
            self.JankenResult = False
        elif m_Onsei.onseiStatus == 3:
            m_Onsei.onseiStatus = 0
            self.StartOnseiThread()
            
            self.JankenResult = False
            self.looretCnt = 0
            time.sleep(0.1)

        resultImage = cv2.imread(self.filePath)

        return resultImage
    
    def PartnerLoop(self):
        m_Onsei.onseiStatus = 3
        while True:
            self.looretCnt += 1
            self.result_img = self.PartnerHands()
            self.StartFlg = True

            time.sleep(0.01)

            if self.FinishFlg == True:
                break

CPU側の処理としては、大きく分けると
 ・じゃんけんの掛け声の間は数百ミリ秒単位でグー、チョキ、パーをローリング
 ・ぽんの掛け声でランダムに処理を切り替える
の2つになります。

こちらの2つの処理の切り替えですが、
音声と同期をとるために、m_Onsei.onseiStatusにて管理しております。
m_Onsei.onseiStatusですが、
m_Onsei.onseiStatus = 0 :じゃんけんの掛け声中。
m_Onsei.onseiStatus = 1 :ぽんを言う直前。CPUの手を決定し表示。
m_Onsei.onseiStatus = 2 :ぽんを言った後。CPUとの勝敗を表示している間の待ち。
m_Onsei.onseiStatus = 3 :次のじゃんけんを行うための初期化処理。

音声ですが、本クラス内でOnseiインスタンスを生成させ、別スレッドで音声出力を実行しております。

じゃんけんの掛け声中のグー、チョキ、パーのルーレットですが、
looretCntの数値により切り替えております。
(こちらは別の方法の方がいいかも?)

こちらの切り替えを実施しているのが、
PartnerHands関数になります。
そして、PartnerHands関数を定期的にコールしているのが、
PartnerLoop関数。
PartnerLoop関数内では10ms間隔でループを実行。

PartnerLoop関数ですが、
このじゃんけんゲームのmain関数にあたる、
JankenMain.py内でmainとは別スレッドでコールしております。

デバッグ用ですがCPU側のみを出力させることも出来るようになっております。
こちらのコードを実行するとCPU側だけの処理が動くようになります。

python PartnerHands.py

実際に実行するとこちらのようになります。
※実際には音声も出力されています。
f:id:Elsammit:20220116105735g:plain

■最後に

とりあえずのレベルですがじゃんけんゲーム動くレベルまで持っていくことが出来ました!!
ちょっと気になる点もあるので、時間を見て修正していきたいと思います!!
また最新版が出来たらご連絡したいと思います。



MediaPipeで手の動作をリアルタイムで検出する

たまたまになるのですが、MediaPipeという手や顔の動きをリアルタイムで検出するライブラリを発見。
面白そうだったので触ってみることにしました。




■MediaPipeとは?

MediaPipeは、Googleが提供しているライブメディアやストリーミングメディア向けのMLソリューションであり、
CPUのみで検出が行えるツールになります。

MediaPipeの特徴として、
 ・エンドツーエンドの高速化:一般的なハードウェアでも高速化された組み込みの高速ML推論と処理
 ・一度構築してどこにでもデプロイ:統合ソリューションはAndroidiOS、デスクトップ/クラウド、ウェブ、IoTで機能します
 ・すぐに使用できるソリューション:フレームワークのフルパワーを実証する最先端のMLソリューション
 ・フリーでオープンソースApache 2.0でのフレームワークとソリューション、完全に拡張可能でカスタマイズ可能
があります。

本ライブラリのソリューションとして、
 ・顔検知
 ・フェイスメッシュ
 ・手検知
 ・ポーズ
 ・物体検出
 ・インスタントモーショントラッキング
などなど、、、
12種類用意されております。
また、MediaPipeは様々なプログラミング言語に対応しております。

対応しているプログラミング言語やソリューションの詳細は下記をご確認ください。
https://google.github.io/mediapipe/

■MediaPipeインストール

今回はpythonを用いて手の動作検知を行っていきます。
そこで、pythonでMediaPipeが利用できるようにMediaPipeをインストールしていきます。
といってもpythonであれば、

pip install mediapipe

or

pip3 install mediapipe

でOKです。
pythonはライブラリインストールが簡単でありがたいです。

自分はインストール済みでしたが、OpenCVが必要になります。
インストールがまだの方は下記を実行してOpenCVをインストールください。

pip install opencv-python
pip install opencv-contrib-python

or 

pip3 install opencv-python
pip3 install opencv-contrib-python

■MediaPipeを用いて手の動作を検知

では早速、てのモーションをリアルタイムで検知していきたいと思います!!
コードは公式が掲載しているコードを少し改変したものを載せます。
https://google.github.io/mediapipe/solutions/hands.html

コードはこちら。

import cv2
import mediapipe as mp

class hands:
    mp_drawing = mp.solutions.drawing_utils
    mp_drawing_styles = mp.solutions.drawing_styles
    mp_hands = mp.solutions.hands
    after_img = ""
    StartFlg = False
    rsp = ""
    FinishFlg = False

    def HandsLoop(self):
        cap = cv2.VideoCapture(0)
        with self.mp_hands.Hands(
            min_detection_confidence=0.5,
            min_tracking_confidence=0.5) as hands:

            while cap.isOpened():
                success, image = cap.read()
                if not success:
                    print("Ignoring empty camera frame.")
                    continue

                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
                before_image = image.copy()

                results = hands.process(image)
                
                if results.multi_hand_landmarks:
                    for hand_landmarks in results.multi_hand_landmarks:
                        self.mp_drawing.draw_landmarks(
                            image,
                            hand_landmarks,
                            self.mp_hands.HAND_CONNECTIONS,
                            self.mp_drawing_styles.get_default_hand_landmarks_style(),
                            self.mp_drawing_styles.get_default_hand_connections_style())

                self.after_img = cv2.flip(image - before_image, 1)

                if __name__ == '__main__':
                    cv2.imshow('MediaPipe Hands', self.after_img)
                    if cv2.waitKey(5) & 0xFF == 27:
                        break

        cap.release()

if __name__ == '__main__':
    m_hands = hands()
    m_hands.HandsLoop()

こちらのコードで手のリアルタイム検知結果がimshowで表示させることが出来ます。
パラメータに関してはとりあえずデフォルト設定で利用しておりますが、
 ・min_detection_confidence:検出が成功したと判断するための最小信頼値(0.0 ~ 1.0の範囲)
 ・min_tracking_confidence:手のランドマークが正常に追跡されたと見なされるためのランドマーク追跡モデルからの最小信頼値(0.0 ~ 1.0の範囲)
を意味しております。


処理フローですが、
まずOpenCVのVideoCaptureによりカメラからのフレーム画像を取得します。
その後、min_detection_confidence、min_tracking_confidenceをそれぞれ0.5のパラメータで
hands.processにてフレーム画像の解析を行い、
self.mp_drawing.draw_landmarksにて解析結果を画像上に描画しているようです。
最後に、imshowにて画像を表示。

この際、手の検知結果のみを表示させるために、
image - before_image
により検出結果の画像から元画像を差し引いています。

こちらのコードを実行するとこちらのような映像が表示されます。
f:id:Elsammit:20220113224901g:plain

■グー、チョキ、パーを認識してみる

手を検知するだけだとサンプルのまんまですので、、、
簡単にグー、チョキ、パーかを判別してみたいと思います。
コードはこちら。

import cv2
import mediapipe as mp
from PIL import ImageFont, ImageDraw, Image
import numpy as np
import time

FONT = ImageFont.truetype('C:\Windows\Fonts\meiryo.ttc' , 32)
class hands:
    mp_drawing = mp.solutions.drawing_utils
    mp_drawing_styles = mp.solutions.drawing_styles
    mp_hands = mp.solutions.hands
    after_img = ""
    StartFlg = False
    rsp = ""
    FinishFlg = False

    def HandShape(self, hand_landmarks, image):
        image_width, image_height = image.shape[1], image.shape[0]
        index_finger = [
            int(hand_landmarks.landmark[8].y * image_height),
            int(hand_landmarks.landmark[6].y * image_height)
        ]

        index_middle = [
            int(hand_landmarks.landmark[12].y * image_height),
            int(hand_landmarks.landmark[10].y * image_height)
        ]

        index_ring = [
            int(hand_landmarks.landmark[16].y * image_height),
            int(hand_landmarks.landmark[14].y * image_height)
        ]

        index_pinky = [
            int(hand_landmarks.landmark[20].y * image_height),
            int(hand_landmarks.landmark[18].y * image_height) 
        ]        

        fingersList = [index_finger, index_middle, index_ring, index_pinky]

        return fingersList

    def JudgeRPS(self, fingersList):
        hand_ret = ""
        if fingersList[0][0] > fingersList[0][1] and fingersList[1][0] > fingersList[1][1] and \
            fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]:
            hand_ret = "グー"
        elif fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]:
            hand_ret = "チョキ"
        else:
            hand_ret = "パー"

        return hand_ret

    def HandsLoop(self):
        cap = cv2.VideoCapture(0)
        with self.mp_hands.Hands(
            min_detection_confidence=0.5,
            min_tracking_confidence=0.5) as hands:

            while cap.isOpened():
                success, image = cap.read()
                if not success:
                    print("Ignoring empty camera frame.")
                    continue

                image.flags.writeable = True
                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
                before_image = image.copy()

                results = hands.process(image)
                
                if results.multi_hand_landmarks:
                    for hand_landmarks in results.multi_hand_landmarks:
                        self.mp_drawing.draw_landmarks(
                            image,
                            hand_landmarks,
                            self.mp_hands.HAND_CONNECTIONS,
                            self.mp_drawing_styles.get_default_hand_landmarks_style(),
                            self.mp_drawing_styles.get_default_hand_connections_style())
                        fingerList = self.HandShape(hand_landmarks, image)
                        self.rsp = self.JudgeRPS(fingerList)
                        
                self.after_img = cv2.flip(image - before_image, 1)
                img_pil = Image.fromarray(self.after_img)
                draw = ImageDraw.Draw(img_pil)
                draw.text((10, 350), self.rsp, font = FONT, fill = (255,0,255))
                self.after_img = np.array(img_pil)

                cv2.imshow('MediaPipe Hands', self.after_img)
                 if cv2.waitKey(5) & 0xFF == 27:
                    break

        cap.release()

if __name__ == '__main__':
    m_hands = hands()
    m_hands.HandsLoop()

先ほど解析しませんでしたが、
hand_landmarks
に検知した手の節?の座標が格納されております。
hand_landmarksは{x,y}構造体の配列になっておりそれぞれ下記画像の通り割り当てられております。
f:id:Elsammit:20220111221902p:plain

こちらの座標情報を元に、
手の形状解析を行っております。
解析のコードはこちら。

    def HandShape(self, hand_landmarks, image):
        image_width, image_height = image.shape[1], image.shape[0]
        index_finger = [
            int(hand_landmarks.landmark[8].y * image_height),
            int(hand_landmarks.landmark[6].y * image_height)
        ]

        index_middle = [
            int(hand_landmarks.landmark[12].y * image_height),
            int(hand_landmarks.landmark[10].y * image_height)
        ]

        index_ring = [
            int(hand_landmarks.landmark[16].y * image_height),
            int(hand_landmarks.landmark[14].y * image_height)
        ]

        index_pinky = [
            int(hand_landmarks.landmark[20].y * image_height),
            int(hand_landmarks.landmark[18].y * image_height) 
        ]        

        fingersList = [index_finger, index_middle, index_ring, index_pinky]

        return fingersList

解析の結果から、グー、チョキ、パーの解析を行います。
コードはこちら。
今回は簡単のために、y座標のみをチェックかつ先端(TIP)がDIPの位置以下になった場合に指が曲がったと判断し、
その曲がった指をチェックすることでグー、チョキ、パーを解析しています。

    def JudgeRPS(self, fingersList):
        hand_ret = ""
        if fingersList[0][0] > fingersList[0][1] and fingersList[1][0] > fingersList[1][1] and \
            fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]:
            hand_ret = "グー"
        elif fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]:
            hand_ret = "チョキ"
        else:
            hand_ret = "パー"

        return hand_ret

後は解析した結果を画像に重畳させてimshowで表示。

self.after_img = cv2.flip(image - before_image, 1)
img_pil = Image.fromarray(self.after_img)
draw = ImageDraw.Draw(img_pil)
draw.text((10, 350), self.rsp, font = FONT, fill = (255,0,255))
self.after_img = np.array(img_pil)
cv2.imshow('MediaPipe Hands', self.after_img)

こちらのコードを実行した結果は下記に掲載しました。
よろしければご覧ください。
youtu.be

■最後

MediaPipeですが、結構検出精度が高く使っていてとても楽しかったです。
もう少し使ってみたいと思います。
また、何か出来たら掲載していきたいと思います。



Linuxでのファイルzip化時のフォルダ構成についてまとめてみる

Linuxにてzip化を行う場合にはこちらのコマンドを実行すれば可能なのです。

zip dist.zip srcfile 

もしディレクトリごとzip化したい場合には、

zip -r dist.zip srcdir

とすればOKです。

■zipコマンド実行後の挙動
しかしながら、、、1つ困ったことがあります。
それは、指定したパスの通りにzip暗号化してしまう点です!!
例えば、

zip dist.zip /home/hoge/huga/srcdir

とした場合、dist.zipが生成されるのですが、
このdist.zipを展開すると、
/home/hoge/huga/srcdir
のフォルダ構成でsrcdirが格納されます。
圧縮したい対象がsrcdir配下に格納したファイルのみであった場合、
ここまで階層深い状態でファイルが格納されていると結構面倒。。。

ということで階層をコントロールして、自分が欲しいフォルダ構成の状態でzip化する方法についてまとめていきたいと思います。

■ファイルのみをzip化したい場合
もしファイルのみをzip化したい場合には、--junk-paths(-j)オプションを付与すればOKです。
例えば、srcdir配下に、
 ・srcfile1
 ・srcfile2
 ・srcfile3
が格納された構成に対して--junk-paths(-j)オプションを付与して、

zip -r -j dist.zip /home/hoge/huga/srcdir

を実行するとdist.zipが格納されます。
このdist.zipを解凍すると、
 ・srcfile1
 ・srcfile2
 ・srcfile3
が展開されることが確認できるかと思います。
階層が深くならなずに必要なファイルのみ暗号化出来るので便利ですね。

■フォルダパスを指定して圧縮する方法
先ほどの--junk-paths(-j)オプションを付与した方法であればファイルのみを圧縮できるのですが、、、
1つ難点があります。
それは、同じファイル名が存在する場合には圧縮出来ない!!ということです。
まぁ、ファイル名が被ってしまうので当たり前と言えばそうなのですが。

そこで、今度はフォルダパスを指定して圧縮する方法をまとめていきます。

そもそも、zip圧縮にて階層が深くなってしまう理由はzipコマンドを実行する際に
/home/hoge/huga/srcdir
のように深い階層で指定しまうことになります。

そこで、自分の好きなパスまで移動してからzipコマンドを使用すればOK!!
例えば、srcdirをzip圧縮したい場合には

cd /home/hoge/huga/
zip -r dist.zip srcdir

とすればOK。

さらに、別のフォルダも一緒に含めてzip圧縮をしたい場合には、

    • update(-u)オプションを駆使すればOK。

例えば、/home/hoge/huga/srcdir以外に、/home/hoge/huga/srcdir2も圧縮させたい場合には、

cd /home/hoge/huga/
zip -r -u dist.zip srcdir

cd /home/hoge/huga/
zip -r -u dist.zip srcdir2

とすればOK。
圧縮したdist.zipを解凍すると、
rcdirフォルダ
srcdir2フォルダ
が格納されているのが確認できるかと思います。

■最後に
基礎的な部分ですが以外に知らない内容だったので勉強になってよかったです。
また不明点などがあったら調べてまとめていきたいと思います。

■参考
https://atmarkit.itmedia.co.jp/ait/articles/1608/01/news039.html
https://uxmilk.jp/50165

Linuxでサマータイムを無効にしつつタイムゾーンを操作する方法方法

今回はLinuxサマータイムを疑似的に?無効化する方法についてまとめたいと思います。

タイムゾーンサマータイムありの地域を選択しているが、
サマータイムはオフにして運用したい場合に使えるかな?と思い調べてみました!!
Linuxだとタイムゾーンごとにサマータイム有効・無効が決まっているので、どうしても無効化したいことがあった場合タイムゾーンを変えるしかないのか??
と思ったのが調査のきっかけです。



Linuxでのタイムゾーン設定

サマータイム有効・無効を切り替える前にタイムゾーンを切り替える方法についてまとめます。
基本的にLinuxではタイムゾーン毎にサマータイム有効にする地域、無効にする地域が分かれているので、
タイムゾーンを切り替えることで自動的に有効・無効が切り替わる仕組みになります。

linuxタイムゾーンを切り替える手順はこちらになります。
タイムゾーンとしてTokyoをセットするケースを例として取り上げます。

sudo rm -f /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

/etc/localtimeとしてzoneinfo内のファイルのシンボリックを作成すればOKになります。
ですので、UTCにする場合には、
/usr/share/zoneinfo/UTCを/etc/localtimeとしてシンボリックリンクを作成すればOKです。
UTCの場合】

sudo rm -f /etc/localtime
sudo ln -s /usr/share/zoneinfo/UTC /etc/localtime

サマータイム無効化方法

先ほどの方法でタイムゾーンの設定が行えるのですが、、、
タイムゾーンですとタイムゾーンによってサマータイムが有効・無効が自動的に決まってしまうため、
有効となるタイムゾーンで無効に出来ていない。。

そこで、サマータイム有効・無効を探したのですが、、
そんなものは用意されていない!!
基本的にタイムゾーンで管理しなさい!!というのがスタンスのよう。

しかし!!
タイムゾーンの切り替えにより疑似的に切り替える方法があります。
それは、、、
/usr/share/zoneinfo/Etc配下に格納されている、
GMT+**やGMT-**を用いる方法です。

どうやらEtc配下に格納されたファイルはタイムゾーンは全て無効になっており、時刻の±のみを操作するファイルのようです。
このため、地域に合わせてGMT+**やGMT-**を使用すればOKになります。
手順は先ほどと同様に、

sudo rm -f /etc/localtime
sudo ln -s /usr/share/zoneinfo/Etc/GMT** /etc/localtime

でOK!!

地域ごとに動かす時刻を把握しなければならないので、結構手間ですが、、、
やれないことはない!!
といった感じでしょうか??

■(補足)タイムゾーンリスト

タイムゾーンのリストは下記にまとまっておりますので、必要に応じて参考にしてください。
各地域毎のGMTオフセット時間も掲載されておりますので、どうしても切り替えたい場合には参考になるかと思います。
en.wikipedia.org

■最後に

今回はサマータイムを無効にしつつタイムゾーンを操作する方法についてまとめました。
まぁ、面倒な方法ですしLinux側でわざわざ地域毎にサマータイムの設定してくれているのですから、ちゃんとタイムゾーンを操作すればよいかと。
どうしても、、という場合の対応なのかな??
と個人的には感じる方法です。



Dear PyGuiとOpenCVを組み合わせて動画再生アプリを作成してみる

前回はDear PyGuiというpython用のGUIライブラリについてご紹介しました。
elsammit-beginnerblg.hatenablog.com

今回はこちらのDear PyGuiとOpenCVを組み合わせて動画再生アプリを作成してみたいと思います。



■Dear PyGui上で動画を表示してみる

では早速Dear PyGuiとOpenCVを組み合わせて動画表示方法についてまとめていきます。
まずはコードを載せます。

import dearpygui.dearpygui as dpg
import numpy as np
import cv2
import time

dpg.create_context()
dpg.create_viewport(max_width=550, max_height=360)
dpg.setup_dearpygui()

VideoFile = "X:\StudyCode\python\Forest - 49981.mp4"

vid = cv2.VideoCapture(VideoFile)
ret, img = vid.read()
frame_rate = int(vid.get(cv2.CAP_PROP_FPS))
img = cv2.resize(img , (480, 270))

with dpg.texture_registry(show=False):
    dpg.add_raw_texture(480, 270, img, tag="texture_tag", format=dpg.mvFormat_Float_rgb, use_internal_label=False)

with dpg.window(label="Main window",pos=[10,10]):
    dpg.add_image("texture_tag")

dpg.show_viewport()

while dpg.is_dearpygui_running():
    ret, img = vid.read()
    if ret == True:
        img = cv2.resize(img , (480, 270))
        data = np.flip(img, 2)
        data = data.ravel()
        data = np.asfarray(data, dtype='f')
        texture_data = np.true_divide(data, 255.0)
            
        dpg.set_value("texture_tag", texture_data)

        time.sleep(1/frame_rate)
    else:
        vid.release()
        vid = cv2.VideoCapture(VideoFile)

    dpg.render_dearpygui_frame()

dpg.destroy_context()

解説ですが、、、

dpg.create_context()
dpg.create_viewport(max_width=550, max_height=360)
dpg.setup_dearpygui()

はDear PyGuiでGUI Windowのセットアップを行うためのコードになり、

VideoFile = "動画path"
vid = cv2.VideoCapture(VideoFile)
ret, img = vid.read()
frame_rate = int(vid.get(cv2.CAP_PROP_FPS))
img = cv2.resize(img , (480, 270))

OpenCVによる動画データ読み出し処理になります。

ここまでは一般的な処理になりますね。

GUI Windowの定義ですが、

with dpg.window(label="Main window",pos=[10,10]):
    dpg.add_image("texture_tag")

としており、imageを追加しているのみになり、タブ名をtexture_tagとしています。
このtexture_tagに表示する画像データを定義しているのは、

with dpg.texture_registry(show=False):
    dpg.add_raw_texture(480, 270, texture_data, tag="texture_tag", format=dpg.mvFormat_Float_rgb, use_internal_label=False)

になります。

add_raw_texture APIはimage領域に画像データをセットする関数になります。
今回は、texture_tag名のimage領域にtexture_dataをセットします。
texture_dataですが、こちらのコードにてOpenCVで読み出したフレーム画像を
numpyにより変換を行った結果が格納しております。

data = np.flip(img, 2) 
data = data.ravel()
data = np.asfarray(data, dtype='f')
texture_data = np.true_divide(data, 255.0)

最後に、、、

while dpg.is_dearpygui_running():
    ret, img = vid.read()
    if ret == True:
        img = cv2.resize(img , (480, 270))
        data = np.flip(img, 2)
        data = data.ravel()
        data = np.asfarray(data, dtype='f')
        texture_data = np.true_divide(data, 255.0)
            
        dpg.set_value("texture_tag", texture_data)

        time.sleep(1/frame_rate)
    else:
        vid.release()
        vid = cv2.VideoCapture(VideoFile)

    dpg.render_dearpygui_frame()

にて、OpenCVで1フレーム毎に読み出し、先ほどの通りnumpyにより配列変換を行った上でtexture_tagにセット。

ループ再生にしておきたかったので、

    else:
        vid.release()
        vid = cv2.VideoCapture(VideoFile)

により、動画データのフレームが読み出せなくなったら再度動画データを読み出し、フレームの読み出しを繰り返していきます。

こちらのアプリを実行するとこちらの通り、動画再生が行われます。
f:id:Elsammit:20211230145820g:plain

■動画再生アプリのご紹介

先ほどのコードは動画がただ再生されるのみのコードになります。
ちょっとそれだけでは寂しいので、、、
こちらの機能を追加した動画再生アプリを作成してみました!!
 ・動画一時停止
 ・動画種類として、赤外や2値画像に切替処理
 ・再生する動画の変更

アプリ動作はyoutubeにて投稿してみましたのでよかったらご覧ください。
youtu.be

また、コードはこちらに格納してみました。
詳しい解説はもしかしたら後ほど投稿するかもです!!
GitHub - Elsammit/PlayMovie_CreatedInDearPy

■最後に

今回はDear PyGuiとOpenCVを組み合わせて動画再生アプリを作成してみました。
Dear PyGui、結構簡単にGUIアプリ作成できるのでお勧めです!!



Dear PyGuiを用いてPython GUIを作成してみる

先日Dear PyGuiという、シンプルで簡単に実装できかつ色々なことが出来そうなフレームワークを見つけたので早速触ってみました!!
今回はチュートリアルとして、インストール方法や簡単なアプリの作成方法を備忘録として残しておきたいと思います。



■Dear PyGuiとは?

公式には下記の通り記載されておりました。

Dear PyGui is a simple to use (but powerful) Python GUI framework. Dear PyGui is NOT a wrapping of Dear ImGui in the normal sense. It is a library built with Dear ImGui which creates a unique retained mode API (as opposed to Dear ImGui's immediate mode paradigm).

Dear PyGui is fundamentally different than other Python GUI frameworks. Under the hood, Dear PyGui uses the immediate mode paradigm and your computer's GPU to facilitate extremely dynamic interfaces.

要約すると、
Dear PyGuiは使いやすくかつ強力なPython GUIフレームワークです。
Dear ImGuiで構築されたライブラリではありますが、ただのWrapperではなく独自モードのAPIを作成しています。

Dear PyGuiは他のPythonGUIとはことなり、
immediate mode paradigm と GPUを用いて動的なインターフェースを促進しています。
とのことです。

Dear PyGuiはWindowsmacOSLinux、Raspberry Pi4で使用できるとのこと。
※ただし、GPUは必要です。

公式ではPlatformとしてWIndows 10を記載しておりましたが、
Windows 11でも動作しましたので、Windows 10でないと使えない!!というわけではないようです。

■環境構築

では早速環境構築方法を記載します。

今回はWindows11 を用いていきます。
と言ってもLinuxmacOSでも同じような手順で環境構築などは実施できるかと思います。

Dear PyGuiのインストールですが、下記コマンドを実行!!のみです。

pip install dearpygui
or
pip3 install dearpygui

■手始めにデモソフトを動かしてみる

Dear PyGuiでどのようなことが出来るのか、を公式ではデモソフトを提供しています。
ちょっとまずはこちらを動かしてみます。

下記コードを作成し、実行すればOKです。

import dearpygui.dearpygui as dpg
from dearpygui.demo import show_demo

dpg.create_context()
dpg.create_viewport()
dpg.setup_dearpygui()

show_demo()

dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

実行するとこちらのようなGUIが表示されます。
f:id:Elsammit:20211226114702p:plain

GUI上で操作すると何が出来るのかを実際に動かして確認できます。
操作すると分かるかと思うのですが、結構色々なことが出来そうです!!

ただ、、、
フォントや背景デザインはあまりいじれないのかな??といった感じです。
フォントの色やサイズぐらいしか変えられないのかな??

■サンプルアプリを作成して動かしてみる

では次にアプリケーションを作成し、実行させてみたいと思います。

今回は公式が出しているサンプルプログラムを少しだけアレンジさせたものを掲載したいと思います!!
コードはこちら。

import dearpygui.dearpygui as dpg

def save_callback():
    print("Save Clicked")

dpg.create_context()
dpg.create_viewport(max_width=300, max_height=200)
dpg.setup_dearpygui()

with dpg.window(label="Example Window",pos=[30,30],width=200,height=150):
    dpg.add_text("Hello world")
    dpg.add_button(label="Save", callback=save_callback)
    dpg.add_input_text(label="string")
    dpg.add_slider_float(label="float")

dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

まず、

dpg.create_context()
dpg.create_viewport(max_width=300, max_height=200)
dpg.setup_dearpygui()

にて、GUIのmain windowを作成しています。
create_viewportでmax_width=300, max_height=200を定義しておりますが、
こちらはwindowサイズを指定しております。
今回は表示させるアプリのサイズが小さいので300x200で定義してみました。

次に、

with dpg.window(label="Example Window",pos=[30,30],width=200,height=150):
    dpg.add_text("Hello world")
    dpg.add_button(label="Save", callback=save_callback)
    dpg.add_input_text(label="string")
    dpg.add_slider_float(label="float")

が各widgetを定義しているコードになります。
用意したwidgetですが、
・テキストラベル
・ボタン
・テキストボックス
・スライダー
の4種類になります。

ここで、ボタン押下時のコールバック関数として、

def save_callback():
    print("Save Clicked")

がセットされております。
このため、ボタンを押下するとコンソール上に
Save Clicked
といった文字列が表示されます。

widget達を表示するwindowですが、

with dpg.window(label="Example Window",pos=[30,30],width=200,height=150):

で定義しております。
引数としてWindow内にて表示する位置指定とサイズ指定が行えます。
それぞれ、
 ・位置:30x30の位置
 ・サイズ:200x150
としてみました。

そして、

dpg.show_viewport()
dpg.start_dearpygui()

で作成したGUIを表示しております。

こちらを実行すると、
Dear PyGuiと記載されたwindowの中にExample Windowと記載した小さなwindowが入っていることが確認できます。
f:id:Elsammit:20211226114047p:plain

それぞれ、Dear PyGuiと記載されたwindowは

dpg.create_context()
dpg.create_viewport(max_width=300, max_height=200)
dpg.setup_dearpygui()

で定義したwindowであり、
Example Windowと記載した小さなwindowは

with dpg.window(label="Example Window",pos=[30,30],width=200,height=150):

で定義したwindowになります。

windowの中のwith dpg.windowは複数作成することが出来ます。
例えば、

import dearpygui.dearpygui as dpg

def save_callback():
    print("Save Clicked")

dpg.create_context()
dpg.create_viewport(max_width=300, max_height=200)
dpg.setup_dearpygui()

with dpg.window(label="Example Window",pos=[30,30],width=200,height=150):
    dpg.add_text("Hello world")
    dpg.add_button(label="Save", callback=save_callback)
    dpg.add_input_text(label="string")
    dpg.add_slider_float(label="float")

with dpg.window(label="Example2 Window"):
    dpg.add_text("hoge hoge")
    
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

と変更の上実行すると、
Example2 Windowが追加表示されているのが分かるかと思います。

■最後に

今回はDear PyGuiのセットアップやチュートリアルをまとめてみました。
今後、こちらのフレームワークを使用したアプリもご紹介できれば、と考えております。