みなさんこんにちは。Beckhoff TwinCAT3 TF7xxxの第2話です。今回はContainerのコンセプトについてもう少し深く説明します。ContainerはTc3_Visionの中の大事な部品なのでコンセプトをしっかり理解する必要があり、QRCodeやBarCode読み取るからObject Detectなどのアプリケーションまでも、絶対Containerを使いますので。
みなさんが最後までお付きいただけると嬉しいです。
前回の記事:
Beckhoff#TwinCAT3 TF7000 VisionでQRCode読み込んでみよう | (soup01.com)
Functions
Containerを説明する前に、まずVisionのFunctionsについてもう少し説明しますね。前回もある程度書いてますが、今回は復習しますね。関数は以下のFormatになります:
hr
Functionの実行結果が格納されます。
F_VN_
Visionの関数はF_VN_から始まります。
Parameter<..>
FunctionのParameterは必ずすべて入れてください。Parameterは以下の種類あります:
- Input Parameter:ImagesやContainer
- アルゴリズムの設定パラメタ
- 戻り値
Expert Functions
Tc3_VisionにF_VN_xxx_Expのような名前の関数で(例えばF_VN_MaxImageとF_VN_MaxImageExp)、Expがついてる関数は必要のパラメタも多くなり、できることも多くなります。
Define the Container
まずcontainerを定義します。そのcontainerは増設できる、形はいつでも壊して作り直せるとの箱だと思ってください。が、増設するときの素材(いわゆる変数のタイプ)は同じにする必要があります。
LoginしMonitorしたら2つのContainerも16#0格納されていますね。
F_VN_CreateContainer
次は関数を使用しContainerを作ります。ここをみるとちょっと混乱する読者もいると思いますが、先のStepではあくまでもこのようContainerがありますよと定義するだけで中は空っぽです。このF_VN_CreateContainer関数を使用することで、Containerをどういう形になるかを作ります。
関数に渡してたパラメータ(nTypeGuidなど)はこのContainerを作るときの仕様だと思ってください。
LoginしMonitorしたらipContainerは16#0ではなく、16#xxxxxxxの値が格納されています。
その関数はどんなContainerで作られたのでしょうか。ContainerType_Vector_REALは実数タイプの変数ですね。そしてnElementNum=10なので、10個の実数だと。
なので、下図のようになります。
Function Block
VAR_INPUT
ipContainer | ITcVnContainer | 実数 Elementsが含まれてるContainer |
nTypeGid | GUID | ContainerのElement Data Type |
nElementNum | ULINT | いくつElementsを設定する |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_CreateContainer | HRESULT | 実行結果 |
Code
//Create a Container IF bcreateContiner THEN hr:=F_VN_CreateContainer( ipContainer:=ipContainer ,nTypeGuid:=ContainerType_Vector_REAL ,nElementNum:=10 ,hrPrev:=hr ); IF SUCCEEDED(hr) THEN bcreateContiner:=FALSE; END_IF END_IF |
Adding Element
この部分はContainer内のElement操作です。
F_VN_AppendToContainer_REAL
次は生成されたContainerに新しいElementを追加します。Containerの操作は配列と似てると思ってください。このF_VN_AppeendToContainer_REAL関数は新しいElementを1番最後に追加します。
Function Block
VAR_INPUT
fElement | Reference To REAL | 該当するElement現在値 |
ipContainer | ITcVnContainer | 実数 Elementsが含まれてるContainer |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_AppendToContainer_REAL | HRESULT | 実行結果 |
F_VN_InsertIntoContainer_REAL
今度は同じくElementを追加するのですが、Indexを指定し特定の場所に插入します。F_VN_InsertIntoContainer_REAL関数を使用します。下記のようにIndex=0なら1番先頭に新しいElementを追加することになります。前も書いていますが、操作は配列とすごく似ています。
Function Block
VAR_INPUT
fElement | Reference To REAL | InsertするElement値 |
ipContainer | ITcVnContainer | 実数 Elementsが含まれてるContainer |
nIndex | ULINT | 插入したいElementのIndex |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_InsertIntoContainer_REAL | HRESULT | 実行結果 |
Code
IF bAddElement AND ipContainer <>0 THEN hr := F_VN_AppendToContainer_REAL(1.23, ipContainer, hr); hr := F_VN_AppendToContainer_REAL(2.23, ipContainer, hr); hr := F_VN_InsertIntoContainer_REAL(FrealTest, ipContainer, 0, hr); IF SUCCEEDED(hr) THEN ii:=ii+1; END_IF bAddElement:=FALSE; END_IF |
Accessing Element
F_VN_GetAT_XXとF_VN_SetAt_XXという関数を使ってContainer内のElementを操作することができます。注意するのはContainerのIndexは0からStartで、XXはあなたのContainer含まれているData Tyoeにより変わりまので、このあたりはManualに参照してください。
F_VN_GetAt_REAL
Container内に指定するElementの現在値を取得したい場所はF_VN_GetAt_REAL関数を追加します。下図の例ではnIndex=5なら5番目のElementの現在値を取得できます。
Function Block
VAR_INPUT
ipContainer | ITcVnContainer | 実数 Elementsが含まれてるContainer |
fElement | Reference To REAL | 該当するElement現在値 |
nIndex | ULINT | 取得したいElementのIndex |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_GetAt_REAL | HRESULT | 実行結果 |
F_VN_SetAt_REAL
同じ考え方で指定のElementの値をセットすることもできます。下記の例だと3番目のElement現在値をfVarablesに変更します。
Function Block
VAR_INPUT
fElement | Reference To REAL | 該当するElementの与え値 |
ipContainer | ITcVnContainer | 実数 Elementsが含まれてるContainer |
nIndex | ULINT | 書き込みしたいElementのIndex |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_SetAt_REAL | HRESULT | 実行結果 |
Code
IF ipContainer <> 0 THEN hr:=F_VN_GetAt_REAL( ipContainer:=ipContainer ,fElement:=fElement ,nIndex:=myIndex ,hrPrev:=hr ); END_IF IF ipContainer <>0 AND bset3 THEN hr:=F_VN_SetAt_REAL( fElement:=fVarables ,ipContainer:=ipContainer ,nIndex:=3 ,hrPrev:=hr ); bSet3:=FALSE; END_IF |
Looping
先の説明では1LineのプログラムだけでContainerの指定Elementにアクセスすることができます(もちろんContainer Typeは一致とIndexは範囲内)。もしContainerのElementをすべてアクセスしたい場合、iteratorsを使ったほうがパフォーマンスがよいので。
今回はExample内で使われてる関数やInterfaceを説明します。
ITcVnForwardIterator
Offers an interface for a forward iterator.
Methods – TcQueryInterface
このMethodからIntefaceのQuery を取得することができます。このQueryを使用すればLoopingなどの機能も実装できるようになります。
HRESULT TcQueryInterface(RITCID iid, PPVOID pipItf )
VAR_INPUT
iid | RITCID | Interface IID |
pipItf | PPVOID | interface pointer |
Return Value
実行に成功したらS_OK(0)や他の正の整数が戻ります。
HRESULT | 実行結果 |
Methods – CheckIfEnd
いまのiteratorが最後かどうかをCheckします。
F_VN_CheckIfIteratorIsAtEnd()に代用できます。
Methods – Increment
iteratorを足します。(つまり次の部品に回します)
F_VN_IncrementIterator()に代用できます。
ITcVnAccess_REAL
実数の変数にアクセスできるInterfaceです。
Methods – Get
現在値を取得します。
Methods – Set
新しい値を格納します。
Flow
こちらはLoopingのSample Flowです。
Program
//Get From Loop IF bGet THEN hr := F_VN_GetForwardIterator(ipContainer, ipIterator, hr); index:=0; IF SUCCEEDED(hr) AND ipIterator <> 0 THEN hr := ipIterator.TcQueryInterface(IID_ITcVnAccess_REAL, ADR(ipAccess)); IF SUCCEEDED(hr) AND ipAccess<>0 THEN WHILE SUCCEEDED(hr) AND ipIterator.CheckIfEnd() <>S_OK DO hr:=ipAccess.Get(fValue:=fElement); IF SUCCEEDED(hr) THEN fElement:=fElement+1; hr:=ipAccess.Set(fValue:=fElement); END_IF IF SUCCEEDED(hr) THEN arrReal[index]:=fElement; hr:=ipIterator.Increment(); index:=index+1; END_IF END_WHILE END_IF hr:=FW_SafeRelease(ADR(ipAccess)); END_IF bGet:=FALSE; hr:=FW_SafeRelease(adr(ipIterator)); bGet:=FALSE; END_IF |
+in Python
ここまで説明すると、かなり複雑に見えますが、Pythonで書くと単なる以下の3Linesですねー
elements=[4,1,2,4,5,7,8,1,2,66,224,4] for element in elements: #Do you stuff |
Export Container as Array
次はContainerから配列を出力するときに使える関数です。
F_VN_ExportContainerSize
Containerの部品が占有しているメモリサイズを取得できます。
VAR_INPUT
ipContainer | ITcVnContainer | Basic Elementが含まれているContainer |
nBufferSize | ULINT | Containerからの出力先メモリサイズ |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_SetAt_REAL | HRESULT | 実行結果 |
F_VN_ExportContainer
この関数はContainerをData bufferにExportすることができます。必ずBufferSizeの容量を確認してください。F_VN_ExportContainerSize()関数で必要なMemoryが取得できます。
VAR_INPUT
ipContainer | ITcVnContainer | Basic Elementが含まれているContainer |
pBuffer | PVOID | Containerからの出力先変数メモリアドレス |
nBufferSize | ULINT | Containerからの出力先メモリサイズ |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_SetAt_REAL | HRESULT | 実行結果 |
Code
//Export it IF bExport THEN nBufferSize:=0; FOR i:=0 TO 99 DO arrReal[i]:=0.0; END_FOR hr:=F_VN_ExportContainerSize( ipContainer:=ipContainer ,nBufferSize:=nBufferSize ,hrPrev:=hr ); hr:=F_VN_ExportContainer( ipContainer:=ipContainer ,pBuffer:=ADR(arrReal) ,nBufferSize:=nBufferSize ,hrPrev:=hr ); bExport:=FALSE; END_IF |
Container In Container
最後はContainerの中にさらにContainerを格納するプログラムです。ではいままで説明したContainerのコンセプトを見てみましょう。
一つのCotainerが配列のように、データ・タイプが固定で中に自由自在に部品を数を設定できます。
もしContainerの中にさらにContainerがあると、どうなるでしょうか。
下図のようなイメージです。
ContainerがContainerのままで中に配列が格納され、Data Typeは一致します。そしてContainerの中に5つのSub-Containerが入っています。
面白いのはここです。各SubContainerには更に配列が格納されてます。その配列には実数の部品が入っています。その下図は固定ではなく、Container0は6個あり、Container1は4個あるようなFlexibilityの構成ができます。
このような構造はどこで使えるの?と疑問を持ってる人がいるかもしれません。例えば画像内で複数のObjectを検出するとき、当然1つの画像に複数のObjectを検出するのは普通のことです。そのとき1つめのObject情報はContainer0番、2つめのObject情報はContainer1番、そしてObjectの種類によって格納する情報数も違いますよね。
そのために、Container In Containerはよく使われてると思います。
以下は今回Example CodeからContainer In ContainerのFlowになります。他の作り方もたくさんありますので、これはあくまで参考になります。
F_VN_AppendToContainer_ITcVnContainer
シングルのContainerを一番最後に插入します。ipElement とipContainerはSame TypeID必要です。
Function block
VAR_INPUT
ipElement | ITcVnContainer | 插入したいSignle Container |
ipContainer | ITcVnContainer | 插入されたいContainer |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_AppendToContainer_ITcVnContainer | HRESULT | 実行結果 |
F_VN_ReserveContainerMemory
Containerのメモリを先に確保する関数です。
VAR_INPUT
ipContainer | ITcVnContainer | メモリ予約するContainer |
nElement | ULINT | そのContainerにあるElementの数予定 |
hrPrev | HRESULT | 前回実行結果 |
Return Value
F_VN_AppendToContainer_ITcVnContainer | HRESULT | 実行結果 |
Code
IF bSubContainerExample THEN hr := F_VN_CreateContainer(ipContainerBase, ContainerType_Vector_Vector_REAL, 0, hr); hr := F_VN_ReserveContainerMemory(ipContainerBase, TO_ULINT(cNumberOfSubContainers), hr); FOR i:=0 TO (cNumberOfSubContainers-1) DO hr := F_VN_CreateContainer(ipHelper, ContainerType_Vector_REAL, TO_ULINT(aContainerStructure[i]), hr); hr := F_VN_AppendToContainer_ITcVnContainer(ipHelper, ipContainerBase, hr); END_FOR bSubContainerExample:=FALSE; END_IF IF ipContainerBase <> 0 THEN F_VN_GetNumberOfElements( ipContainer:=ipContainerBase ,nNumberOfElements:=nNumberOfElementsInSubContainter ,hrPrev:=hr ); hr := F_VN_GetForwardIterator(ipContainerBase,ipIterator,hr); hr := ipIterator.GetContainer(ADR(ipSubContainer)); IF ipSubContainer <> 0 AND SUCCEEDED(hr) THEN iOpeartionCounter[9]:=iOpeartionCounter[9]+1; hr:=F_VN_GetForwardIterator(ipSubContainer,ipIteratorInSubContainer,hr); IF SUCCEEDED(hr) AND ipIteratorInSubContainer <> 0 THEN iOpeartionCounter[10]:=iOpeartionCounter[10]+1; ipIteratorInSubContainer.GetValueSize((nBufferSize)); ipIteratorInSubContainer.TcQueryInterface( iid:=IID_ITcVnAccess_REAL ,ADR(ipAccessSubContainer) ); IF SUCCEEDED(hr) AND ipAccessSubContainer<>0 THEN iOpeartionCounter[11]:=iOpeartionCounter[11]+1; ipAccessSubContainer.Get(fValue:=fElement); ipAccessSubContainer.Set(fValue:=2); ipAccessSubContainer.Get(fValue:=fElement); END_IF END_IF END_IF END_IF |
Sample Code Download
TwinCAT3/TwinCAT Project_Vision_Container.tnzip at main · soup01Threes/TwinCAT3 (github.com)
Summary
今回はF_VN_AppendToContainer_REAL()、F_VN_InsertIntoContainer_REAL()、F_VN_GetAt_REAL()、F_VN_StAt_REAL()でContainerに操作しましたが、もしContainerの部品はINTならF_VN_AppendToContainer_INT()がありますし、DINTならF_VN_AppendToContainer_DINT()の関数があります。
それは最初にいってた自由自在で、箱の形を自由に設定できるのと同じです。F_VN_AppendToContainer_REAL()を使用することで箱の入れ物も自由に組み立てることができます。そのあたりはManual調べてください。(使い終わったPointerを必ずリリースしてください。)
今回の例では実数のContainerベースで説明しましたが、F_VN_CreateContainer()関数でGuidを設定することにより違うData TypeのContainerを作成することができます。それはContainerの強みで、事前に”実数 Type”のContainer、”整数 Type”のContainerなどを定義しなくてもよく、一つのContainerを使い回すことができます。下図は設定できるGUIDです。