ImageProcessing-ElectronicPublications / scantailor-experimental

Scan Tailor Experimental is an interactive post-processing tool for scanned pages.
https://github.com/Tulon/scantailor/tree/experimental
GNU General Public License v3.0
40 stars 1 forks source link

research: distortion model not working? #47

Closed zvezdochiot closed 2 days ago

zvezdochiot commented 6 days ago

Hi @plzombie , @trufanov-nok , @noobie-iv .

В первую очередь данный вопрос будет интересен (помимо меня) наверное @noobie-iv .

Подкинули мне на DWG.RU фото-материальчик:

img02

Ничего вроде особенного. Накладываем на него "Кривые":

img02screen

Верхняя кривая на месте, нижняя - на месте. Но вот средние режут текст наискосок (коррекция кривизны в данном случае вообще никак не помогает).

И встаёт вопрос: Почему так? И нет ли какой возможности регулирования цилиндрической модели так, чтобы "побеждать" и такие вот случаи? Что то типа "добавления эксцентриситета"?

PS: Я готов пожертвовать "Коррекцией глубины и кривизны", отдав ползунок полностью под этот "эксцентриситет".

noobie-iv commented 6 days ago

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

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

А по-честному такое, видимо, только триангуляцией плоскости надо восстанавливать, и подбирать деформации треугольников плюс параметры расположения камеры плюс искажения объектива, чтобы точно воспроизвести форму изгиба бумаги и положение фотоаппарата. Как в Блендере в режиме трассировки камеры, чтобы 3D в видео встраивать. Но это ж в качестве опорных линий сначала надо отдельные строки трассировать. Или даже буквы - без букв сейчас даже ширина области неправильно восстанавливается на многих страницах. И когда чертеж попадается - тоже все ломается сразу.

Подозреваю, что это простыми алгоритмами типа "размыть-бинаризировать" не лечится, они заглавные буквы от строчных не отличают, и текст от чертежа, и горизонтальные линии от наклонных. Нужно что-то, что умеет различать буквы и линии. OpenCV или вообще самодельный ИИ, обученный конкретно на сканах. Но я туда точно не полезу, не по специальности оно мне.

zvezdochiot commented 5 days ago

Hi @noobie-iv .

Всё не так печально.

На данный момент имеется линейныый расчёт "промежуточных" линий прямо: https://github.com/ImageProcessing-ElectronicPublications/scantailor-experimental/blob/f0e124cbd5d5b20542bfb4632b74cfedeb475ba2/src/dewarping/CylindricalSurfaceDewarper.cpp#L113-L151 и обратно: https://github.com/ImageProcessing-ElectronicPublications/scantailor-experimental/blob/f0e124cbd5d5b20542bfb4632b74cfedeb475ba2/src/dewarping/CylindricalSurfaceDewarper.cpp#L160-L202 в координатах от 0 до 1 между верхей и нижней кривой.

Краевые точки кривых имеют ключевое значение и образуют координатный квадрат (0,0)-(1,1).

Ежели бы в этой конструкции присутствовала бы ещё одна линия (именно линия, не кривая), то можно было бы заменить линейность на параболу (то, что нужно прямо и обратно, для параболы не проблема, для кубической уже проблематичней, а дальше уже и лезть не стоит).

Но вот GUI. Ну не дружу я с ним. И реализовать эту линию (как вариант двумя точками на вертикальных линиях четырёхугольника) - не моё, ну никак. Я из-за GUI бросил идею добавления TrimBox в "2. Разрезка страниц", хотя на "подкапотной" части у меня всё работало (поэкспериментировал и удалил, чтоб не печалиться). Такие вот дела.

zvezdochiot commented 4 days ago

Hi @noobie-iv .

Чисто "подкапотно" добавил даже не линию, а отклонение от середины (0.5) слева и справа:

CylindricalSurfaceDewarper::Generatrix
CylindricalSurfaceDewarper::mapGeneratrix(double crv_x, State& state) const
{
    double const pln_x = m_arcLengthMapper.arcLenToX(crv_x, state.m_arcLengthHint);

    double const lin_y1 = -0.007;
    double const lin_y2 = 0.007;
    double const lin_y = lin_y1 + (lin_y2 - lin_y1) * pln_x;

    Vector2d const pln_top_pt(pln_x, 0);
    Vector2d const pln_bottom_pt(pln_x, 1);
    QPointF const img_top_pt(toPoint(m_pln2img(pln_top_pt)));
    QPointF const img_bottom_pt(toPoint(m_pln2img(pln_bottom_pt)));
    QLineF const img_generatrix(img_top_pt, img_bottom_pt);
    ToLineProjector const projector(img_generatrix);
    QPointF const img_directrix1_pt(
        m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1)
    );
    QPointF const img_directrix2_pt(
        m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2)
    );
    double const pln_straight_line_y = (fabs(m_plnStraightLineY - 0.5) > 0.45) ? 0.5 : m_plnStraightLineY;
    double const img_directrix1_proj(projector.projectionScalar(img_directrix1_pt));
    double const img_directrix2_proj(projector.projectionScalar(img_directrix2_pt));
    double const img_directrix12f_proj = (1.0 - pln_straight_line_y) * img_directrix1_proj
                                       + pln_straight_line_y * img_directrix2_proj;
    double const img_directrix12fd_proj = img_directrix12f_proj - pln_straight_line_y;
    //double const curve_coef = 1.0 + 0.5 * (m_curveCorrect - 2.0);
    double const curve_coef = (m_curveCorrect < 2.0) ? (1.0 / (3.0 - m_curveCorrect)) : (m_curveCorrect - 1.0);
    double const img_directrix12fds_proj = img_directrix12fd_proj * curve_coef;
    double const img_directrix12fs_proj = img_directrix12fds_proj + pln_straight_line_y + lin_y;
    QPointF const img_straight_line_pt(toPoint(m_pln2img(Vector2d(pln_x, img_directrix12fs_proj))));
    double const img_straight_line_proj(projector.projectionScalar(img_straight_line_pt));

    boost::array<std::pair<double, double>, 3> pairs;
    pairs[0] = std::make_pair(0.0, img_directrix1_proj);
    pairs[1] = std::make_pair(1.0, img_directrix2_proj);
    pairs[2] = std::make_pair(pln_straight_line_y, img_straight_line_proj);

    HomographicTransform<1, double> H(threePoint1DHomography(pairs));

    return Generatrix(img_generatrix, H);
}

Просто линия без всяких там парабул. И получил такую вот картину: img02lincorscreen

PS: Так то по уму вообще использовать третью (среднюю) кривую, а не вычислять её как среднуюю из верхней и нижней. Но как получить её из RANCAS?

PS: Ежели я ползунок отряжу полностью под эту линию, забив болт на глубину и кривизну? Это как? Не очень нагло будет? Но было бы менее плохо, ежели бы всё-таки по регулировочной точке на левой и правой прямой.

noobie-iv commented 4 days ago

Вот развернуто вручную в блендере по сетке 5x3, 5x5, 5x9 линий.

Lines

Cтроки чуть кривоваты (легкая синусоида по длине строки), потому что развертка из отрезков, а не из сплайнов. И перспективу я не пытался починить, хотя покрутить камеру можно.

3x5: 3x5

5x5: 5x5

9x5: 9x5

Во всех версиях верхняя, нижняя и одна средняя строки ровные. Однако между ними кривизна таки видна, и каждое дополнительное деление пополам улучшает дело.

То есть лучший результат получился в варианте 5x9=45 точек, но подбирать их вручную на каждой странице нереально. А для автоматики нужно таки трассировать по горизонтали строки, а по вертикали буквы.

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

zvezdochiot commented 3 days ago

Hi @noobie-iv .

Вариант с тыканьем - не вариант, ибо напрочь минусует автоматику. Вариант с трассировкой каждой строки - не вариант, ибо убивает сглаживание и модель становится суперкривой.

Чем не устраивает вариант с корректировкой наклона вычисляемой средней линии (см. пост выше) "избранных" страниц? Надо то добавить две точки по бокам, на крайняк использовать для регулировки ползунок (минусовав при этом глубину и кривизну).

Текущее исправление искажений: img02lcornoscreen

После указанной в посте выше "подкапотной" корекции наклона средней линии: img02lcorpostscreen

PS: Хотелось бы конечно использовать RANCAS для обнаружения средней линии и использовать её для создания этих самых двух боковых точек. Но как?

PS2: Нахождение пары верхней и нижней кривой в RANCAS: https://github.com/ImageProcessing-ElectronicPublications/scantailor-experimental/blob/f0e124cbd5d5b20542bfb4632b74cfedeb475ba2/src/dewarping/DistortionModelBuilder.cpp#L252-L273 А как среднюю то найти? Делить список линий пополам? Или сначала найти верхнюю и нижнюю кривую, после чего делить диапазон между ними пополам? Тоже "слегка" геммороно и ненадежно ни разу. Более того, существует возможность дефектных страниц (и на одну из них я уже напоролся - нижняя часть страниц была спошным шумом), на которых наблюдается немеренное кол-во линий. На такой странице схватил SEGFAULT. Это конечно "уникальный" случай, но подумываю разветвить схему составления списка пар для RANCAS на Тулон-новскую и мою по ограничению кол-ва линий (например, 200). А после этого всё станет ещё гемморойней.

noobie-iv commented 3 days ago

Чем не устраивает вариант с корректировкой наклона вычисляемой средней линии

Тем, что конкретно в этой версии книги из-за подкладки схема деформации явно состоит из двух независимых частей:

Page

И средняя линия тут - это середина основной страницы. А деформации в правом верхнем углу - сами по себе, их через среднюю линию не вычислить. Собственно это и видно в той развертке, которую STEX делает - строки кривые там, где одна деформация переходит в другую, а STEX их пытается по средней посчитать.

Я такую кривую страницу где-то тут уже выкладывал. Специально держал книгу левой, а фотал правой, и внизу такой же загиб получался.

zvezdochiot commented 3 days ago

Hi @noobie-iv .

Ясен красен на такой кривой странице я какого то "суперварианта" никак не получу. Но вариант с коррекцией наклона средней линии меня уже устраивает (в отличии от текущего). А ежели появится "средняя" кривая в явном виде, так я вообще расчёт разверну: буду использовать "среднюю" кривую как среднюю линию, а проекционное перспективное положение этой линии как значение для составления пары вместо 0.5. Такие вот дела.

noobie-iv commented 3 days ago

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

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

Так что ничего против волшебной середины сказать не могу, раз уж она есть. Лучше кривая прямая в программе, чем прямая кривая в фантазиях 😄.

zvezdochiot commented 3 days ago

@noobie-iv .

Так как мне эти две точки в ГУИ получить, чтобы с сигналами (аналогично точкам верхней и нижней кривой)? Без них у меня только один вариант - отдать ползунок полностью под коррекцию наклона средней линии. А это не айс ибо не удобно и на глубину и кривизну положить придётся (хотя я уже сам подумываю отключит кривизну от ползунка ибо не айс 2 параметра на одном регуляторе). Сам я эти 2 точки "победить" не смогу, тупо не осилю, ибо копипаста на такой замудренной системе сигналов не работает.

noobie-iv commented 3 days ago

Это хороший вопрос. Я целый mvst замутил, чтобы отследить, как там этот гуй работает. Та пара тысяч строк, что есть в нем сейчас - это путь одного щелчка через два фильтра, причем только по главной картинке. Там и какие-то самодельные цепочки задач, и сами задачи ничего не делают, кроме засовывания каких-то промежуточных результатов в параметры обратного вызова, и многократные заныривания в многопоточную часть QT, и непойми что еще. И где-то там по дороге эти задачи несколько раз дергают за ST за гуй. Я даже вырезал несколько уровней вложений типа ID-PageID-HalfPageID; какую-то петлю с обратным возвратом по фильтрам назад, и еще много чего. Но даже оставшееся наизусть уже не могу воспроизвести, в оконцовке придется еще диаграммы порисовать, кто с кем связан. Сейчас путь обрывается в кеше превьюшек. Я думал, там чуть-чуть осталось, но внутри оказался еще один такого же размера клубок из задач, списков задач, приоритетов задач, запусков списков задач, отмен задач и списков задач, возврата превьюшек в несколько заходов через создание разных типов задач, и.т.п. Если я когда-нибудь вообще продерусь через эти болота с задачами, то смогу подсказать, как кнопочку приделать, чтобы хотя бы не поломать картинку с превьюшками, как в STD.

zvezdochiot commented 3 days ago

Hi @noobie-iv .

Хоть я ковыряю по "вершине айсберга", но точно так же воспринимаю эту "шкатулку". Переключился с корекции наклона средней линии на нахождение третьей кривой (m_midCurve). Вряд ли доведу дело до результата (как и во многих других случаях), но пока как то так.

img02curves3

zvezdochiot commented 2 days ago

Hi @noobie-iv .

Неплохая идея была с третьей кривой. Ой неплохая. Она устраняла необходимость и "корекции кривизны" и "корекции наклона средней линии". Но не судьба. Не нашёл я как эту третью кривую пропихнуть в CylindricalSurfaceDewarper. Да и с перспективным искажением определённые траблы намечались. Не судьба.

На том возвращаемся к корекциям. Не подсобишь ли с ещё двумя ползунками, по образу depth_perception: под curve_correct (уже есть такой параметер) и curve_angle (собственно, корекция наклона средней линии, придётся вновь пропихивать через все эти объекты)? Так, чтобы сигналы доходили до адресатов? Мои попытки замутить эти ползунки копипастой с depth_perception закончились ничем.

zvezdochiot commented 2 days ago

Я СЬДЕЛЯЛЬ ЭТО!!!!

Три ползунка для корректировки модели: "глубина", "кривизна" и "наклон" средней кривой!

Плюс вернул метод составления пар кривых от @Tulon , только размерность выборки поменял с 5 на 16.

Такие вот дела.

noobie-iv commented 6 hours ago

А вот еще настоящий скан с настоящего сканера. Сканер, конечно, немного болеет. Давно смазки не получал.

z

Думаю, сотня-другая промежуточных кривых может поправить положение. Или нужна дополнительная модель деварпа, "пьяный сканер".