映像から数値を抽出する(Python)



想定する場面
---------------------------------------
照度計や温度計といったデジタル表示のディスプレイを監視して記録する。
![789D46C2-0FAC-445A-961D-EDF8ADEEA502.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234828/716cb3a2-ec64-fbc5-582e-0af05f113199.jpeg)
(写真はイメージ)


プログラムの内容＆流れ
---------------------------------------
既存のssocrというプログラムとpythonのコマンド操作機能を組み合わせて
映像から数値を認識してCSVに出力するプログラムを作成した。

1.動画から静止画を生成する。
2.静止画を回転させて数値が水平になる角度を探す。
3.ディスプレイ領域を把握する。
4.数値の抽出＆CSVファイルへ書込み。


出力イメージ
---------------------------------------
![スクリーンショット 2022-08-11 21-08-28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234828/3cf3f21f-fcb6-ba12-23d3-cca65de62f98.png)




導入(Linux/Mac)
---------------------------------------
☆すべてターミナル上で実行

・ssocrのインストール

https://ourcodeworld.com/articles/read/741/how-to-recognize-seven-segment-displays-content-with-ssocr-seven-segment-optical-character-recognition-in-ubuntu-1604

```terminal
sudo apt-get update
sudo apt-get install libx11-dev
sudo apt-get install libimlib2-dev
```

```terminal
git clone https://github.com/auerswal/ssocr.git
```

```terminal
cd ssocr
```

```terminal
make 
```

```terminal
sudo mv ./ssocr /usr/local/bin/ssocr
```

```terminal
ssocr --version
```

・pythonのインストール
```terminal
sudo apt install python
```

ライブラリのインストール

```terminal
pip install opencv-python
```




導入(Windows)
---------------------------------------
☆コマンドプロンプトとCygwinコンソールで実行

・Cygwinをインストール

https://www.cygwin.com/


インストールと同時に以下のパッケージをインストール
・git
・make
・gcc-core
・gcc-g++
・libintl-devel
・imlib2
・imlib2-devel
・cygutils-X11
※後でもインストール可能


・ssocrのインストール

https://ourcodeworld.com/articles/read/741/how-to-recognize-seven-segment-displays-content-with-ssocr-seven-segment-optical-character-recognition-in-ubuntu-1604

```terminal
git clone https://github.com/auerswal/ssocr.git
```
```terminal
cd ssocr
```
```terminal
make 
```

```terminal
mv ./ssocr /usr/local/bin/ssocr
```

```terminal
ssocr.exe --version
```


・Microsoft StoreのPythonをダウンロード＆インストール

・ライブラリのインストール（コマンドプロンプトで）
```terminal
pip install opencv-python
```

・ソースコードのsubprocess.run()のssocrをssocr.exeに変更する。


使い方
--------------------------------

(Linux/Mac)
・デスクトップに抽出する動画を置く
・cdコマンドでmain.pyのあるディレクトリまで移動
・以下コマンド実行
```terminal
python3 main.py
```


(Windows)
・デスクトップに抽出する動画を置く。
・cdコマンドでmain.pyのあるフォルダまで移動する。（Cygwinコンソールで）
・以下コマンドでpythonの実行ファイルのフルパスを調べる。

```terminal
where.exe python
```
・python main.pyではなく以下コマンドで実行する。（Cygwinコンソールで）
```terminal
pythonの実行ファイルのフルパス + main.py
```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">映像から数値を抽出する <a href="https://t.co/sAOYiwT2xe">pic.twitter.com/sAOYiwT2xe</a></p>&mdash; てと (@ppyyaammaa) <a href="https://twitter.com/ppyyaammaa/status/1556623582481944577?ref_src=twsrc%5Etfw">August 8, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

ソースコード
--------------------------------


```python:main.py
import os
import subprocess
import cv2

class NumberExtraction(object):
    def __init__(self,base_path_name,movie_file_name):
        self.base_path_name   = base_path_name
        self.movie_file_name  = movie_file_name
        self.movie_path_name  = base_path_name + "/" + movie_file_name
        self.rotate_path_name = base_path_name + "/angle"
        self.divide_path_name = base_path_name + "/frame"
        self.csv_path_name    = base_path_name + "/extracted_number.csv"
        self.cap   = cv2.VideoCapture(self.movie_path_name)
        self.digit = len(str(int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    def fps(self, movie_path_name):
        cap = cv2.VideoCapture(a.movie_path_name)  # 動画を読み込む
        video_fps = cap.get(cv2.CAP_PROP_FPS)  # フレームレートを取得する
        return video_fps

    def save_all_frames(self,dir_path, basename, ext='jpg'):
        subprocess.run(["mkdir", self.divide_path_name])
        if not self.cap.isOpened():
            return
        os.makedirs(dir_path, exist_ok=True)
        base_path = os.path.join(dir_path, basename)
        n = 1
        while True:
            print(f"フレーム分解中:{n}")
            ret, frame = self.cap.read()
            if ret:
                cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(self.digit), ext), frame)
                n += 1
            else:
                return

    def rotate_picture(self,start_number,change_angle):
        subprocess.run(["mkdir", self.rotate_path_name])
        path = self.divide_path_name + "/div_" + str(start_number).zfill(self.digit) + ".jpg"
        img = cv2.imread(path)
        height, width = img.shape[:2]
        img = cv2.resize(img,(round(width/4), round(height/4)))
        height, width = img.shape[:2]
        center = (width/2, height/2)
        i = 1
        while True:
            print(f"{i}度回転画像生成")
            rotate_matrix = cv2.getRotationMatrix2D(center=center, angle=i, scale=1)
            rotated_image = cv2.warpAffine(src=img, M=rotate_matrix, dsize=(width, height))
            cv2.imwrite(self.rotate_path_name +"/"+ str(i) + ".jpg",rotated_image)
            cv2.waitKey(0)
            cv2.destroyAllWindows()
            i += change_angle
            if i > 360:
                break

    def coordinate(self,start_number):
        print("ディスプレイ範囲の設定")
        print("1:左上と右下をクリックする")
        print("2:適当なボタンを押すと画面ビューが終了する")
        print("3:コンソールで半角スペースを入れて4つの数値を入力(例：279 180 733 447)")
        print("4:Enter")
        def onMouse(event, x, y, flags, params):
            if event == cv2.EVENT_LBUTTONDOWN:
                print(x,y)
        img = cv2.imread(self.divide_path_name + "/div_" + str(start_number).zfill(self.digit) + ".jpg")
        cv2.imshow('area_setting', img)
        cv2.setMouseCallback('area_setting', onMouse)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
        x1,x2,y1,y2= input().split()
        trimming_area = [int(x1),int(x2),int(y1),int(y2)]
        return trimming_area

    def width_hight(self, trimming_area):
            w = trimming_area[2] - trimming_area[0]
            h = trimming_area[3] - trimming_area[1]
            wh = [w,h]
            return wh

    def extract_number(self, rotate, trimming_area, wh, start_frame, finish_frame, fps):
        # 回転角度
        r = str(360-rotate)
        # 座標指定
        x = str(trimming_area[0])
        y = str(trimming_area[1])
        w = str(wh[0])
        h = str(wh[1])
        i = start_frame
        count_null = 0
        count_extract = 0
        count_frames = 0
        while True:
            j = self.divide_path_name + "/div_" + str(start_frame).zfill(self.digit) + ".jpg"
            a = subprocess.run(["ssocr","-D","crop",x,y,w,h,"rotate",r,"-T","-d","-1",j],stdout=subprocess.PIPE)
            start_frame += fps
            #CSVファイルへの書き込み
            path = self.csv_path_name
            if start_frame / fps < 60:
                print(f"0:{str(int(start_frame / fps)).zfill(2)}")
                with open(path, "a") as file:
                    file.write(f"0:{str(int(start_frame / fps)).zfill(2)}")
                    file.write(",")
            else:
                print(f"{int(start_frame / fps) // 60}:{str(int(start_frame / fps) % 60).zfill(2)}")
                with open(path, "a") as file:
                    file.write(f"{int(start_frame / fps) // 60}:{str(int(start_frame / fps) % 60).zfill(2)}")
                    file.write(",")
            try:
                print(int(a.stdout))
                count_extract += 1
                count_frames += 1
                with open(path,"a") as file:
                    file.write(str(int((a.stdout))))
                    file.write("\n")
            except:
                print("NULL")
                count_null += 1
                count_frames += 1
                with open(path, "a") as file:
                    file.write("NULL")
                    file.write("\n")
            if start_frame > finish_frame:
                print(f"測定フレーム数：{count_frames}\n抽出成功フレーム数：{count_extract}\n抽出失敗フレーム数：{count_null}\n")
                print(f"抽出成功率：{int((count_extract/count_frames)*100)}%")
                break



desktop_path = input("デスクトップまでのパスを入力してください。＞＞")
movie_name = input("測定する動画のファイル名を入力してください。＞＞")
a = NumberExtraction(desktop_path,movie_name)
fps = int(a.fps(a.movie_path_name))
a.save_all_frames(a.divide_path_name,"div")
view_number = int(input("[frame]フォルダを確認してビューに使用するフレーム番号を入力してください。＞＞"))
a.rotate_picture(view_number,1)
trimming_area = a.coordinate(view_number)
wh = a.width_hight(trimming_area)
start_number = int(input("[frame]フォルダを確認して測定する最初のフレーム番号を入力してください。＞＞"))
finish_number = int(input("[frame]フォルダを確認して測定する最後のフレーム番号を入力してください。＞＞"))
angle = int(input("[angle]フォルダを確認して数値が水平となるファイル番号を入力してください。＞＞"))
a.extract_number(angle,trimming_area,wh,start_number,finish_number,fps)



```

※随時、変更・修正・追加しています。
指摘・不明点・要望などあれば気軽にメッセージください。