補助要素および帰無仮説を使用したFlexiLayoutの単純化
この方法は、複数の要素で同じ大きな検索制限を確認しなければいけない時に使用できます。制限が確認され、複数の補助要素で計算が1回だけ実行されるという考え方が裏にあります。制限を確認した結果として、所定の要素が検索されるものであってもなくても、発見されたまたは発見されなかった仮説は、制限が満たされているか否かを知らせる指標です。この補助要素は、DontFind()関数(「検索停止」)がアクティブになっていないことを条件として、画像で常に何かを検出できるように作成されます。仮説ツリーの拡張およびFlexiLayoutの合致の時間延長を防ぐため、仮説の数は大きすぎてはいけません。これを実行するために、例えば、オブジェクト収集または段落の種類の要素を使用できます。これらの要素は、検索領域から指定された種類のすべてのオブジェクトを含む単一の仮説を常に生成します。ダミー要素の高度な検索前関係セクションで、プロジェクトツリーのダミー要素の下にある要素を確認する条件を書きます。すべての条件が満たされている場合は、ダミー要素のDontFind()関数を呼び出します。この場合、ダミー要素の帰無仮説が生成され、他の要素の検索が始まる時に、すべての条件が満たされたことを示す標識として機能します。これを行うことで、要素の同じ大きな制限を確認することなく、ダミー要素のIsNull確認のみを実行するよう指示します。
注意:この方法で、コードは記述的になります。また、制限を編集する必要がある場合は、ダミー要素の記述のみで行うことができます。これでコードの複製時に論理的および構文的なエラーが発生しにくくなります。
注意:将来のバージョンでは、グループのサブ要素の領域で変数の作成をサポートする予定です。制限の確認結果は、異なる変数の値に格納されます。現在の方法は一時的なソリューション(workaround)で、FlexiLayout Studioの現行バージョンの高度セクションでコードが簡略化されます。
この方法がどのように作用するかをプロジェクト1.fspで見ましょう(フォルダ%public%\ABBYY\FlexiCapture\12.0\Samples\FLS\Tips and Tricks\Auxiliary element)。
これらの画像では、以下のフィールドの検索を行います:「請求書番号」、「請求日」、「企業名」、「企業所在地」。
2つの異なる種類の請求書を処理するとします:
- フィールド「企業名」と「企業所在地」は、フィールド「請求書番号」の上にあります;
- フィールド「企業名」と「企業所在地」は、フィールド「請求書番号」の下にあります。
画像の通り、フィールド「企業名」と「企業所在地」には名前がなく、その名前に頼って日付フィールドを探す標準的な手順が使えません。でも特定のパターンに気づきます: 日付フィールドが請求書番号の右にある場合、企業名と所在地はフィールド「請求書番号」の下にあります(ページ1および3)。日付フィールドが請求書番号フィールドの下にある場合、企業の詳細情報はフィールド「請求書番号」の上にあります(ページ2および4)。
検出されたパターンを考慮し、最初にフィールド「請求書番号」と「請求日」の場所を検索することが最善です。次に、この2つに頼って、相互位置を指定し、他のフィールドを検出します。
グループ要素InvoiceGroupを作成し、そこには要素InvoiceHeader、InvoiceNum、DateHeader、グループ要素DateGroupが含まれます。これらのサブ要素は、フィールド名とフィールド「請求書番号」と「請求日」を検出するために必要です。
注意:データ検索方法の詳細は質の低い事前認識での日付の検出をご覧ください。ここでは、現在のプロジェクトでの日付検索の制限のみ扱います。
画像の日付フィールドには必ずしも名前が付いているわけではないことがわかります。そのため、日付フィールドを検索する時に2つの制限セットを指定する必要があります: 名前が検出された場合および名前が検出されなかった場合(特殊なケースとしては、名前がページに存在するものの、例えば、ノイズが原因で検出されていないことがあります)。
if not DateHeader.IsNull then
{ RightOf: DateHeader.Rect.Right;
Below: DateHeader.Rect.Top - 30dt;
Above: DateHeader.Rect.Bottom + 30dt;
}
else
{ RectArray ar;
Let ar1 = Rect (InvoiceNum.Rect.Right, InvoiceNum.Rect.Top-30dt, PageRect.Right, InvoiceNum.Rect.Bottom + 30dt);
Let ar2 = Rect (InvoiceHeader.Rect.Left, InvoiceHeader.Rect.Bottom, InvoiceHeader.Rect.Right + 300dt, InvoiceHeader.Rect.Bottom + 150dt);
ar = RectArray (ar1);
ar.Add (ar2);
RestrictSearchArea (ar);
}
日付フィールドの名前が検出された(制限if not DateHeader.IsNullが確認された)場合、日付フィールドの名前に関連して検索が行われます: 名前の右、1つの横水準、縦ズレのエラーの余裕あり:
{ RightOf: DateHeader.Rect.Right;
Below: DateHeader.Rect.Top - 30dt;
Above: DateHeader.Rect.Bottom + 30dt;
}
そうでないと検索領域は2つの矩形に分割されます: 請求書の右、請求書番号と同じ水準、請求書フィールドの下。
注意:わかりやすいよう、フィールド「請求書番号」とその名前が常に検出される質の良い画像があるとします。実際の状況では、これらの要素のプロパティを呼び出す前に、そのIsNull確認を実行します。要素が検出されない場合、対応する要素の検索領域でさらなる検索が実行されるためです。
要素日付の高度な検索後関係セクションで、以下のコードを書きました:
if (DateHeader.IsNull) and (not IsNull) then
{if (not InvoiceHeader.IsNull) then
{ FuzzyQuality: Rect.Left - InvoiceHeader.Rect.Right, {0, 0, 0, 50000}*dt;
FuzzyQuality: Rect.Top - InvoiceHeader.Rect.Bottom, {-50000, 0, 0, 50000}*dt;
}
if (not InvoiceNum.IsNull) then
{ FuzzyQuality: Rect.Left - InvoiceNum.Rect.Right, {0, 0, 0, 50000}*dt;
FuzzyQuality: Rect.Top - InvoiceNum.Rect.Bottom, {-50000, 0, 0, 50000}*dt;
}
}
これによって、日付フィールドの名前が検出されない場合に、日付仮説の質に対し、請求書フィールドの名前からフィールド「請求書番号」自体までの距離に応じて、影響を及ぼすことができます。指定されたフィールドまでの距離が長いほど、対応する仮説に対するペナルティが大きくなります。すなわち、フィールド「請求書番号」に最も近い日付フィールドを検索することになります。
注意:これらの関数の使用の詳細は要素の検索にNearestおよびFuzzyQualityを使用をご覧ください。
要素DateAsStringで同じ検索制限を指定しますが、高度な検索後関係セクションで、上記のコードにもう1行追加します:
FuzzyQuality: 600dt - Width, {0, 0, 0, 50000}*dt;
長いアルファベットの文字列の仮説が短い方の仮説よりも好ましくなるよう、この行は日付の検索時に必要です。
また、要素DateAsStringの検索制限タブで、日付が文字列として検索される場合、要素InvoiceNumの領域は除外される、と指定しました。これは、わかりやすいよう、要素日付の高度な検索前関係セクションで指定した検索制限と同じ検索制限を、要素DateAsStringで重複させないことにしたためです。検索領域をRestrictSearchArea(Date.Rect);として指定しました。そのため、要素日付のあいまい矩形の領域で要素DateAsStringのオブジェクトが検索されるよう、指示しました。要素日付の検索領域は、矩形の配列として表現できます。帰無仮説が要素日付で生成される時、検索領域を囲む矩形は現在の要素の矩形(Rect)と見なされます。以下の画像の通り、要素InvoiceNumで記述されたフィールド「請求書番号」も囲んでいます。複数の条件の下で(データフィールドのノイズが多いなど)、日付の代わりに要素DateAsStringが請求書番号のフィールドを検出する時、この要素で指定された文字(数字含む)にはいかなる形式制限もないために、このような状況が起こり得ます。
フィールド「請求書番号」と「請求日」の検索に必要な要素が記述された後、フィールド「企業名」と「企業所在地」の検索に進むことができます。
種類段落の要素を作成して名前をShamElementと付けます。この要素は補助要素として機能し、フィールド「請求書番号」と「請求日」の相互位置の確認にのみ使用されます。
補助要素の高度な検索前関係セクションで、以下のコードを書きました:
Let Date1 = InvoiceGroup.DateGroup.Date;
Let Date2 = InvoiceGroup.DateGroup.DateAsString;
Let DateGroup = InvoiceGroup.DateGroup;
Let InvoiceHeader = InvoiceGroup.InvoiceHeader;
Let InvoiceNumber = InvoiceGroup.InvoiceNum;
if ((not InvoiceHeader.IsNull) or (not InvoiceNumber.IsNull)) and
(
((Date1.IsNull == FALSE) and ((max (InvoiceHeader.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt) or (max (InvoiceNumber.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceNumber.Rect.YCenter ) < 30dt)) and ((Date1.Rect.Left - InvoiceHeader.Rect.Right > 100dt) or (Date1.Rect.Left - InvoiceNumber.Rect.Right > 50dt)))
or
((Date2.IsNull == FALSE) and ((max (InvoiceHeader.Rect.YCenter - Date2.Rect.YCenter, Date2.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt) or (max (InvoiceNumber.Rect.YCenter - Date2.Rect.YCenter, Date2.Rect.YCenter - InvoiceNumber.Rect.YCenter ) < 30dt)) and ((Date2.Rect.Left - InvoiceHeader.Rect.Right > 100dt) or (Date2.Rect.Left - InvoiceNumber.Rect.Right > 50dt)))
)
then
{ Dontfind(); }
else
{ Below: PageRect.Top; }
このコードで確認された制限の簡易さにもかかわらず、コード自体は大きくなりました。要素日付またはDateAsStringの1つで検出されたデータフィールドが請求書番号フィールドの右、しかしながら同じ水準(縦許容差30dt)に位置している場合、帰無仮説が補助要素で生成されるよう指示しなければいけない、という考え方です。別のケースでは、要素ShamElementはページ上端の下で検索されます。種類段落の補助要素をいかなる追加的な検索制限もなしに作成したため、ページのテキストオブジェクトすべてが囲まれ、単一仮説が生成されます。
確認するほぼすべての制限は直感的に明確です。そのため、最も複雑なもののみを扱います。
max (InvoiceHeader.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt
コードのこの部分が確認するのは、データフィールド(この場合は要素日付で検出されたかどうか)がフィールド「請求書番号」の名前の要素と同じ横水準に位置していることです。日付は名前より若干高いまたは低いかもしれません(スキャンや文書の作成中にエラーが発生する可能性があるためです)。最大差を30dtと指定しました。同様の方法で、フィールド「請求日」と「請求書番号」の縦の相互位置を確認します。
Date1.Rect.Left - InvoiceHeader.Rect.Right > 100dt
この行は、日付フィールドがフィールド「請求書番号」の名前の右に位置している(日付の左境界の座標が名前の右境界の座標よりも大きい)ことを確認します。名前と日付の間に請求書番号が必要なので、100dtの許容差を指定しました。日付フィールドの場所は、フィールド「請求書番号」の右にあります。
確認のすべての条件はその後、要素DateAsStringで複製されます。
コードが正しいことを確認するために、すべてのページでFlexiLayoutの合致を実行しましょう。日付フィールドが請求書番号の右にあるページ1と3で、要素ShamElementの帰無仮説が生成されたことを見ることができます。他の2ページでは、テキスト断片が検出されました。
企業の詳細を検出するために、グループ要素CompanyGroupを作成します。これは種類文字列の要素CompanyName(試験画像の企業名は単一行に書かれています)および種類段落の所在地をグループ化します。この要素は、企業所在地を含むブロックを検索するために使用されます。
補助要素の使用は、高度な検索前関係セクションの要素の検索制限を記述するコードを簡易化するのに役立ちます。そのため、補助要素が検出されない場合(すなわちその帰無仮説が生成される)場合、これは日付フィールドが請求書フィールドの右に位置することを意味します。このような場合、フィールド「企業名」はフィールド「請求書番号」の下で検索されます。補助要素が検出された場合、フィールド「企業名」はフィールド「請求書番号」の上で検索されます。
if ShamElement.IsNull then
{ Below: InvoiceGroup.InvoiceHeader;
Below: InvoiceGroup.InvoiceNum;
NearestY: PageRect.Top;
}
else
{ Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
}
注意:この場合、企業名を含むフィールドを一義的かつ正確に検出するのに役立つことから、関数Nearestを使用することができます(同じ検索制限に合致するオブジェクトはありません)。
要素CompanyNameの高度な検索後関係セクションで、以下のコードを書きました:
if not IsNull then
{ FuzzyQuality: Rect.Left - PageRect.Left, {0, 0, 0, 50000}*dt;
FuzzyQuality: Rect.Top - PageRect.Top, {0, 0, 0, 50000}*dt;
FuzzyQuality: 100dt - Height, {0, 0, 0, 10000}*dt;
}
このコードは、単一行のフィールド「企業名」が請求書番号のフィールドの上で検索される時に機能します。求められるフィールドには名前がありません。同時に、フィールド「企業名」と「企業所在地」の相互位置はページ2と4で異なっています。制限Above:InvoiceGroup.InvoiceHeader;と Above:InvoiceGroup.InvoiceNum;は企業名の文字列だけでなく、所在地フィールドの文字列でも満たされます。
生成されたすべての仮説の中から正しい仮説のみを選択するには、高度な検索後関係セクションの以下のコードが使用されます。
By typing the code
FuzzyQuality: Rect.Left - PageRect.Left, {0, 0, 0, 50000}*dt;
および
FuzzyQuality: Rect.Top -PageRect.Top, {0, 0, 0, 50000}*dt;
画像の下部境界と右境界に近いオブジェクトのある仮説にもっと厳しいペナルティを科すよう指示します。ですが、ページ2ではフィールド「企業名」は所在地フィールドの左にあるものの、その上の行よりも下にあります。従って、記述された制限は、求められるフィールド「企業名」の検出に不十分であり、制限をもう1つ追加する必要があります。
FuzzyQuality: 100dt - Height, {0, 0, 0, 10000}*dt;
この行で、生成された仮説のすべての行の高さを確認するよう指示します。行の文字が高いほど、対応する仮説の質が高くなります。
FlexiLayoutを合致させた後、フィールド「企業名」がすべてのページで正常に検出されていることがわかります。
所在地フィールドを検出するために、要素所在地の高度な検索前関係セクションで以下のコードを書きました:
if ShamElement.IsNull then
{ Below: CompanyName;
Above: TotalSumHeader.Rect.Top;
}
else
{
Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
RightOf: CompanyName.Rect.Left;
Exclude: CompanyName;
}
ここでも、コードの簡易化に役立つ補助要素ShamElementを使用します。
FlexiLayoutの作成が完了しました。FlexiLayoutの合致を実行すると、すべてのフィールドが正常に検出されていることがわかります。
12.04.2024 18:16:07