Skip to content

Wrong proportions of dewarped images #5

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
noobie-iv opened this issue Jan 3, 2025 · 269 comments
Closed

Wrong proportions of dewarped images #5

noobie-iv opened this issue Jan 3, 2025 · 269 comments
Labels
enhancement New feature or request process Solution at work

Comments

@noobie-iv
Copy link
Member

Sample images:

Dewarp-1
Dewarp-2

Results:

The width should increase when unfold curved image, but it doesn’t:

01

The width should not decrease when unfold flat image, but it does:

02

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 3, 2025

Hi @noobie-iv .

В STEX эта проблема по большей части решается режимом "Уместить" (Affine) в "Макетировании страницы". Но здесь такого нет, так что решать надо как то "на месте". Хоть корректировку пропорции ручную вводить:

float delta = 0.0f; /* params */
float scale_X = 1.0f + delta;
float scale_Y = 1.0f / scale_X;

@noobie-iv
Copy link
Member Author

@zvezdochiot

Ручную корректировку же после прикручивать надо, потому что сразу ее не видно. А это как раз новая стадия нужна.

Но это даже не главное. Я тут подумал, что можно ведь вообще нарендерить в Блендере тестовых картинок со страницами заранее известной формы. Тогда точно известно, что должна развертка получить обратно.

Например, вот квадрат, вид наискосок:

Перспектива

01

Развертка

02

"Какие тут уши, тут бы пальцы не обжечь (с)"

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 3, 2025

@noobie-iv , сразу (рефлекторно) возникает мысль о необходимости новый репы: scantailor-testing. И вот туда всё это "добро" плодить и "чудить": dataset0001, dataset0002, ... А в дальнейшем уже ссылаться на них.

Но сама идея верная.

Попробуй для начала исходить из перспективной прокции:

p1,p2,p3,p4 - четыре угла
count = count(p1,p2,p3,p4) = 4
mx = (p1.x + p2.x + p3.x + p4.x) / count
my = (p1.y + p2.y + p3.y + p4.y) / count
sx = sqrt(sum((p{1,2,3,4}.x - mx)^2) / count)
sy = sqrt(sum((p{1,2,3,4}.y - my)^2) / count)

wn = 2 * sx
hn = 2 * sy

а потом уже будем ковырять псевдоцилиндрическую проекцию.

PS: Твоя модель понятна и во всех смыслах интересна. Но! Явно теряется общий масштаб. Это будет проблемой? Ещё как. Здесь тоже надо покумекать. Равенство площадей?

@noobie-iv
Copy link
Member Author

Вот набор тестов под перспективу:
PerspectiveTestSet.zip
(STEX тоже лажает)

Sample

@zvezdochiot
Copy link
Member

@noobie-iv say:

Вот набор тестов под перспективу:

В STEX автомат только на 4х из 13 сработал (причём на одной криво). И да. Пропорции не особо алё.

@noobie-iv
Copy link
Member Author

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

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 3, 2025

@noobie-iv say:

А результаты выходят один другого хуже.

Вы будете смеяться с моей истории, но в STU автомат сработал на 8ми из 13. Сделал всё ровно, но пропорции никакущие. В STA тестить не хочу - он частенько падает на этом.

PS: Искривления же "на ровном месте" в STEX связаны уже не с автоматом, а с DistorsionModel. Она генериться из трёх линий: верхней, нижней и "средней", в координатах от 0 до 1. Надо трассировать её в Generatrix или обратной, смотреть что не так.

@zvezdochiot
Copy link
Member

Hi @noobie-iv .

Вставил в Generatrix и обратную код от Tulon-а: STEX: release qt5. Ни шиша. Кривизна та же самая. Откуда? Хз.

@noobie-iv
Copy link
Member Author

Набор тестов под развертку: WarpTestSet.zip

Sample2

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 3, 2025

@noobie-iv say:

Набор тестов под развертку

С исправленным XSpline там, где сработал автомат:

  • 1-0001 - норм
  • 1-0003 - выпрямил, но буквы слева широкие, справа - узкие, корекция глубины не помогает
  • 1-0005 - норм
  • 1-0008 - верх кривой на пол буквы
  • 1-0011 - верх кривой на пол буквы
  • 2-0001 - выпрямил, но буквы поцентру широкие, по краям - узкие, корекция глубины не помогает
  • 2-0002 - верх, лево и право кривы на пол буквы, слишком широкий
  • 2-0003 - выпрямил, но буквы слева широкие, справа - узкие, корекция глубины не помогает
  • 2-0004 - выпрямил, но буквы слева широкие, справа - узкие, корекция глубины не помогает, уже предыдущего
  • 2-0005 - право криво на пол буквы
  • 2-0008 - кривой на одну букву везде
  • 2-0010 - право криво на пол буквы, узковат
  • 2-0011 - кривой на пол буквы везде

PS: STU выпрямил полностью без кривизы (по краям кое-где чуть-чуть): 1-0001, 1-0003, 1-0004, 1-0005, 1-0008, 1-0010, 1-0011, 2-0001, 2-0003, 2-0004, 2-0008, 2-0010. Но пропорции (не только наружные, но и внутренние) просто ни к чёрту.

@noobie-iv
Copy link
Member Author

И это еще точный цилиндр. А ему еще можно перекос устроить, чтобы 4 вершины из плоскости закрутились.

WarpTestRotatedSet.zip

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

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 3, 2025

@noobie-iv say:

гладкие кривые разворачиваются во что-то ломано-перекошенное

Да я уже "ручками пошалил" и увидел. Как и сказал ранее - DistortionModel сбоит. Попробую конечно разобраться, но... там же сплошные плюсы! X(

PS: А DataSet хорош! Давно надо было его сделать.

PS2: "По секрету" расскажу: походу GaussBlur() от Tulon-а тоже слегка шальной, но это уже мелочи.

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 3, 2025

@noobie-iv say:

А ему еще можно перекос устроить, чтобы 4 вершины из плоскости закрутились.

С исправленным XSpline картинка практически идентична STEX. Так что мерж прошёл вполне себе успешно.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Примитивный тест с перспективой.

Беру две камеры - длиннофокусную и короткофокусную . Обе навожу каждую на свой на квадрат, чтобы он закрыл поле зрения. А потом поворачиваю квадрат вокруг нижней стороны. От этого дальняя сторона уменьшается, а боковые начинают указывать в точку схода. Для поворота боковых сторон зрительно на один и тот же угол сами квадраты у разных камер надо повернуть по-разному: у короткофокусной послабее, у длиннофокусной посильнее. Но в любом случае легко подобрать повороты так, чтобы боковые стороны совпали. А вот расположение дальней стороны при этом не совпадет. Но и его можно подогнать, просто удлинив квадрат у длиннофокусной камеры.

001

В результате вид из обеих камер одинаковый:

002
003

А реальные пропорции разные:

004

При этом равномерная сетка на обоих "листах" в камеру тоже выглядит одинаково, фокус с "восприятием глубины" не прокатит.

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

@zvezdochiot
Copy link
Member

@noobie-iv say:

просто по координатам вершин восстановить пропорции не получится

Просто по координатам углов нет. Но ты забыл про псевдоцилиндр. Ежели в blender рассматривать не лист, а цилиндр, то кое что с этого выжать можно. Да потребуется допущение: ты должен как будто бы знать форму цилиндра (откуда?). Но то, что по одному снимку нельзя восстановить объёмную картину - это известно изначально.

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 4, 2025

Hi @noobie-iv .

Тестирование бэтки.

Правда не самой бэтки, а current.

Материал для тестирования

set0002 из scantailor-testing

Тестирование

  1. current:

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

На этом тестирование прекратил. Результата нет.

  1. current + исправление XSpline, позаимствованное в STEX:

set0002-20250104-std.pdf

Данный результат отражает все текущие недостатки и недоработки STD.

  1. STEX 1.2024.11.18 (last). Для сравнения:

set0002-20250104-stex.pdf

Без постабработки в GraphicsMagick и GIMP.

@zvezdochiot
Copy link
Member

Hi @noobie-iv .

Первый более-менее вменяемый отклик по тестированию на Руборде.

@noobie-iv
Copy link
Member Author

@zvezdochiot

на Руборде

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

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

Статейка попалась про подбор положения камеры, как раз по двум точкам схода, для перспективы в самый раз будет:
Camera calibration using two or three vanishing points
А если считать, что у цилиндра все углы в плоскость попадают - можно и для него так же камеру выставить, по 4 углам. Плюс дополнительный множитель по ширине за счет кривизны листа. Возможно, на первое время пойдет.

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 4, 2025

@noobie-iv , про стили в соседний "Bug report" (ха-ха) сказануть не хочешь? 😄

PS: Залью ка сюда: Camera_calibration_using_two_or_three_vanishing_points.pdf

PS2: А что по моему тестированию? Вопросы то должны быть. Али нет?

@noobie-iv
Copy link
Member Author

@zvezdochiot

Со стилями все просто. То, что можно поменять через QSS - я поменял. А текст в эскизах - это элементы типа QGraphicsSimpleTextItem, они в Qt, судя по гуглу, стилями не настраиваются в принципе, потому что не наследники от QWidget. И их цвета тупо захардкожены в ThumbnailSequence.cpp, под светлую тему, потому и на стили не реагируют. Со ссылками на главной странице та же фигня была, их пришлось заменить на кнопки из-за этого, одним из первых коммитов перед применением стилей. И, чтобы "просто починить текст", надо устраивать целую городильню с новым самодельным классом-наследником, ручными свойствами и отловом событий (paint-hover-хз-что-еще), чтобы вовремя менять цвета, типа такого:
https://stackoverflow.com/questions/56441849/qt-get-stylesheet-border-color
Это не слишком сложно, но потом ведь точно обнаружится, что под виндами оно работает те так, как под линуксами. А потом еще и еще. Так что в отложенных лежит.

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

@noobie-iv
Copy link
Member Author

@zvezdochiot

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

Краткий обзор формул:
Из фото в 3D, ч.2: калибровка камеры

Список методов вообще:
Camera Pose Estimation from

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 4, 2025

@noobie-iv say:

А вот формулы, по которым развертка сейчас считается

Залью сюда же: camera_calibration-habr.com.pdf

PS: Плохо, что формулы в виде изображений, а не SVG.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Конкретно на хабре еще и в формулах опечатки. Оригиналы, судя по виду, из справки по OpenCV притырены.
Camera Calibration and 3D Reconstruction

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

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 4, 2025

@noobie-iv . Не-не-не! Не лезь в OpenCV!

"Оно" конечно вполне себе "забавно", но помимо того, что всё "это" немеренно жрущее память и процессор, так ещё и с версионностью ещё хуже чем у Qt. Но решать тебе. Ежели хочешь 2 головные боли вместо одной, то вперёд. 😄

PS: Залью ка сюда: OpenCV-Camera_Calibration_and_3D_Reconstruction.pdf ("распечатка" не очень, но уж как получилось).

@noobie-iv
Copy link
Member Author

@zvezdochiot

Про память был хороший вопрос в багтрекерах какого-то из тейлоров. "Почему при 100G оперативки нужно постоянно ждать перезагрузки одних и тех же файлов?" Могу от себя добавить, что на современных мониторах эскизов на экране помещается больше, чем у ST в кеше (там 40 элементов ограничение стоит), и тот все время по диску шарит. Старенький он уже, мало что помнит 😄

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

@zvezdochiot zvezdochiot added the enhancement New feature or request label Jan 4, 2025
@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 5, 2025

@noobie-iv say:

Бага дальше сидит, на стадии развертки

Короче. Есть код:

HomographicTransform<2, double>
CylindricalSurfaceDewarper::fourPoint2DHomography(
boost::array<std::pair<QPointF, QPointF>, 4> const& pairs)
{
Matrix<double, 8, 8> A;
Matrix<double, 8, 1> b;
int i = 0;
typedef std::pair<QPointF, QPointF> Pair;
BOOST_FOREACH(Pair const& pair, pairs)
{
QPointF const from(pair.first);
QPointF const to(pair.second);
A.row(i) << -from.x(), -from.y(), -1, 0, 0, 0, to.x()*from.x(), to.x()*from.y();
b[i] = -to.x();
++i;
A.row(i) << 0, 0, 0, -from.x(), -from.y(), -1, to.y()*from.x(), to.y()*from.y();
b[i] = -to.y();
++i;
}
auto qr = A.colPivHouseholderQr();
if (!qr.isInvertible())
{
throw std::runtime_error("Failed to build 2D homography");
}
Matrix<double, 8, 1> const h(qr.solve(b));
Matrix3d H;
H << h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1.0;
return HomographicTransform<2, double>(H);
}

Это не что иное, как нахождение коэффициентов аффинного преобразования четырёхугольника в квадрат. И используется QR-разложение из Eigen. Но матрица 8x8 на треть состоит из нулей. И это не очень хорошо. Да, проверка на валидность вроде есть, но проверка проверке рознь. А прикрутить SVD из того же Eigen на место QR я чего то не смог (не понимаю я эту плюсовую дичь).

То же самое и с кривыми, только там матрица попроще (3x3):

HomographicTransform<1, double>
CylindricalSurfaceDewarper::threePoint1DHomography(
boost::array<std::pair<double, double>, 3> const& pairs)
{
Matrix<double, 3, 3> A;
Matrix<double, 3, 1> b;
int i = 0;
typedef std::pair<double, double> Pair;
BOOST_FOREACH(Pair const& pair, pairs)
{
double const from = pair.first;
double const to = pair.second;
A.row(i) << -from, -1, from * to;
b[i] = -to;
++i;
}
auto qr = A.colPivHouseholderQr();
if (!qr.isInvertible())
{
throw std::runtime_error("Failed to build 2D homography");
}
Matrix<double, 3, 1> const h(qr.solve(b));
Matrix2d H;
H << h[0], h[1], h[2], 1.0;
return HomographicTransform<1, double>(H);
}

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

@noobie-iv
Copy link
Member Author

@zvezdochiot

Поможет или нет - не знаю, но пусть будет. Это dewarp, вырезанный из STEX в отдельную программу. Все проще, чем из ST запускать. Не проверял, насколько он рабочий. Можно сдать в поликлинику для опытов.

dewarp.tar.gz

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 5, 2025

Hi @noobie-iv .

Хмм. При трассировке mapGeneratrix() и threePoint1DHomography() через qDebug() на Warp-3-rotated-0008.png нигде аномальных значений не получил. Но!:

Warp-3-rotated-0008-curve
Warp-3-rotated-0008-dewarp

Аномалия явным образом существует. Но возникает она походу не на этапе расчёта HomographicTransform, а на этапе её применения к изображению.

"Чем дальше, тем чуднее и чуднее....". И где теперь искать эту "падлу"?

PS: Перспективу проверил соответствующим режимом - аномалии нет. Ещё вариант, что аномалия возникает при комбинировании этих двух трансформаций: перспективы и кривизны внутри квадрата.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Выглядит так, как будто за краями текстового блока деварпер пытается по экстраполяции дорисовать цилиндр. А из-за большой кривизны левый край при трассировке сползает по цилиндру совсем далеко вниз, потому черная часть так опухает: деварпер считает, что она огромная, просто под таким углом выглядит маленькой, но если ее честно развернуть, получится ого-го сколько:

dewarp

@zvezdochiot
Copy link
Member

zvezdochiot commented Jan 30, 2025

@noobie-iv say:

Как бы тут физику подхимичить?

Ты накладываешь ограничения на квадрат фокуса. Но может стоит накладывать ограничение не на него, а на второй элемент размеров? Что то типа от 0.1 до 10.0 в долях от первого элемента? Да и не стоит ли подвергнуть анализу эти самые элементы (первый и второй) по отдельности на вполне конкретном set0001?

@noobie-iv
Copy link
Member Author

noobie-iv commented Jan 30, 2025

@zvezdochiot

Размеры будут использованы для вычисления пропорций листа, и больше нигде не понадобятся. А вот фокус, так или иначе, попадает в нормаль к листу. И, если он, например, 1e-16, то нормаль оказывается какой-то великоватой. По такой нормали точки с модели будут проецироваться за пределы фото, и вся развертка будет битая. Или наоборот, в точку стянется. И я все не могу въехать, физически тут фокус бывает кривой, или я где в формулах накосячил. Я несколько книжек по машинной графике бегло пролистал, но нигде подробностей не нашел.

@zvezdochiot
Copy link
Member

@noobie-iv say:

Размеры будут использованы для вычисления пропорций листа, и больше нигде не понадобятся. А вот фокус, так или иначе, попадает в нормаль к листу.

Ты не уловил смысл, сказанного мной. Попробую разжевать.

Предлагаемое тобой отсечение фоеуса понятно, но и только.

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

Ежели сработало отсечение какого то размера, то значит расчитанный фокус негож. Ну негож, так негож, значит надо "придумать" такой фокус, который даст приемлемые результаты по обоим размерам. При таком подходе "придуманный" фокус будет давать как минимум результат не хуже отсечённого.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Прикольно. Вот тут смещение снизу больше, чем сверху - и дефолтный алгоритм считает, что лист вогнутый:

Image

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

Бывают "мусорные ветки", которые можно временно залить, чтобы просто показать, а потом стереть?

@zvezdochiot
Copy link
Member

@noobie-iv

Бывают "мусорные ветки", которые можно временно залить, чтобы просто показать, а потом стереть?

Пока ты не решил данный вопрос, ты можешь хоть каждый день делать новую ветку, а на следующий - удалять и делать новую. Был бы прок только с этого.

А ежели нужно показать что то на материале, тогда пользуй scantailor-testing, которрый всегда можно удалить и создать по новой с обнулённой историей. Или вообще новую репу, типа scantailor-wrong. 😄

@noobie-iv
Copy link
Member Author

@zvezdochiot

Ок, тогда вот демка: change_dewarp_model

С дефолтным построителем глючит. Но если я вбиваю фиксированное сечение:

CylindricalSurfaceDewarper::initArcLengthMapper(...)
{
    m_arcLengthMapper.addSample(0.00, 0.00);
    m_arcLengthMapper.addSample(0.25, 0.08);
    m_arcLengthMapper.addSample(0.50, 0.10);
    m_arcLengthMapper.addSample(0.75, 0.08);
    m_arcLengthMapper.addSample(1.00, 0.00);

    m_directrixArcLength = m_arcLengthMapper.totalArcLength();
    m_arcLengthMapper.normalizeRange(1);
}

То в принципе получаю то, что хотел:

Image

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

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

@zvezdochiot
Copy link
Member

@noobie-iv say:

Ок, тогда вот демка: change_dewarp_model

Роман, не дели на 2, когда float или double. Умножай на половину (0.5f).

@noobie-iv
Copy link
Member Author

@zvezdochiot

Добавил тестовое чтение профиля с фото: 895d0fd

Image

Image

Базовая идея простая: из модели я выношу на фото единичные габариты профиля (0,0,0)(0,1,0)(0,0,1)(0,1,1), строю обратную гомографию по 4 точкам, и по ней переношу трассировку обратно в габарит - получается сечение с фотографии. На первой картинке единичные габариты отрендерены пунктиром.

Set0001 проходится на тех тестах, где есть двойной поворот. Где одинарный - битый фокус портит нормаль, и вслед за ним профиль. Перекошенные тесты, разумеется, не восстанавливаются.

В этой простой идее есть места для спотыканий:

  1. Сечение может пройти сквозь камеру, и тогда гомография не обращается. Я оцениваю этот случай по проекции левой и правой сторон габарита на нормаль к секущей - img_bound_left_vector.dot(img_directrix_normal_vector). Если проекция не вписалась в допуск, надо чуть повернуть сечение. Видимо, надо добавить еще две точки - (0,1,1)(1,1,1) - это плоскость, проходящая горизонтально через верх габарита, вынести их на фото, построить новую гомографию с листа на плоскость, и перенести пересечения боковых сторон этой плоскости с линией допуска - тогда на плоскости получатся точки, до которых надо повернуть габарит. Выбираю худшую из них dz, и заново выношу на фото (0,dz,1)(1,dz,1). Строю новую обратную гомографию, по которой можно безопасно переносить сечение.
  2. Сечение после переноса может вылететь за габариты вверх/вниз - тогда надо смасштабировать его по вертикали.
  3. Сечение после переноса может вылететь за габариты влево/вправо - тогда надо сделать перекос типа x += kxy, подобрав k по худшей из точек.
  4. Пункты 2,3 можно провернуть с уже перенесенным сечением, или исправить габарит до трапеции, вынести его на фото, и повторно перенести профиль.
    Обработка спотыканий пока не написана, но в целом, видимо, идея рабочая, буду переписывать начисто.

@zvezdochiot
Copy link
Member

zvezdochiot commented Feb 13, 2025

Hi @noobie-iv .

На всякий. Устранение неправильной растеризации:

// Called for points where pixel density reaches the lower or upper threshold.
auto const processCriticalPoint =
[&generatrix, &dst_y_range, model_domain_top, model_domain_height]
(double model_y, bool upper_threshold)
{
if (!generatrix.pln2img.mirrorSide(model_y))
{
double const dst_y = model_domain_top + model_y * model_domain_height;
double const second_deriv = generatrix.pln2img.secondDerivativeAt(model_y);
if (std::signbit(second_deriv) == upper_threshold)
{
if (dst_y > dst_y_range.first)
{
dst_y_range.first = std::min((int)std::ceil(dst_y), dst_y_range.second);
}
}
else
{
if (dst_y < dst_y_range.second)
{
dst_y_range.second = std::max((int)std::floor(dst_y), dst_y_range.first);
}
}
}
};

заменить на:

        // Called for points where pixel density reaches the lower or upper threshold.
        auto const processCriticalPoint =
            [&generatrix, &dst_y_range, model_domain_top, model_domain_height]
            (double model_y, bool upper_threshold)
        {
/*
            if (!generatrix.pln2img.mirrorSide(model_y))
            {
                double const dst_y = model_domain_top + model_y * model_domain_height;
                double const second_deriv = generatrix.pln2img.secondDerivativeAt(model_y);
                if (std::signbit(second_deriv) == upper_threshold)
                {
                    if (dst_y > dst_y_range.first)
                    {
                        dst_y_range.first = std::min((int)std::ceil(dst_y), dst_y_range.second);
                    }
                }
                else
                {
                    if (dst_y < dst_y_range.second)
                    {
                        dst_y_range.second = std::max((int)std::floor(dst_y), dst_y_range.first);
                    }
                }
            }
*/
        };

отключив этим неправильную растеризацию при выходе разрешения за лимиты.

@noobie-iv
Copy link
Member Author

@zvezdochiot

На всякий

Круто, это минус несколько дней поисков. Боюсь только, что на стадии прикручивания границ все это придется переделывать с нуля.

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

Любимый неисправленный глюк на сегодня - в коммите a40f54c (.2.14: rename from STU to STD). После замены "universal" на "deviant" слетел UI: плавающие панели перестали плавать и прилипли, а стили по умолчанию больше не грузятся - это то, на что в руборде жаловались "стиль win98". У меня даже нет идей, что вообще могло сломаться.

@noobie-iv
Copy link
Member Author

@zvezdochiot

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

Можно посмотреть тестовый набор set0005, у него Fov=1.35; при ручном вводе Fov развертка получается близко к той, что хотел - чтобы фото с телефона в автомате обрабатывать.

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

@zvezdochiot
Copy link
Member

@noobie-iv say:

Вот, минимально криво-рабочий интерфейс к формулам

При отключении ограничения по разрешению растеризация происходит верно, но "хвосты" такие, что закачаешься. Без отключения - результат на некотрых изображениях set0001 кривой. Как быть? Надо это как то решать.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Как быть?

Обычно слева и справа от текстового блока - поля, которые все равно потом отрезать. Слева-справа планирую жесткую обрезку в настройках (блок "поля", пока не активирован).

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

А главное, что дает хвосты - это точка схода, попавшая на лист. Но к точке схода и увеличение при развертке в бесконечность уходит. Возможно, тут ограничение по масштабированию поможет. Увеличение в 10 раз все равно не восстановит мелкие буквы, так зачем его делать.

@zvezdochiot
Copy link
Member

zvezdochiot commented Feb 18, 2025

@noobie-iv say:

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

Давай ещё раз проясним по порядку. На Perspective-0013 из set0001 хвосты немеренные независимо от применения/неприменения патча. Патча лмшь убирает неправильную растеризацию в уже установленных границах. Для отсечения границ в исходной версии служила такая же функция в DewarpingImageTransform.cpp (то самое отсечение по разрешению или увеличению), но не в твоём варианте. Такие вот дела.

Или для новой модели надо проект пересоздавать? Я использую проект, созданный при выходе первой бэтки.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Я еще не добрался до растеризатора.

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

Потом - размеры.

А растеризатор на закуску. Он глючит вроде как только на специфических тестах, с сильной перспективой; на реальных фото не ловил. Титры к звездным войнам сойдут за реальный тест (это аналог 0013)?

Или для новой модели надо проект пересоздавать?

Это еще одна идея на 100 багов, кроме хвостов. При загрузке старых проектов настройки грузятся умолчальные. И в OptionsWidget есть pageParams, которые не бывают "неопределенными"; причем первая загрузка интерфейса в preUpdateUi происходит до того, как картинку первый раз открывают - отсюда глюкавые значения, которые видны на стадии обработки, до получения результатов. И еще хз сколько таких насекомышей там сидит.

@zvezdochiot
Copy link
Member

zvezdochiot commented Mar 21, 2025

Hi @noobie-iv .

Просмотрел set0004 в scantailor-testing и заметил важную деталь. В моделях типа:
set0004/Warp-1-0003.png
площадь выгнутой поверхности переходит в площадь ровной поверхности. Но ведь физически между этими двумя поверхностями должна находиться призма, обладающая определёнными характеристиками, потому как именно такое соприкосновение данных плоскостей при равенстве площадей невозможно. Не вносит ли данный "волентаризм" в расчёты "маленькую неточность"?

@noobie-iv
Copy link
Member Author

@zvezdochiot

Эту неточность компенсирует arcLengthMapper - он вычисляет поправку за счет кривизны, и площадь изогнутой поверхности становится-таки больше в расчетах (конкретно, увеличивает ширину листа в ответе). А поправку за счет перспективы вычисляет исправленная 3D-модель. Не правится сейчас только линза. Но, по результатам моих экспериментов, линза тонет на фоне неточностей FOV и положения точек, натыканных мышкой.

Собственно, контроль такой:
В блендере по умолчанию стоит F=50mm и Film=36mm (это явно популярный объектив и размер фотопленки). Значит, у половины тестов FOV = 36/50 = 0.72. У второй половины я поставил Film=24mm, это FOV = 24/50 = 0.48. Вот на них и надо тестировать результаты. Если на выходе квадрат - значит, формулы работают.

Пока остается вопрос с масштабом, он с фото не восстанавливается. По моим тестам, "вписать в исходную площадь" дает больший разброс размеров, чем просто "принять Sx, Sy из модели". Возможно, надо вписывать в искривленную площадь, а не в плоскую.

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

Кстати, еще тестирование развертки показывает, что пороги в STD сейчас слабоваты - они рассчитаны на сканер, а не на фото. Выравнивания освещения в STD теперь недостаточно.

@zvezdochiot
Copy link
Member

@noobie-iv say:

Кстати, еще тестирование развертки показывает, что пороги в STD сейчас слабоваты - они рассчитаны на сканер, а не на фото. Выравнивания освещения в STD теперь недостаточно.

Именно поэтому я и менял в STEX систему автоматического выравнивания освещения с помощью нерегулируемого фильтра "Выровнять освещение" на систему регулируемого управления цветом (набор фильтров + кривые). Автоматика - это конечно хорошо, но только пока она хоть как то управляема.

@zvezdochiot
Copy link
Member

@noobie-iv say:

Эту неточность компенсирует arcLengthMapper

Ещё раз обдумав, понял, что данный вопрос всё равно вызывает смутное беспокойство. И вроде всё понятно. И замена проведена. Но! Недостаток топографического критинизма делает малодоступными для понимания некоторые иррационнальные вещь. Да, сам виноват, что таким родился. Но ежели отстраниться от arcLengthMapper и использовать соединяющую призму, то у неё есть какие-никакие, а параметры. Как минимум глубина. А какая глубина? Можно сказать, что неважно. Но неважно ли? Вот такие вот сомнения.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Глубина вычисляется переносом профиля с фото на плоскость сечения. Фактически, формулы сейчас восстанавливают 3D-модель листа, положение камеры, и делают по ним обратную трассировку. Это получается двойная коррекция - и выгиба, и перспективы. В 3D-моделировании так делают текстурирование моделей по фото.

Разницу между STEX и STD можно увидеть в set0005/03.jpg, там специально страницы сильно выгнуты. Вот STEX кривизну компенсирует, а перспективу - нет:

Image

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

Image

Заодно теперь 4 точек по ширине мало. Чуть недоизгнут сплайн у корешка - и буквы ползут. И, возможно, семплы в сплайне редковаты, надо тестить.

@zvezdochiot
Copy link
Member

@noobie-iv say:

Заодно теперь 4 точек по ширине мало. Чуть недоизгнут сплайн у корешка - и буквы ползут.

Спорный вопрос. 4 точки - это фактически кубический интерполянт. Только его автоматика будет ловить более-менее. Для более сложных (смешанных) изгибов в любом случае нужна ручная доводка.

@zvezdochiot
Copy link
Member

@noobie-iv .

У меня очередное "прозрение" (не без корысти, конечно). Ежели на второй этап добавить TrimBox, то это можно использовать для выравнивания страниц между собой. Примитивно, конечно, но на данный момент это будет наименее плохим вариантом для случаев типа set0002.

@noobie-iv
Copy link
Member Author

Допишу сюда, чтобы тут было полное описание алгоритма.

Нашел причину бага с дерганой разверткой в средних FOV. Сейчас расчетное сечение, с которого снимается профиль, выбирается по максимальной проекции единичного габарита. Единичный габарит строится примитивно: в модели по краям сечения задается Z=1, и переносится на картинку. Когда FOV маленький - камера смотрит издалека, и оба габарита видны в удачном ракурсе - тогда выбор между ними правильный. А при увеличении FOV камера приближается, и в какой-то момент проходит мимо одного из габаритов. Тогда проекция габарита улетает в бесконечность (а еще ближе - так вообще меняет знак). Какое сечение ушло в бесконечность - то и принимается в расчет, как самое масштабное. Вот и есть диапазон дальностей, когда в бесконечность попадает неудачное сечение:

Image

Математически это значит, что знаменатель в формуле координат (x,y) для точки габарита на картинке уходит в ноль. В гомографии mdl2img это выглядит так:

[h11 h12 h13 h14][x]
[h21 h22 h23 h24][y]
[h31 h32 h33 h34][z]
                 [1]
h31·x + h32·y + h33·z +  h34·1 = 0

Значит, для любого угла в модели (x,y) = {0,0}, {0,1}, {1,0}, {1,1} можно найти опасное приближение:

       h31·x + h32·y + h34·1
z = (-)---------------------
               h33

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

@zvezdochiot
Copy link
Member

zvezdochiot commented Apr 29, 2025

Привет, Роман (@noobie-iv ).

Подбросили тут пример в RU-BOARD: СканКромсатор (comment)

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

Image

В результате "компромиса" получил следующее: 307f0186-std.pdf

В STEX всё выпрямил, но с глубиной совсем не айс: 307f0186-stex.pdf

Такие вот дела. Советы, рекомендации и просто ИМХО с вашей стороны - всё в прок.

@noobie-iv
Copy link
Member Author

@zvezdochiot

Верх и низ на фото выгнуты примерно одинаково - значит, объектив длиннофокусный. Min FOV, видимо, можно в 0.001 сбросить, и там уже искать.

Углы лежат примено на прямоугольнике. Это область, где ни фокус, ни положение камеры в автомате не снять - в формулах как раз минусы под корнем. А физически это значит, что хз как там книга повернута, длиннофокус - это почти изометрия. И выгиб по той же причине хз какой. Достаточно слегка подвигать углы - будет видно, что все параметры пляшут как попало. Главная проблема - камера ставится по 4 углам + принудительно по фокусу. Как мнимый фокус пошел - получаются битые нормали, камера улетает куда попало и начинает левые проекции строить. Можно ее условно подвигать, назначая фальшивый центр кадра вручную. Но это все без гарантий - только случайно дергать все подряд, пока случайно не получится что надо.

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

@zvezdochiot
Copy link
Member

zvezdochiot commented Apr 30, 2025

@noobie-iv say:

Min FOV, видимо, можно в 0.001 сбросить, и там уже искать.

Премного благодарен за подсказку. Всё получилось:

Image

И кривизна промежуточных и глубина:

PS: RU-BOARD (comment)

@zvezdochiot
Copy link
Member

@noobie-iv say:

чтобы тут было полное описание алгоритма.

Ещё немного про dewarping: Page dewarping от @mzucker .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request process Solution at work
Projects
None yet
Development

No branches or pull requests

4 participants