想定する場面
照度計や温度計といったデジタル表示のディスプレイを監視して記録する。
(写真はイメージ)
プログラムの内容&流れ
既存のssocrというプログラムとpythonのコマンド操作機能を組み合わせて
映像から数値を認識してCSVに出力するプログラムを作成した。
1.動画から静止画を生成する。
2.静止画を回転させて数値が水平になる角度を探す。
3.ディスプレイ領域を把握する。
4.数値の抽出&CSVファイルへ書込み。
出力イメージ
導入(Linux/Mac)
☆すべてターミナル上で実行
・ssocrのインストール
sudo apt-get update
sudo apt-get install libx11-dev
sudo apt-get install libimlib2-dev
git clone https://github.com/auerswal/ssocr.git
cd ssocr
make
sudo mv ./ssocr /usr/local/bin/ssocr
ssocr --version
・pythonのインストール
sudo apt install python
ライブラリのインストール
pip install opencv-python
導入(Windows)
☆コマンドプロンプトとCygwinコンソールで実行
・Cygwinをインストール
インストールと同時に以下のパッケージをインストール
・git
・make
・gcc-core
・gcc-g++
・libintl-devel
・imlib2
・imlib2-devel
・cygutils-X11
※後でもインストール可能
・ssocrのインストール
git clone https://github.com/auerswal/ssocr.git
cd ssocr
make
mv ./ssocr /usr/local/bin/ssocr
ssocr.exe --version
・Microsoft StoreのPythonをダウンロード&インストール
・ライブラリのインストール(コマンドプロンプトで)
pip install opencv-python
・ソースコードのsubprocess.run()のssocrをssocr.exeに変更する。
使い方
(Linux/Mac)
・デスクトップに抽出する動画を置く
・cdコマンドでmain.pyのあるディレクトリまで移動
・以下コマンド実行
python3 main.py
(Windows)
・デスクトップに抽出する動画を置く。
・cdコマンドでmain.pyのあるフォルダまで移動する。(Cygwinコンソールで)
・以下コマンドでpythonの実行ファイルのフルパスを調べる。
where.exe python
・python main.pyではなく以下コマンドで実行する。(Cygwinコンソールで)
pythonの実行ファイルのフルパス + main.py
映像から数値を抽出する pic.twitter.com/sAOYiwT2xe
— てと (@ppyyaammaa) August 8, 2022
ソースコード
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)
※随時、変更・修正・追加しています。
指摘・不明点・要望などあれば気軽にメッセージください。