Russian (Русский) - Change language

Вспомогательный элемент с нулевой гипотезой – как средство упрощения гибкого описания

Данный прием можно использовать, когда необходимо проверять одни и те же громоздкие условия при поиске нескольких элементов. Суть приема в том, что проверка условий или необходимые сложные вычисления производятся один раз в некотором вспомогательном элементе. В результате проверки условий данный элемент либо ищется, либо не ищется, так что наличие найденной или ненайденной гипотезы выполняет роль флага, сигнализирующего о выполнении или невыполнении условий. Данный вспомогательный элемент создается так, чтобы он всегда находил что-нибудь на изображениях, если специально не выполнить команду DontFind(), т.е. «не искать». Количество гипотез элемента при этом должно быть небольшим (во избежание разрастания дерева гипотез и замедления наложения гибкого описания). Для этих целей можно использовать, например, элементы типа Object Collection или Paragraph, для которых всегда формируется одна гипотеза, в которую включаются все объекты заданного типа из области поиска. В секции Advanced pre-search relations фиктивного элемента пишем набор условий, выполнение которых мы хотим проверять для нескольких интересующих нас элементов, находящихся в дереве проекта ниже фиктивного элемента. Если все заданные условия оказались соблюдены, говорим программе, что фиктивный элемент искать не надо (вызываем функцию Dontfind()). В этом случае для фиктивного элемента будет сформирована нулевая гипотеза, и это будет служить признаком выполнения всех заданных условий при поиске других элементов. Т.е. вместо того, чтобы снова проверять одни и те же громоздкие условия, при поиске элементов просто надо осуществить проверку фиктивного элемента на IsNull.

Замечание. Данный прием позволяет делать код более наглядным. Кроме того, если необходимо отредактировать заданные условия, это необходимо выполнить только при описании одного фиктивного элемента. Это снижает вероятность появления ошибок в отличие от ситуации многократного дублирования громоздкого кода, которая, практически, неизбежно приведет к появлению ошибок, как логических, так и синтаксических.

Замечание. В последующих версиях продукта планируется поддержать создание переменных в области видимости подэлементов групп. Таким образом, результат проверки условий можно будет передавать в значениях переменных различных типов. Предлагаемый прием является временным решением (workaround), позволяющим упростить код advanced-секций в текущей версии FlexiLayout Studio.

Проиллюстрируем данный прием на примере проекта 1.fsp (папка %public%\ABBYY\FlexiCapture\12.0\Samples\FLS\Tips and Tricks\Auxiliary element). На данных изображениях будем искать поля “Номер счета”, “Дата счета”, “Название компании”, “Адрес компании”.

Допустим, нам необходимо обрабатывать счета двух типов:

  • Поля “Название компании” и “Адрес компании” находятся выше поля “Номер счета”;
  • Поля “Название компании” и “Адрес компании” находятся ниже поля “Номер счета”.

Как видно на изображениях, поля “Название компании” и “Адрес компании” не имеют заголовков, что не позволяет использовать стандартный прием поиска поля данных относительно его заголовка. Но прослеживается определенная закономерность: когда поле даты расположено правее, номера счета, реквизиты компании располагаются под полем “Номер счета” (страницы 1 и 3), а когда поле даты находится под полем счета, реквизиты компании находятся над полем “Номер счета” (страницы 2 и 4).

Исходя из обнаруженной закономерности, будет логично сначала определять местоположение полей “Номер счета” и “Дата счета”. А потом, искать два остальных поля относительно найденных полей, анализируя их взаимное расположение.

В проекте создан составной элемент InvoiceGroup, в котором находятся элементы (InvoiceHeader, InvoiceNum, DateHeader и составной элемент DateGroup), необходимые для поиска заголовков и самих полей “Номер счета” и “Дата счета”.

Замечание. Более подробную информацию о групповом подходе, применяемом при поиске полей даты, можно получить в разделе Поиск даты в случае хорошего и плохого распознавания. Подробнее остановимся лишь на описании условий поиска поля даты в данном проекте.

Как видно при просмотре изображений, у поля даты не всегда присутствует заголовок. Поэтому необходимо для поиска поля даты задавать два набора условий: когда заголовок найден, и, когда заголовок не найден (частный случай - когда на самом деле он на странице присутствует, но не обнаружен, например, из-за мусора).

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), процедура поиска будет идти относительно собственного заголовка поля даты: правее заголовка, примерно на одном с ним уровне, допускается незначительный запас по вертикали:

{ RightOf: DateHeader.Rect.Right;
Below: DateHeader.Rect.Top - 30dt;
Above: DateHeader.Rect.Bottom + 30dt;
}
    

Иначе область поиска разбивается на две прямоугольных области: правее номера счета, на одном с ним уровне и под полем счета.

Замечание. Для простоты мы полагаем, что у нас изображения хорошего качества, и на них всегда находится поле номера счета и его заголовок. На самом деле, перед обращением к свойствам этих элементов следовало бы осуществить их проверку на IsNull, поскольку, если элементы окажутся не найдены, поиск будет вестись относительно областей поиска соответствующих элементов.

В секции Advanced post-search relations элемента Date мы написали следующий код:

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;
}
}
    

Он позволяет влиять на качество гипотезы даты в зависимости от ее удаленности от заголовка поля счета и самого поля “Номер счета” в случае, если заголовок самого поля даты не найден. Чем больше расстояние до указанных полей, тем сильнее будут оштрафованы соответствующие гипотезы. Т.е. мы ищем поле даты, ближайшее к полю номера счета.

Замечание. Подробная информация об использовании функции FuzzyQuality приведена в разделе Использование функций Nearest и FuzzyQuality для поиска элементов.

Для элемента DateAsString заданы аналогичные условия поиска. Только в секции Advanced post-search relations кроме указанного выше кода есть дополнительная строка:

FuzzyQuality: 600dt - Width, {0, 0, 0, 50000}*dt;
    

Она нужна для того, чтобы при поиске поля даты в виде строки при прочих равных условиях предпочтение было отдано более длинной цепочке символов, соответствующей заданному алфавиту.

Кроме того, на вкладке Search Constraints элемента DateAsString мы задали, что при поиске поля даты в виде строки необходимо исключать прямоугольник элемента InvoiceNum. Дело в том, что для простоты мы решили не дублировать для элемента DateAsString все те же условия поиска, которые мы задавали в секции Advanced pre-search relations для элемента Date. И мы определили область поиска как RestrictSearchArea (Date.Rect);. Т.е. сообщили программе, что поиск объектов элемента DateAsString необходимо вести в области нечеткого прямоугольника элемента Date. Как мы помним, область поиска поля даты в элементе Date была описана в виде массива прямоугольников. В случае, когда для элемента Date формируется нулевая гипотеза, в качестве прямоугольника (Rect) данного элемента берется описывающий прямоугольник области поиска. Как можно видеть на изображении ниже, в него попадает и поле “Номер счета”, описываемое элементом InvoiceNum. При определенных условиях (например, при наличии большого количества мусора в поле даты) не исключена ситуация, когда вместо поля даты строковый элемент DateAsString обнаружит поле с номером счета, поскольку для данного элемента задан набор символов (в том числе цифры) без каких-либо ограничений по формату.

После описания элементов, необходимых для поиска полей “Номер счета” и “Дата счета”, можно уже приступать к поиску полей “Название компании” и “Адрес компании”.

В проекте создан элемент типа Paragraph с именем ShamElement, который играет роль вспомогательного элемента, используемого исключительно для проверки взаимного расположения полей “Номер счета” и “Дата счета”.

В секции Advanced pre-search relations вспомогательного элемента мы задали следующий код:

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; }
    

Несмотря на простоту условий, которые мы проверяем с помощью данного кода, он получился довольно громоздким. Смысл его сводится к следующему. Если поле даты, найденное с помощью одного из элементов Date или DateAsString, расположено правее поля номера счета, но на одном с ним уровне (допуском 30dt по вертикали), мы сообщаем программе, что вспомогательный элемент искать не будем, для него будет сформирована нулевая гипотеза. Во всех остальных случаях будет осуществляться поиск элемента ShamElement ниже верхнего края страницы. Поскольку мы создали вспомогательный элемент типа Paragraph и не задали никакие дополнительные условия поиска, в него войдут все текстовые объекты, присутствующие на странице и будет сформирована единственная гипотеза.

Интуитивно практически все проверяемые условия понятны. Поэтому подробнее остановимся лишь на некоторых, которые могут быть не вполне очевидны.

max (InvoiceHeader.Rect.YCenter - Date1.Rect.YCenter, Date1.Rect.YCenter - InvoiceHeader.Rect.YCenter ) < 30dt
    

Данное выражение проверяет, что поле даты (в данном случае, если она найдена с помощью элемента Date) расположено практически на одном уровне по вертикали с элементом заголовка поля “Номер счета”. Причем допустимо, чтобы дата располагалась немного выше или ниже заголовка (учитывается возможная погрешность заполнения и/или сканирования). Мы задали, что максимальное отклонение не должно превышать 30dt. Аналогично проверяется взаимное расположение по вертикали полей даты и номера счета.

Date1.Rect.Left - InvoiceHeader.Rect.Right > 100dt
    

Данное выражение проверяет, что поле даты расположено правее заголовка поля “Номер счета“ (координата левой границы даты больше координаты правой границы заголовка). Мы задали запас 100dt с учетом того, что между заголовком и датой должен находиться еще собственно номер счета. Аналогично проверяется расположение поля даты правее поля “Номер счета”.

Все условия проверки дублируются для элемента DateAsString.

Для проверки правильности написанного кода запустим процедуру наложения гибкого описания на всех страницах. Мы видим, что на страницах 1 и 3, на которых поле даты расположено правее номера счета, как и ожидалось, для элемента ShamElement оказалась сформирована нулевая гипотеза. А на остальных двух страницах оказались выделены текстовые фрагменты.

Для поиска реквизитов компании в проекте создан составной элемент CompanyGroup. В него вошли элемент CompanyName типа Character String (поскольку имя компании на тестовых изображениях пишется в одну строку) и элемент Address типа Paragraph для поиска блока адреса компании.

Использование вспомогательного элемента позволило максимально упростить код при описании условий поиска элемента в секции Advanced pre-search relations. Т.е. если вспомогательный элемент не найден (для него сформирована нулевая гипотеза), это служит признаком того, что поле даты расположено правее поля счета. В этом случае поиск поля “Название компании” будет осуществляться под полем “Номер счета”. Если вспомогательный элемент найден, поиск поля “Название компании” будет идти над полем “Номер счета”.

if ShamElement.IsNull then
{ Below: InvoiceGroup.InvoiceHeader;
Below: InvoiceGroup.InvoiceNum;
NearestY: PageRect.Top;
}
else
{ Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
}
    

Замечание. В данном случае мы можем использовать функцию Nearest, поскольку она позволяет однозначно и правильно определить поле названия компании (отсутствуют другие объекты, удовлетворяющие тем же условиям поиска).

В секции Advanced post-search relations элемента 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; соответствует не только строка с названием компании, но и строки в поле адреса.

Чтобы выбрать из множества сформированных гипотез единственную правильную, и используется код в секции Advanced post-search relations.

Записывая

FuzzyQuality: Rect.Left - PageRect.Left, {0, 0, 0, 50000}*dt;
    

и

FuzzyQuality: Rect.Top -PageRect.Top, {0, 0, 0, 50000}*dt;
    

, мы сообщаем программе, что сильнее необходимо штрафовать гипотезы, объекты которых находятся ближе к нижнему и правому краям изображения. Однако на странице 2 поле “Название компании” расположено левее поля адреса, но ниже его верхней строки. Поэтому описанных выше условий недостаточно, чтобы однозначно определить местоположение искомого поля “Название компании”. Поэтому мы записали еще одно условие.

FuzzyQuality: 100dt - Height, {0, 0, 0, 10000}*dt;
    

С помощью данной записи мы сообщаем программе, что необходимо проверять высоту строк для всех сформированных гипотез. Чем больше символы строки по высоте, тем выше качество соответствующей гипотезы.

После запуска процедуры наложения гибкого описания и просмотра результатов мы видим, что поле “Название компании” успешно найдено на всех страницах.

Для поиска поля адреса в секции Advanced pre-search relations элемента Address мы задали следующий код:

if ShamElement.IsNull then
{ Below: CompanyName;
Above: TotalSumHeader.Rect.Top;
}
else
{
Above: InvoiceGroup.InvoiceHeader;
Above: InvoiceGroup.InvoiceNum;
RightOf: CompanyName.Rect.Left;
Exclude: CompanyName;
}
    

Здесь мы опять прибегли к помощи вспомогательного элемента ShamElement, что позволило существенно упростить код.

На этом создание гибкого описания окончено. После запуска процедуры наложения на всех страницах пакета видим, что все поля успешно найдены.

10.11.2020 12:08:08


Please leave your feedback about this article