この記事ではAXC F 3152 PLCnext と最近発売し始めた機械学習モジュールAXC F XT ML1000の立ち上げ手順・デモ作成までご紹介します。よろしくおねがいします。
Thanks!
この記事では、Phoenix Contact ジャパンからAXC F 3152とAXC F XT ML1000のお貸出しの提供でお送りします。誠にありがとうございます。
Video
AXC F XT ML1000?
この拡張モジュールAXC F XT ML1000を使用することにより、AXC F 3152 PLCnext controllerから直接機械学習アプリケーションを実装できます。データはPLCと直接に交換でき、ML1000使用するモデルFileはTensorFlow Lite filesで、Edge TPU (Coral)を AXC F 3152 PLCnext controllerと統合、なおかつPythonでAI アプリケーションを作成できます。
Firmware
PLCNEXT ControllerはAXC F 3152 のみでFirmware >2022.0 LTS以上です。
Interneal Cirucit
下図はML1000内部回路です。
- Tensor Processing unit
- エラー表示のLED
- Extension busはAxioline F 拡張モジュール用のもの
Layout
以下はML1000のLayoutです。
- Bus base モジュール
- Function Identification
- Electronics module
- 電源供給のConnector
- 電源診断のLED
Power Supply
基本は24v DCで、隣のAXCF 3152から電源を取ればよいと思います。ですが、注意するのはAXC F3152とAXC F XT ML1000は必ず同じなので電源をとってください。AXC F 3152だけ電源なくなりAXC F XT ML1000がまだPOWER ONしてる状態になるとAXC F XT ML1000モジュールがエラーで復旧不可になります。
Terminal
ML 1000はAXC F 3152 CPUと同じく小さな電源ターミナルがついています。
赤はa1,a2は24V+側で、青いのはb1,b2はマイナス側です。
LED
AXC F XT ML1000で使えるLEDはその左下だけです。赤になるとエラーです。
Color | State | Description |
赤 | On | モジュールエラーがあります。例えば、CPUがSupportしないモジュールが正しくインストールされてない稼働中に単体で電源落としたことある |
黄色 | On | Boot-upシーケンス中 |
緑 | On | 稼働中、エラーなし |
Setup
AXC F XT ML1000をSetupするには、2つの前提が必要です。
- root 権限でLoginしてる
- インタネットの接続
PLCNEXT Engineer Version
こちらは自分のPLCNEXT Enginner Versionです。
SFTP Download
Reference Link
以下のStepでRoot、PLCNEXTをSFTP 使用しFileをDownloadする手順を説明しました。
Step
下記のLinkからML1000のSoftware packageをDownloadします。
https://www.phoenixcontact.com/en-in/products/extension-module-axc-f-xt-ml-1000-1259849
ライセンス同意し、Downloadします。
このようなZIP FileがDownloadされました。
解凍しso.1の2つのFileは先Reference Link通りに、\usr\libに転送してください。
PROTOはまた別のところで使いますので、いまはひとまず放置します。
Download python basic
もしPLCNEXT StoreからすべてのPython Basic appがインストールされたらこのStepが不要です。異なる場合は以下のパッケージをPIPからインストールしてください。
- numpy
- pillow
- pycoral
- tflite_runtime
Refercence Link
下記のLinkはOfflineでPLCNEXTのApp StoreからAppをインストール手順を説明します。PackageはPython basicなので、その記事に沿ったままインストールしてください。
Check the Module
以下のコマンドでAXC F XT ML 1000にアクセスできるかを確認できます。
lspci –nn | grep 089a |
もしアクセスできるのであればモジュールからこのような返答がきます。
逆にもしアクセスできないのであれば、システムからエラーメッセージを返答します。そのときはモジュールが正しくインストールされたか、電源があるかをCheckしてください。
Downloading and installing a sample application
次はあなたのAXC F 3152 PLCNEXT ControllerをInternetと接続してください。そしてGoogleからSampleをDownloadし、モジュールをテストします。今回の例は鳥の画像判定Applicationです。
Controller内にDirectoryを作成し、作業Directoryを移動します。
mkdir coral && cd coral |
DirectoryにSample applicationをDownloadします。
git clone https://github.com/google-coral/pycoral.git |
Sample アプリケーションをインストラクターします。
bash examples/install_requirements.sh classify_image.py |
最後は AXC F 3152 controllerをInternetと切断します。
SSL Certificate problem certificate is not yet valid
もしSSL Certificate エラーがあれば以下の記事を参考にしてください。
Executing the sample application
以下のコマンドで画像判定を実行し、判定したい鳥の写真をパラメーターとして入力します。
python3 examples/classify_image.py \ –model test_data/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \ –labels test_data/inat_bird_labels.txt \ —input test_data/parrot.jpg |
TPUから判定結果が返答されました!
Integrate With PLCNEXT Controller
次は判定した結果をどうやってPLCNext Engineerのアプリケーションに転送するかを考えます。もちろんOPCUAやMQTTなどもOKですが、ここではAXCF 3152に統合されたgRPC serverを使用し、判定結果を指定の変数に転送します。
gRPC communication?
まずgRPC通信のことを簡単に紹介しますね。gRPCはオープンソースで・High PerformanceのFrame Work、Remote Procedural Callの略です。
最初にGOOGLEが開発され、いまは Cloud Native Computing Foundationが主導していますgRPC はUser-frinedly、異なるサービスで通信できます。
Communication LayerではgRPC はHTTP2を使用し、Protocol Bufferをを使っています。
例えばいま異なるServicesがあるとします。よくあるのはAMAZONでの買い物ですね。商品を購入するにはBuy Servicesがあり、買った商品は物流センターに頼む必要があります。つまりDelivery Servicesが必要です。買い物してるおすすめ商品の項目もあります。どの商品を客に薦めるのあhPromotion Servicesあります。そしてそれらのServicesがUser seriesとつながっています。つまりあなたです。それらのSeriesは必ずしも同じプログラミング言語を実装するとはかぎらず、お互いにやりとりするためにいまよく使われているのはREST(HTTP-JSON)です。
ですが、RESTに様々な考慮が出てきます。例えば、
- Data Modelはどうするのか。JSON?XML?他には?
- End Pointが長くて複雑になる。
- GETなら、/api/v1/something/123/post/abcsd
- POSTなら、/api/v1/post/something
- Errorの検知
- 効率か?
- 1回の呼び出しはどれくらいのデータ?
- 大量のデータは問題ないか?データが少なすぎるとAPIのCALL回数が増えすぎるのではないか?
- 1000 Clientは大丈夫ですか?
- Load Balaningは大丈夫か?
- 複数の言語はSupportしていますか?
- User認証・監視・Loggin機能は?
そこに出てくるのはAPIです。
APIは簡単に言いますと契約です。Clientがリクエストを送信、そして受信側がそのリスボンを返す。それだけです。すべてDATAです。
先にも言いましたが、gRPCは無料で、オープンソースでGoogleから開発され、現在は Cloud Native Computing Foundationが主導しています。Higher Layerになると、RPCは私達がリクエストとリスボンを定義し処理してくれるものだと思ってください。gRPCは:
- Modern
- 高速
- 効率的
- HTTP/2で作られ
- Stream Support
- プログラミング言語に依存しない
- Loggin・認証・監視などのPlug-inにはめ込むのは簡単
RPC特徴の一つとしてはClient側が直接Serverからの関数をよび出すようにみえます。
つまり下図のように関数名が同じく、直接Server側から直接呼び出してるみたいなのはRPC Codeです。
実際のアプリケーションではこのようになるかもしれません。
gRPCを使用するには、”Protocol buffers”からメッセージとサービスを定義する必要があります。
Client側はまず読みリクエストと書きリクエストを初期化します。書き読みのリクエストは
.proto Fileで定義されgRPC GitHub repositoryからDownloadできます。.proto Fileは自動的に通信上のプログラムを生成します。
PLCnext Technology and gRPC
アプリケーションにより、私はPLCnext Runtime Systemとやり取りするにはなにかの通信Intefaceが必要です。100%ができなく、一部は機能上の制限か、プログラム言語の制限にもあるかもしれません。
gRPC がinter process communication (IPC)にInterfaceを追加できます。
RPC Frameworkを使用するとオープンソース・プログラム言語に頼らないProtocolになります。
Step1:Creating the required variables
PLCnext Engineerに変数を定義し、プロジェクトをCPUにDownloadします。
今回の変数はINT_Aの文字列になります。
Step2:Install the packages
次はPackageをインストールします。
もしPLCNEXT StoreからPython Basic appをインストールしてないなら、grpcioとprotobuf Packageをインストールしてください。
pip3 install grpcio pip3 install protobuf |
Phoenix ContactからDownloadしたML1000_packages内にあるPROTO FolderをAXC F 3152 controllerの下記Directoryに移動します。
移動先は実際Pythonの判定Scriptを実行するpyと同じのDirectoryにいれてください。
\home\root\coral\pycoral\examples |
Step3:Making changes in the machine-learning application
Python scriptの実行結果をPLCNEXT CPUに転送するにはgRPC Communication を使用すると先ほども言いました。Scriptを開き、プログラムを少し修正します。
Importing modules
まずScriptの最初に必要なモジュールをImportします。
import grpc from google.protobuf.empty_pb2 import Empty from PROTO.Authentication_pb2 import CreateSessionRequest from PROTO.Authentication_pb2_grpc import AuthenticationStub from PROTO.IDataAccessService_pb2 import IDataAccessServiceReadSingleRequest, \ IDataAccessServiceReadRequest, IDataAccessServiceWriteSingleRequest, IDataAccessServiceWriteRequest from PROTO.IDataAccessService_pb2_grpc import IDataAccessServiceStub from PROTO.PLCnextBase_pb2 import CoreType from PROTO.ErrorCode_pb2 import ErrorCode |
Opening a gRPC channel
gRPCチャンネルを開きます。
channel=grpc.insecure_channel(“unix:/run/plcnext/grpc.sock”) |
Defining the function :
PLCNEXT CPUの変数を上書きする関数を作成します。
def WriteSingle_StringValue(P_PortName,P_StringValue): stub=IDataAccessServiceStub(channel) response1= IDataAccessServiceWriteSingleRequest() response1.data.PortName= P_PortName response1.data.Value.TypeCode=19 response1.data.Value.StringValue=P_StringValue response=stub.WriteSingle(response1) return response |
Writing the result into a variable
最後は判定結果をPLCNEXT CPUの変数に書き込みます。
result=(‘%s’ % (labels.get(c.id, c.id))) WriteSingle_StringValue(“Arp.Plc.Eclr/Main1.INT_A”, result) |
Full Example Code
こちらは実際、拡張されたPython Scriptです。
import grpc from google.protobuf.empty_pb2 import Empty from PROTO.Authentication_pb2 import CreateSessionRequest from PROTO.Authentication_pb2_grpc import AuthenticationStub from PROTO.IDataAccessService_pb2 import IDataAccessServiceReadSingleRequest, \ IDataAccessServiceReadRequest, IDataAccessServiceWriteSingleRequest, IDataAccessServiceWriteRequest from PROTO.IDataAccessService_pb2_grpc import IDataAccessServiceStub from PROTO.PLCnextBase_pb2 import CoreType from PROTO.ErrorCode_pb2 import ErrorCode #Opening a gRPC channel channel=grpc.insecure_channel(“unix:/run/plcnext/grpc.sock”) import argparse import time import numpy as np from PIL import Image from pycoral.adapters import classify from pycoral.adapters import common from pycoral.utils.dataset import read_label_file from pycoral.utils.edgetpu import make_interpreter def main(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( ‘-m’, ‘–model’, required=True, help=’File path of .tflite file.’) parser.add_argument( ‘-i’, ‘–input’, required=True, help=’Image to be classified.’) parser.add_argument( ‘-l’, ‘–labels’, help=’File path of labels file.’) parser.add_argument( ‘-k’, ‘–top_k’, type=int, default=1, help=’Max number of classification results’) parser.add_argument( ‘-t’, ‘–threshold’, type=float, default=0.0, help=’Classification score threshold’) parser.add_argument( ‘-c’, ‘–count’, type=int, default=5, help=’Number of times to run inference’) parser.add_argument( ‘-a’, ‘–input_mean’, type=float, default=128.0, help=’Mean value for input normalization’) parser.add_argument( ‘-s’, ‘–input_std’, type=float, default=128.0, help=’STD value for input normalization’) args = parser.parse_args() labels = read_label_file(args.labels) if args.labels else {} interpreter = make_interpreter(*args.model.split(‘@’)) interpreter.allocate_tensors() # Model must be uint8 quantized if common.input_details(interpreter, ‘dtype’) != np.uint8: raise ValueError(‘Only support uint8 input type.’) size = common.input_size(interpreter) image = Image.open(args.input).convert(‘RGB’).resize(size, Image.ANTIALIAS) # Image data must go through two transforms before running inference: # 1. normalization: f = (input – mean) / std # 2. quantization: q = f / scale + zero_point # The following code combines the two steps as such: # q = (input – mean) / (std * scale) + zero_point # However, if std * scale equals 1, and mean – zero_point equals 0, the input # does not need any preprocessing (but in practice, even if the results are # very close to 1 and 0, it is probably okay to skip preprocessing for better # efficiency; we use 1e-5 below instead of absolute zero). params = common.input_details(interpreter, ‘quantization_parameters’) scale = params[‘scales’] zero_point = params[‘zero_points’] mean = args.input_mean std = args.input_std if abs(scale * std – 1) < 1e-5 and abs(mean – zero_point) < 1e-5: # Input data does not require preprocessing. common.set_input(interpreter, image) else: # Input data requires preprocessing normalized_input = (np.asarray(image) – mean) / (std * scale) + zero_point np.clip(normalized_input, 0, 255, out=normalized_input) common.set_input(interpreter, normalized_input.astype(np.uint8)) # Run inference print(‘—-INFERENCE TIME—-‘) print(‘Note: The first inference on Edge TPU is slow because it includes’, ‘loading the model into Edge TPU memory.’) for _ in range(args.count): start = time.perf_counter() interpreter.invoke() inference_time = time.perf_counter() – start classes = classify.get_classes(interpreter, args.top_k, args.threshold) print(‘%.1fms’ % (inference_time * 1000)) print(‘——-RESULTS——–‘) for c in classes: print(‘%s: %.5f’ % (labels.get(c.id, c.id), c.score)) result=(‘%s’ % (labels.get(c.id, c.id))) WriteSingle_StringValue(“Arp.Plc.Eclr/Main1.INT_A”, result) def WriteSingle_StringValue(P_PortName,P_StringValue): stub=IDataAccessServiceStub(channel) response1= IDataAccessServiceWriteSingleRequest() response1.data.PortName= P_PortName response1.data.Value.TypeCode=19 response1.data.Value.StringValue=P_StringValue response=stub.WriteSingle(response1) return response if __name__ == ‘__main__’: main() |
Result
Scriptを実行します。
python3 examples/classify_image_plcnext.py \ –model test_data/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite \ –labels test_data/inat_bird_labels.txt \ —input test_data/parrot.jpg |
判定結果をINT_Aに書き込みました。それで成功!
Try more Fun!
それだけで終わってもよいですが、せっかくなのでもう1つのDEMOを作ります。
Reference Link
Download a new Model
下記のLINKからMobileNet V1 192x192x5の”Edge TPU modelとLabel fileをDownloadします。
https://coral.ai/models/image-classification/
Transfer to the CPU
Modelをテストするために.tfile、Label File、テストイメージを/home/root/coral/pycoral/test/data/ に移動します。
今回テストするのはToilet Paperです笑。
Run the Script
コマンドでScriptを実行します。
python3 examples/classify_image_plcnext.py –model test_data/mobilenet_v1_0.75_192_quant_edgetpu.tflite –labels test_data/imagenet_labels_q.txt —input test_data/test_image3.jpg |
判定するのはこの画像ですね。
Paper towelは79%くらいあるってことはOKですね!
Implementation
次は実際のデモを作ります。下図は今回の構成です。AXC F 3152はOPCUA Serverを立ち上げ、RoboDK側のPython OPCUA Clientと通信しHandshake情報を交換します(プログラム起動・Busy・Endなど)。
ロボットが撮影した写真をSFTP経由でAXF F 3152に送信します。
AXF F XT ML1000とAXC F 3152間はgRPCでHandshakeデータ(判定開始・結果・Busy・Endなど)を交換します。
Handshake Flow
こちらは今回のHandShake Flowになります。
AXC F3152がRoboDKにStart信号を送信します。
RoboDKがロボットプログラムを起動し、Busyを返し、その間撮影した写真をSFTP経由でAXC F3152に送信します。
AXCF 3152がRoboDKからEndのTrue信号をもらったらML1000にStartApps信号を送信します。ML1000はこの信号を受け、画像判定をはじめ、結果をAXF F 3152に書き込みます。ML1000のプロセスが完了したらAXF F 3152にEnd信号を送ります。AXC F3152がStartApps信号をリセットし、ML1000も完了です。
最後はAXC F3152がCanEnd信号をRoboDK送り、RoboDKがプロセスを完了します。そしてまた最初からのStart Signalを待つ状態になります。
PLCNEXT
OPCUA Server Setup
まずOPCUA serverをSetupします。
CertificateをSelf signed by controllerとSecurity Policiesを実際のClientが対応できるものに合わせてください。
DUT
DUT_ML1000
DUT_ML1000はPLCNEXTとML1000モジュールのHandShakreや交換データを定義します。
TYPE DUT_ML1000_ClassificationResult : STRUCT Label:STRING; Score:REAL; END_STRUCT DUT_TO_ML1000 : STRUCT bStart2Classification:BOOL; sImageName :STRING; END_STRUCT DUT_From_ML1000 :STRUCT bClientReady:BOOL; bTPUReady:BOOL; bBusy:BOOL; bFinished:BOOL; Results:ARRAy[1..6]of DUT_ML1000_ClassificationResult; Result_label:ARRAY[0..6]of string; Result_Score:ARRAY[0..6]of REAL; END_STRUCT END_TYPE |
DUT_RoboDK
DUT_RoboDKはPLCNEXTとRoboDKのHandShakreや交換データを定義します。
TYPE DUT_OPCUA_ReadFromRoboDK : STRUCT sImagePath:ARRAY[1..6]of string; bImageisSaved:BOOL; bBusy:BOOL; bEnd:BOOL; END_STRUCT DUT_OPCUA_Write2RoboDK : STRUCT bStartProgram:BOOL; bCanEnd :BOol; END_STRUCT DUT_OPCUA_Write2PI4 : STRUCT bStart2Classification:BOOL; sImageName :STRING; END_STRUCT END_TYPE |
Program
プログラムを作ります。
VAR
Main>Variables Tabで以下の変数を定義します。
PROGRAM
こちらはプログラムになります。Case文を使って先程書いたFlowの通り、HandShakeやデータ交換を行います。
case iStep of 0: ReadFromRoboDK.bBusy:=False; ReadFromRoboDK.bEnd:=False; ReadFromRoboDK.bImageisSaved:=False; Write2RoboDK.bCanEnd:=False; Write2RoboDK.bStartProgram:=False; for i:=1 to 6 DO ReadFromRoboDK.sImagePath[i]:=’ ‘; END_for; ReadFromML1000.bBusy:=False; ReadFromML1000.bClientReady:=False; ReadFromML1000.bTPUReady:=False; ReadFromML1000.bFinished:=False; Write2ML1000.bStart2Classification:=False ; Write2ML1000.sImageName:= ‘ ‘; iStep:=10; Started:=False; 10: if Write2RoboDK.bStartProgram and not Started THEN Started:=True; iStep:=20; end_if; 20: if ReadFromRoboDK.bBusy THEN Write2RoboDK.bStartProgram:=False; iStep:=30; for i:=1 to 6 DO ReadFromML1000.Result_label[i]:=’ ‘; ReadFromML1000.Result_Score[i]:=0.0; END_for; end_If; 30: if ReadFromRoboDK.bEnd THEN Write2ML1000.bStart2Classification:=True; iStep:=40; end_if; 40: if ReadFromML1000.bFinished THEN Write2RoboDK.bCanEnd:=True; Write2ML1000.bStart2Classification:=False; iStep:=50; end_if; 50: if not ReadFromRoboDK.bEnd then Write2RoboDK.bCanEnd:=False; iStep:=0; end_if; END_CASE; |
HMI Setup
PLCNEXT Enginnerで簡単なHMI Applicationを作成し、CPUにDownloadします。
StartRobotProgramはボタンで、それ以外はAll表示です。
ちなみにStartRobotProgramは先程書いたFlowの一番最初のStart 信号になります。
RoboDK Side
RoboDK のPython ScriptはReference Linkのプロジェクトから修正します。
from robodk import robolink # RoboDK API from robodk import robomath # Robot toolbox import datetime import time from opcua import Client,ua import random import os import paramiko SFTP_FROM=’C:\FTPFiles\RoboDK’ SFTP_TO=’/home/root/coral/pycoral/test_data/’ config = { “host” : “192.168.3.10”, “port” : 22, “username” : “SFTPUSER”, “password” : “SFTPPASSWORD” } SFTPClient=paramiko.SSHClient() SFTPClient.set_missing_host_key_policy(paramiko.AutoAddPolicy) os.chdir(r’C:\Users\root\Desktop\ML1000_package\ML1000_package’) RDK = robolink.Robolink() UR=RDK.Item(“UR5”) CameraPosition1=RDK.Item(‘tCamera1’) CameraPosition2=RDK.Item(‘tCamera2’) CameraPosition3=RDK.Item(‘tCamera3’) CameraPosition4=RDK.Item(‘tCamera4’) CameraPosition5=RDK.Item(‘tCamera5’) CameraPosition6=RDK.Item(‘tCamera6’) Prog=RDK.Item(‘pMain’) ENDPOINT=’opc.tcp://user:passwd@192.168.3.10:4840′ USERNAME=’PLCNEXTUSERNAME’ PASSWORD=’PLCNEXTPASSWORD’ NODE_BUSY=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.bBusy’ NODE_END=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.bEnd’ NODE_IMAGEISSAVED=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.bImageisSaved’ NODE_SIMAGEPATH=’ns=5;s=Arp.Plc.Eclr/Main1.ReadFromRoboDK.sImagePath’ NODE_STARTPROGRAM=’ns=5;s=Arp.Plc.Eclr/Main1.Write2RoboDK.bStartProgram’ NODE_CANEND=’ns=5;s=Arp.Plc.Eclr/Main1.Write2RoboDK.bCanEnd’ client=Client(ENDPOINT) client.set_user(USERNAME) client.set_password(PASSWORD) client.set_security_string(‘Basic256,Sign,cert.der,key.pem’) try: client.connect() client.load_type_definitions() print(‘Node2Server is loaded..’) NodeBusy=client.get_node(NODE_BUSY) NodeEnd=client.get_node(NODE_END) NodeImageIsSaved=client.get_node(NODE_IMAGEISSAVED) NodeSImagePath=client.get_node(NODE_SIMAGEPATH) NodeCanEnd=client.get_node(NODE_CANEND) print(‘NodeFromServer is loaded..’) NodeStartProgram=client.get_node(NODE_STARTPROGRAM) Busy=NodeBusy.get_value() End=NodeEnd.get_value() print(Busy,End) except Exception as e: print(e) quit() def WriteNode(): NodeBusy=client.get_node(NODE_BUSY) NodeEnd=client.get_node(NODE_END) NodeImageIsSaved=client.get_node(NODE_IMAGEISSAVED) NodeSImagePath=client.get_node(NODE_SIMAGEPATH) return NodeBusy.get_value(),NodeEnd.get_value(),NodeImageIsSaved.get_value(),NodeSImagePath.get_value() def ReadNode(): NodeStart=client.get_node(NODE_STARTPROGRAM) NodeCanEnd=client.get_node(NODE_CANEND) return NodeStart.get_value(),NodeCanEnd.get_value() cam_item = RDK.Item(“Camera1”) if not cam_item.Valid(): print(‘No camera..’) quit() def createImagePath(s): path=getCurrentTime() return “C:\FTPFiles\RoboDK”+path+”.jpg”,path def getCurrentTime(): return time.strftime(‘%Y_%m_%d_%H_%M_%S_’+str(random.randint(0,99))) def Sanp(Path,Cam): return RDK.Cam2D_Snapshot(Path,Cam) def SetNodeValue(Node,value): DataType=Node.get_data_type_as_variant_type() DataValue=ua.DataValue(ua.Variant(value,DataType)) Node.set_data_value(DataValue) NodeSImagePath=client.get_node(NODE_SIMAGEPATH) flag=[False,False,False,False,False,False] photoTaken=[False,False,False,False,False,False] counts=0 start=0 End=False _busy=False try : while True: Busy,End,ImageIsSaved,Path=WriteNode() bStartProgram,bCanEnd=ReadNode() Pose=UR.Pose() if not _busy and bStartProgram : _busy=True PathArray=[] value = Prog.RunProgram() End=False if Prog.Busy(): NodeBusy.set_value(ua.DataValue(True)) if Pose == CameraPosition1.Pose() and not flag[0]: path,p=createImagePath(“001”) ret=Sanp(path,cam_item) print(‘Camera pos1..’) if ret: flag[0]=True PathArray.append(p) UR.setDO(‘CamIO_1’,1) elif Pose == CameraPosition2.Pose()and not flag[1]: path,p=createImagePath(“002”) ret=Sanp(path,cam_item) print(‘Camera pos2..’) if ret: UR.setDO(‘CamIO_2’,1) PathArray.append(p) flag[1]=True elif Pose == CameraPosition3.Pose()and not flag[2]: path,p=createImagePath(“003”) ret=Sanp(path,cam_item) print(‘Camera pos3..’) if ret: UR.setDO(‘CamIO_3’,1) PathArray.append(p) flag[2]=True elif Pose == CameraPosition4.Pose()and not flag[3]: path,p=createImagePath(“004”) ret=Sanp(path,cam_item) print(‘Camera pos4..’) if ret: UR.setDO(‘CamIO_4’,1) PathArray.append(p) flag[3]=True elif Pose == CameraPosition5.Pose()and not flag[4]: path,p=createImagePath(“005”) ret=Sanp(path,cam_item) print(‘Camera pos5..’) if ret: UR.setDO(‘CamIO_5’,1) PathArray.append(p) flag[4]=True elif Pose == CameraPosition6.Pose()and not flag[5]: path,p=createImagePath(“006”) ret=Sanp(path,cam_item) print(‘Camera pos6..’) if ret: UR.setDO(‘CamIO_6’,1) PathArray.append(p) flag[5]=True if flag[0] and flag[1] and flag[2] and flag[3] and flag[4] and flag[5]: SFTPClient.connect(config[‘host’], port = config[‘port’],username = config[‘username’],password = config[‘password’]) sftp_con = SFTPClient.open_sftp() for i in range(0,6): sftp_con.put(“C:\FTPFiles\RoboDK”+PathArray[i]+”.jpg”, “/home/root/coral/pycoral/test_data/”+PathArray[i]+”.jpg”) SFTPClient.close() SetNodeValue(NodeSImagePath,PathArray) NodeEnd.set_value(ua.DataValue(True)) End=True start=time.time() else: NodeBusy.set_value(ua.DataValue(False)) PathArray=[] for i in range(0,6): flag[i]=False if End: print(‘NodeEnd=True..’) NodeEnd.set_value(ua.DataValue(True)) if time.time()-start > 2 and False : print(‘NodeEnd=False..’) NodeEnd.set_value(ua.DataValue(False)) if bCanEnd: print(‘NodeEnd=False–Cam End..’) NodeEnd.set_value(ua.DataValue(False)) _busy=False except Exception as e: print(e) client.disconnect() |
Point1:SFTP Configuration
SFTP Server(今回はPLCNEXT AXC F3152)のIP・Username・Passwordを実機に合わせてください。
Point2:cert.der/key.pem Location
こちらはAXCF 3152 OPCUA Serverにアクセスするときに使用するCertとKeyの場合に合わせる必要があります。
Point3:OPC-UA ENDPOINT
PLCNEXTの実機に合わせてENDPOINT・Username・Passwordを設定してください。
Point4: OPCUA Endpoint/username/Password/Security
OPCUA ENDPOINT(今回はAXCF 3152)をあわせてUser/Password/Securityに合わせてください。
ML1000 Python Script
最後は先程の画像判定Scriptですね。注意するのは前のScritpから–InputのArgあり、Python実行するときにパラメタを入力する必要がありますが、いまは直接ImageをFile Systemから読み込むので、Argの部分を削除します。RoboDKから画面するとき、画面の名前も一緒に送りますので、Pythonはその画面の名前を沿って画面を読み込むだけです。
import grpc from google.protobuf.empty_pb2 import Empty from PROTO.Authentication_pb2 import CreateSessionRequest from PROTO.Authentication_pb2_grpc import AuthenticationStub from PROTO.IDataAccessService_pb2 import IDataAccessServiceReadSingleRequest, \ IDataAccessServiceReadRequest, IDataAccessServiceWriteSingleRequest, IDataAccessServiceWriteRequest from PROTO.IDataAccessService_pb2_grpc import IDataAccessServiceStub from PROTO.PLCnextBase_pb2 import CoreType from PROTO.ErrorCode_pb2 import ErrorCode #Opening a gRPC channel channel=grpc.insecure_channel(“unix:/run/plcnext/grpc.sock”) import argparse import time import numpy as np from PIL import Image from pycoral.adapters import classify from pycoral.adapters import common from pycoral.utils.dataset import read_label_file from pycoral.utils.edgetpu import make_interpreter def WriteSingle_StringValue(P_PortName,P_StringValue): stub=IDataAccessServiceStub(channel) response1= IDataAccessServiceWriteSingleRequest() response1.data.PortName= P_PortName response1.data.Value.TypeCode=19 response1.data.Value.StringValue=P_StringValue response=stub.WriteSingle(response1) return response def WriteSingle_BoolValue(P_PortName,value): stub=IDataAccessServiceStub(channel) response1= IDataAccessServiceWriteSingleRequest() response1.data.PortName= P_PortName response1.data.Value.TypeCode=2 response1.data.Value.BoolValue=value response=stub.WriteSingle(response1) return response def read_single_value(stub, port_name): single_read_request = IDataAccessServiceReadSingleRequest() single_read_request.portName=port_name return stub.ReadSingle(single_read_request) def read_multiple_values(stub, port_names): read_request = IDataAccessServiceReadRequest() read_request.portNames.extend(port_names) return stub.Read(read_request) def write_single_Stringarray(stub, port_name, value,index): single_write_request = IDataAccessServiceWriteSingleRequest() single_write_request.data.PortName = port_name+'[‘+str(index)+’]’ single_write_request.data.Value.TypeCode = 19 single_write_request.data.Value.StringValue=value return stub.WriteSingle(single_write_request) def write_single_Realarray(stub, port_name, value,index): single_write_request = IDataAccessServiceWriteSingleRequest() single_write_request.data.PortName = port_name+'[‘+str(index)+’]’ single_write_request.data.Value.TypeCode = 13 single_write_request.data.Value.FloatValue=value return stub.WriteSingle(single_write_request) #TPU Path setup LABELPATH=’/home/root/coral/pycoral/test_data/imagenet_labels_2.txt’ MODELPATH=’/home/root/coral/pycoral/test_data/mobilenet_v1_0.75_192_quant_edgetpu.tflite’ top_k=2 threshold=0.0 count=2 input_mean=128 input_std=128 DEBUG=False PORTS=[‘Arp.Plc.Eclr/Main1.ReadFromRoboDK.sImagePath’] WORKING_DIR=’/home/root/coral/pycoral/test_data/’ def main(): labels = read_label_file(LABELPATH) if LABELPATH else {} interpreter = make_interpreter(*MODELPATH.split(‘@’)) interpreter.allocate_tensors() # Model must be uint8 quantized if common.input_details(interpreter, ‘dtype’) != np.uint8: raise ValueError(‘Only support uint8 input type.’) size = common.input_size(interpreter) params = common.input_details(interpreter, ‘quantization_parameters’) scale = params[‘scales’] zero_point = params[‘zero_points’] mean = input_mean std = input_std bInit=False bBusy=False stub=IDataAccessServiceStub(channel) step=0 #————————————————————- while True: PathArray=[] bStart2Classification = read_single_value(stub, ‘Arp.Plc.Eclr/Main1.Write2ML1000.bStart2Classification’)._ReturnValue.Value.BoolValue bPaths=read_multiple_values(stub, PORTS) if bStart2Classification and step ==0 : print(‘command is received.’) bBusy=True WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bBusy”, True) if not bInit: bInit=True Paths=bPaths._ReturnValue[0].Value.ArrayValue for path in Paths.ArrayElements: PathArray.append(path.StringValue) print(path.StringValue) step=10 # Run inference if step==10: i=0 for path in PathArray: image = Image.open(WORKING_DIR+path+”.jpg”).convert(‘RGB’).resize(size, Image.ANTIALIAS) if abs(scale * std – 1) < 1e-5 and abs(mean – zero_point) < 1e-5: # Input data does not require preprocessing. common.set_input(interpreter, image) else: # Input data requires preprocessing normalized_input = (np.asarray(image) – mean) / (std * scale) + zero_point np.clip(normalized_input, 0, 255, out=normalized_input) common.set_input(interpreter, normalized_input.astype(np.uint8)) print(‘—-INFERENCE TIME—-‘) print(‘Note: The first inference on Edge TPU is slow because it includes’, ‘loading the model into Edge TPU memory.’) for _ in range(count): start = time.perf_counter() interpreter.invoke() inference_time = time.perf_counter() – start classes = classify.get_classes(interpreter, top_k, threshold) print(‘%.1fms’ % (inference_time * 1000)) print(‘——-RESULTS——–‘) write=False for c in classes: print(‘%s: %.5f’ % (labels.get(c.id, c.id), c.score)) if write is False: result=labels.get(c.id, c.id) score=c.score write=True write_single_Stringarray(stub,’Arp.Plc.Eclr/Main1.ReadFromML1000.Result_label’,result,i) write_single_Realarray(stub,’Arp.Plc.Eclr/Main1.ReadFromML1000.Result_Score’,score,i) print(i) i=i+1 result=(‘%s’ % (labels.get(c.id, c.id))) WriteSingle_StringValue(“Arp.Plc.Eclr/Main1.INT_A”, result) step=20 if step ==20: bBusy=False WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bBusy”, False) WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bFinished”, True) if not bStart2Classification and step==20: WriteSingle_BoolValue(“Arp.Plc.Eclr/Main1.ReadFromML1000.bFinished”, False) step=0 bInit=False if __name__ == ‘__main__’: main() |
Point1:Label/tfilte Location
このScriptはPython Scriptが実行するときにパラメータを入力しないようになっています。なので、Model FileとLabel Fileなどを設定してください。
Point2; The Image location
RoboDKから転送した画像のDirectoryを実際に合わせましょう。
Result
Download the Project
下記のLinkでProjectをDownloadしてください。
https://github.com/soup01Threes/PLCNEXT/blob/main/Demo_PLCNEXT_DEMO_ML1000_ROBODK.zip
- cert.der,key.pemはPLCNEXT AXC F3152のOPCUA ServerにLoginするとき使用するKey証明書です。
- Demo_RoboDK_PLCNEXT_Image.rdkはRoboDK Projectです。
- imagenet_labels_2.txtはLabel Fileです。
- mobilenet_v1_0.75_192_quant_edgetpu.tfiteはModel Fileです。
- image_classification_plcnext_robodk.pyは画像判定するScriptです。
- robodk_script.pyはRoboDK Project内のPython Scriptです。
- PLCNEXT_DEMO_ML1000_ROBODK.pcweaxはPLCNEXT Projectです。