LoginSignup
1
2

More than 3 years have passed since last update.

Lチカで始めるテスト自動化(5)WebカメラおよびOCRの組込み

Posted at

1. はじめに

Lチカで始めるテスト自動化シリーズ第五弾です。

M5StackとPythonで受入テスト自動化の要素技術を試すでインタラクティブシェルで行っていたWebカメラの画像キャプチャや画像の切り出し、OCRをテストランナーに組み込んで自動化します。

これまでの記事はこちらをご覧ください。

  1. Lチカで始めるテスト自動化
  2. Lチカで始めるテスト自動化(2)テストスクリプトの保守性向上
  3. Lチカで始めるテスト自動化(3)オシロスコープの組込み
  4. Lチカで始めるテスト自動化(4)テストスクリプトの保守性向上(2)

2. テストランナーの改修

  • 以下のコマンドおよび関数を作成します。
コマンド 引数 機能
open_cam カメラデバイスの番号 OpenCVのVideoCaptureを実行する
close_cam なし OpenCVのVideoCaptureをreleaseする
capture_cam ファイル名 カメラ画像をキャプチャしファイルに保存する
crop_img 入力ファイル名
切り出し位置(縦)
切り出し位置(横)
出力ファイル名
指定されたエリアを画像ファイルから切り出してファイルに保存する
open_ocr なし PyOCRを利用可能にする
exec_ocr OCRにかける画像ファイル名 画像ファイルからUS ASCIIの文字列をOCRする
  • 冗長なコードを改修に併せて削除しました。
test-runner.py
#!/usr/bin/python3

#
# This software includes the work that is distributed in the Apache License 2.0
#

from time import sleep
import serial
import codecs
import csv
import sys
import visa
import cv2
from PIL import Image
import pyocr
import pyocr.builders

UNINITIALIZED = 0xdeadbeef

def open_cam(camera_number):
    return cv2.VideoCapture(camera_number)

def close_cam(cam):
    if cam != UNINITIALIZED:
        cam.release()

def capture_cam(cam, filename):
    if cam != UNINITIALIZED:
        _, img = cam.read()
        cv2.imwrite(filename, img)
        return True
    else:
        print("CAM Not Ready.")
        return False

def crop_img(filename_in, v, h, filename_out):
    img = cv2.imread(filename_in, cv2.IMREAD_COLOR)
    v0 = int(v.split(':')[0])
    v1 = int(v.split(':')[1])
    h0 = int(h.split(':')[0])
    h1 = int(h.split(':')[1])
    img2 = img[v0:v1, h0:h1]
    cv2.imwrite(filename_out, img2)
    return True

def open_ocr():
    ocr = pyocr.get_available_tools()
    if len(ocr) != 0:
        ocr = ocr[0]
    else:
        ocr = UNINITIALIZED
        print("OCR Not Ready.")
    return ocr

def exec_ocr(ocr, filename):
    try:
        txt = ocr.image_to_string(
            Image.open(filename),
            lang = "eng",
            builder = pyocr.builders.TextBuilder()
        )
    except:
        print("OCR Not Ready.")
    else:
        return txt

def serial_write(h, string):
    if h != UNINITIALIZED:
        string = string + '\n'
        string = str.encode(string)
        h.write(string)
        return True
    else:
        print("UART Not Initialized.")
        return False

def open_dso():
    rm = visa.ResourceManager()
    resources = rm.list_resources()
    #print(resources)
    for resource in resources:
        #print(resource)
        try:
            dso = rm.open_resource(resource)
        except:
            print(resource, "Not Found.")
        else:
            print(resource, "Detected.")
            return dso

    #Throw an error to caller if none succeed.
    return dso

def main():
    is_passed = True
    val = str(UNINITIALIZED)
    cam = UNINITIALIZED
    ocr = UNINITIALIZED
    dso = UNINITIALIZED
    uart = UNINITIALIZED

    with codecs.open('script.csv', 'r', 'utf-8') as file:
        script = csv.reader(file, delimiter=',', lineterminator='\r\n', quotechar='"')

        with codecs.open('result.csv', 'w', 'utf-8') as file:
            result = csv.writer(file, delimiter=',', lineterminator='\r\n', quotechar='"')

            for cmd in script:
                print(cmd)

                if "#" in cmd[0]:
                    pass

                elif cmd[0] == "sleep":
                    sleep(float(cmd[1]))

                elif cmd[0] == "open_cam":
                    cam = open_cam(int(cmd[1]))

                elif cmd[0] == "close_cam":
                    close_cam(cam)
                    cam = UNINITIALIZED

                elif cmd[0] == "capture_cam":
                    ret = capture_cam(cam, cmd[1])
                    if ret == False:
                        is_passed = False

                elif cmd[0] == "crop_img":
                    crop_img(cmd[1], cmd[2], cmd[3], cmd[4])

                elif cmd[0] == "open_ocr":
                    ocr = open_ocr()
                    if ocr == UNINITIALIZED:
                        is_passed = False

                elif cmd[0] == "exec_ocr":
                    try:
                        val = exec_ocr(ocr, cmd[1])
                    except:
                        is_passed = False
                    else:
                        cmd.append(str(val))

                elif cmd[0] == "open_dso":
                    try:
                        dso = open_dso()
                    except:
                        is_passed = False

                elif cmd[0] == "dso":
                    try:
                        if "?" in cmd[1]:
                            val = dso.query(cmd[1]).rstrip().replace(",", "-")
                            cmd.append(val)
                        else:
                            dso.write(cmd[1])
                    except:
                        is_passed = False

                elif cmd[0] == "open_uart":
                    try:
                        uart = serial.Serial(cmd[1], 115200, timeout=1.0, dsrdtr=1)
                    except:
                        is_passed = False

                elif cmd[0] == "send":
                    ret = serial_write(uart, cmd[1])
                    if ret == False:
                        is_passed = False

                elif cmd[0] == "rcvd":
                    try:
                        val = uart.readline().strip().decode('utf-8')
                        cmd.append(val)
                    except:
                        is_passed = False

                elif cmd[0] == "eval_str_eq":
                    if str(val) != str(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_int_eq":
                    if int(val) != int(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_int_gt":
                    if int(val) < int(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_int_lt":
                    if int(val) > int(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_dbl_eq":
                    if float(val) != float(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_dbl_gt":
                    if float(val) < float(cmd[1]):
                        is_passed = False

                elif cmd[0] == "eval_dbl_lt":
                    if float(val) > float(cmd[1]):
                        is_passed = False

                else:
                    cmd.append("#")

                if is_passed == True:
                    cmd.append("OK")
                    print(cmd)
                    result.writerow(cmd)
                else:
                    cmd.append("NG")
                    print(cmd)
                    result.writerow(cmd)
                    close_cam(cam)
                    print("FAIL")
                    sys.exit(1)

    if is_passed == True:
        close_cam(cam)
        print("PASS")
        sys.exit(0)

main()

3. テストスクリプト

crop_imgコマンドの確認はM5StackとPythonで受入テスト自動化の要素技術を試す3.2 Webcam画像のキャプチャで生成された123abc.bmpファイルを使用しています。

script.csv
open_cam,1
capture_cam,abc1234.jpg
close_cam
crop_img,123abc.bmp,234:274,240:400,123abc_crop.bmp
open_ocr
exec_ocr,123abc_crop.bmp
eval_str_eq,123-abc

4. テスト実行結果

  • 実行環境はWindows10(1909)+Anaconda3 2019.07 (Python 3.6.9 64-bit)です1
  • Webカメラの画像を保存できました。
  • crop_imgコマンドで切り出した画像をOCRにかけて、期待値の文字列と一致することを自動で確認できました。
result.csv
open_cam,1,OK
capture_cam,abc1234.jpg,OK
close_cam,OK
crop_img,123abc.bmp,234:274,240:400,123abc_crop.bmp,OK
open_ocr,OK
exec_ocr,123abc_crop.bmp,123-abc,OK
eval_str_eq,123-abc,OK

5. おわりに

UARTのコマンド送受信、組込み機器のスイッチのON/OFF、ArduinoのA/Dコンバータを使った電圧測定、オシロスコープの組込み、Webカメラの組込み、画像の切り出し、OCRを使った文字列の抽出と比較をできるようになりました。組込み機器やIoTデバイスのシステムレベルのCI/CDの可能性がだいぶ上がったように思います。


  1. OpenCV、Tesseract-OCR、PyOCR、PILのインストールやTesseract-OCRの環境変数の設定はM5StackとPythonで受入テスト自動化の要素技術を試すをご覧ください。 

1
2
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
1
2