Skip to content

research: distortion model not working? #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zvezdochiot opened this issue Nov 12, 2024 · 14 comments
Closed

research: distortion model not working? #47

zvezdochiot opened this issue Nov 12, 2024 · 14 comments
Assignees
Labels
complete Performed dispute Debate enhancement New feature or request question Further information is requested

Comments

@zvezdochiot
Copy link
Member

zvezdochiot commented Nov 12, 2024

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

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

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

img02

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

img02screen

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

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

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

@zvezdochiot zvezdochiot added dispute Debate enhancement New feature or request question Further information is requested labels Nov 12, 2024
@zvezdochiot zvezdochiot self-assigned this Nov 12, 2024
@noobie-iv
Copy link
Member

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

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

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

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 13, 2024

Hi @noobie-iv .

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

На данный момент имеется линейныый расчёт "промежуточных" линий прямо:

CylindricalSurfaceDewarper::Generatrix
CylindricalSurfaceDewarper::mapGeneratrix(double crv_x, State& state) const
{
double const pln_x = m_arcLengthMapper.arcLenToX(crv_x, state.m_arcLengthHint);
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;
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);
}

и обратно:
QPointF
CylindricalSurfaceDewarper::mapToDewarpedSpace(QPointF const& img_pt, State& state) const
{
double const pln_x = m_img2pln(toVec(img_pt))[0];
double const crv_x = m_arcLengthMapper.xToArcLen(pln_x, state.m_arcLengthHint);
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;
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(img_directrix1_proj, 0.0);
pairs[1] = std::make_pair(img_directrix2_proj, 1.0);
pairs[2] = std::make_pair(img_straight_line_proj, pln_straight_line_y);
HomographicTransform<1, double> const H(threePoint1DHomography(pairs));
double const img_pt_proj(projector.projectionScalar(img_pt));
double const crv_y = H(img_pt_proj);
return QPointF(crv_x, crv_y);
}

в координатах от 0 до 1 между верхей и нижней кривой.

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

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

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 14, 2024

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
Copy link
Member

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

Lines

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

3x5:
3x5

5x5:
5x5

9x5:
9x5

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

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

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 15, 2024

Hi @noobie-iv .

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

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

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

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

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

PS2: Нахождение пары верхней и нижней кривой в RANCAS:

// Full RANCAS (zvezdochiot)
for (int i = 0; i < (num_curves - 1); i++)
{
for (int j = i + 1; j < num_curves; j++)
{
ransac.buildAndAssessModel(&ordered_curves[i], &ordered_curves[j]);
}
}
if (dbg && dbg_background)
{
dbg->add(visualizeTrimmedPolylines(*dbg_background, ordered_curves), "trimmed_polylines");
dbg->add(visualizeModel(*dbg_background, ordered_curves, ransac.bestModel()), "distortion_model");
}
DistortionModel model;
if (ransac.bestModel().isValid())
{
model.setTopCurve(Curve(ransac.bestModel().topCurve->extendedPolyline));
model.setBottomCurve(Curve(ransac.bestModel().bottomCurve->extendedPolyline));
}
return model;

А как среднюю то найти? Делить список линий пополам? Или сначала найти верхнюю и нижнюю кривую, после чего делить диапазон между ними пополам? Тоже "слегка" геммороно и ненадежно ни разу. Более того, существует возможность дефектных страниц (и на одну из них я уже напоролся - нижняя часть страниц была спошным шумом), на которых наблюдается немеренное кол-во линий. На такой странице схватил SEGFAULT. Это конечно "уникальный" случай, но подумываю разветвить схему составления списка пар для RANCAS на Тулон-новскую и мою по ограничению кол-ва линий (например, 200). А после этого всё станет ещё гемморойней.

@noobie-iv
Copy link
Member

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

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

Page

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

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 15, 2024

Hi @noobie-iv .

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

@noobie-iv
Copy link
Member

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

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

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 15, 2024

@noobie-iv .

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

@noobie-iv
Copy link
Member

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 16, 2024

Hi @noobie-iv .

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

img02curves3

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 16, 2024

Hi @noobie-iv .

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

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

@zvezdochiot
Copy link
Member Author

zvezdochiot commented Nov 16, 2024

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

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

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

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

@zvezdochiot zvezdochiot added the complete Performed label Nov 16, 2024
@noobie-iv
Copy link
Member

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

z

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

zvezdochiot referenced this issue in ImageProcessing-ElectronicPublications/scantailor-deviant Apr 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
complete Performed dispute Debate enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants