Power Automate Desktop からOpenCVを使うときの注意点


概要

Power Automate Desktop(以下PAD)を使ってGCPのText Detectionを行った後、PythonのOpenCVで画像の中の文字をモザイク処理してみました。
その際の注意点です。

ナンバーがモザイク処理されています。

前提条件

Windows10pro 20H2
Power Automate Desktop 2.2.20339.22608。
GCP Vison API

Python 3.8.5
pandas 1.1.4
numpy 1.18.5
opencv-python 4.4.0.46
.pyファイルが実行できること

ディストリビューションではなくhttps://www.python.org/のインストーラーを使っています。
ライブラリは個別にインストールしています
2020年12月の情報です。

注意事項

PADはPython2がアクションとして使えますが、OpenCV等のライブラリの組み込みが不明なため、自分環境のPythonを呼び出す方法を用いています。
以前記事で紹介しましたがWinAutomationではWrite Textを使ってコードを生成し任意のPythonを使用する事が出来ました。
しかしPADでは同じ方法を使おうとすると制限が掛かっています。
同様の方法を取りますが、制限が掛かっている以上、現状では正式にサポートされている方法ではありません。

フローイメージ

フロー作成

デスクトップに「Landscape」「LMOut」の二つのフォルダを作っておきます。

4行目「テキスト検出」アクションにご自分のAPIキーは取得したものを入れて下さい。

Folder.GetSpecialFolder SpecialFolder: Folder.SpecialFolder.DesktopDirectory SpecialFolderPath=> SpecialFolder
Folder.GetFiles Folder: $'''%SpecialFolder%\\Landscape''' FileFilter: $'''*.jpg''' IncludeSubfolders: False FailOnAccessDenied: True SortBy1: Folder.SortBy.NoSort SortDescending1: False SortBy2: Folder.SortBy.NoSort SortDescending2: False SortBy3: Folder.SortBy.NoSort SortDescending3: False Files=> Files
LOOP FOREACH CurrentItem IN Files
    Cognitive.Google.Vision.TextDetectionFromFile APIKey: $'''''' ImageFile: CurrentItem Timeout: 30 Response=> JSONResponse StatusCode=> StatusCode
    File.WriteText File: $'''%SpecialFolder%/Landscape/%CurrentItem.NameWithoutExtension%.json''' TextToWrite: JSONResponse AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8NoBOM
    File.WriteText File: $'''%SpecialFolder%/Landscape/%CurrentItem.NameWithoutExtension%.py''' TextToWrite: $'''aaaaaaaa''' AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
    System.RunDOSCommand DOSCommandOrApplication: $'''%SpecialFolder%/Landscape/%CurrentItem.NameWithoutExtension%.py''' WorkingDirectory: $'''%SpecialFolder%/Landscape''' StandardOutput=> CommandOutput StandardError=> CommandErrorOutput ExitCode=> CommandExitCode
    File.Delete Files: $'''%SpecialFolder%/Landscape/%CurrentItem.NameWithoutExtension%.py'''
    File.Delete Files: $'''%SpecialFolder%/Landscape/%CurrentItem.NameWithoutExtension%.json'''
    File.Move Files: CurrentItem Destination: $'''%SpecialFolder%\\LMOut''' IfFileExists: File.IfExists.Overwrite MovedFiles=> MovedFiles
END

Pythonコードを入れる

ここからが今回の本題となります。
画像の全体フローでは6行目にPythonコードが入っていますが上記コードでは敢えてaaaaaaaaにしてあります。

aaaaaaaaのところにpythonコードが書ければ問題ないのですが、PADでは「書き込むテキスト」の欄が改行できません。

どうして「書き込むテキスト」を使いたいかというと、このアクションからpythonコードを生成するとPAD側の変数をPythonに渡す事が出来るからです。

幸いPADはRPA言語のRobinベースで動作しています。
PAD上でアクションは視覚的にブロック化され、ユーザーにわかりやすく表示されていますが実際は上のコードのようにテキストベースです。
なので「書き込むテキスト」を一度エディタに張り付けることで単なるテキストとして扱うことができます。

そしてaaaaaaaaaaaの部分にPythonコードをペーストしてから全体をPADに張り付け直すと「書き込むテキスト」アクションにPythonコードを格納することができます。


aaaaaaaaaaa部分のPythonコード

# ライブラリ読み込み
import json
import pandas as pd
import numpy as np
import cv2

# JSON読み込み
with open("%CurrentItem.NameWithoutExtension%.json", "r", encoding="utf-8") as content:
    data = json.loads(content.read())

# 画像の読み込み
img = cv2.imread("%CurrentItem.Name%")
xmax = img.shape[1]
ymax = img.shape[0]

# モザイク定義

def mosaic(img, rect, size):
    # モザイクをかける領域を取得
    (x01, y01, x02, y02) = rect
    w = x02-x01
    h = y02-y01
    i_rect = img[y01:y02, x01:x02]
    # 一度縮小して拡大する
    i_small = cv2.resize(i_rect, (size, size))
    i_mos = cv2.resize(i_small, (w, h), interpolation=cv2.INTER_AREA)
    # モザイクに画像を重ねる
    img2 = img.copy()
    img2[y01:y02, x01:x02] = i_mos
    return img2


# 座標抜出およびモザイク処理
for c in range(1, len(data["responses"][0]["textAnnotations"])):
    pointdata = data["responses"][0]["textAnnotations"][c]["boundingPoly"]["vertices"]
    df_target = pd.read_json(json.dumps(pointdata))

    x0 = int(df_target.fillna(0).iat[0, 0])
    x1 = int(df_target.fillna(xmax).iat[1, 0])
    x2 = int(df_target.fillna(xmax).iat[2, 0])
    x3 = int(df_target.fillna(0).iat[3, 0])

    xlist = np.array([x0, x1, x2, x3])
    xp1 = np.min(xlist)
    xp2 = np.max(xlist)

    y0 = int(df_target.fillna(0).iat[0, 1])
    y1 = int(df_target.fillna(0).iat[1, 1])
    y2 = int(df_target.fillna(ymax).iat[2, 1])
    y3 = int(df_target.fillna(ymax).iat[3, 1])

    ylist = np.array([y0, y1, y2, y3])
    yp1 = np.min(ylist)
    yp2 = np.max(ylist)

    pointlist = np.array([xp1, yp1, xp2, yp2])

    img = mosaic(img, pointlist, 5)
# 画像保存
cv2.imwrite("/Users/Username/Desktop/LMOut/%CurrentItem.NameWithoutExtension%_masked.jpg", img)

上記コードについて
VisionAPIから返却されたJSONの読み取った文字の位置情報抜き出し、モザイク処理を行います。
Pandasに入れたのは文字が画像からはみ出ているケースで、VisionAPIから返却される位置情報が欠損していたため0か画像最大値で埋めたかったからです。

注意点

%で囲われた文字はPADの変数としての処理になります。
最も重要なことはRobin言語で'シングルクオーテーションは既に文字の囲みとして意味を持っているため、Pythonコードでは文字の囲みを全て"ダブルクォーテーションにしています。
\バックスラッシュはRobin言語の中でエスケープ文字になるので注意が必要です。
\%はエスケープできません。

まとめ

  • 2020年12月現在「テキストをファイルに書き込みます」アクションは複数行が使えないです。
  • あくまで推測ですが'シングルクォーテションがRobin言語とかぶって利用されると動かないために制限されていると考えます。
  • Robin言語についてはこちらをご参照ください。

  • Winautomationのドラッグドロップで使えるファイルトリガーは即時的で強力でした。PADでもPower Automateのファイルトリガーを使用することもできますが、今回のようなフロー向けではないと思います。

  • Pythonの環境を作らないといけないフローは実務的には難しいかもしれませんが手札はあった方がよいと思って記事にしました。

  • 需要なさそうなネタですが、今年もあと2日、大掃除のつもりでアウトプットです。