1
Технический / Re: Технические вопросы работы форума
« : 29 јРав 2014, 19:58:26 »
Почистил спам, обновил движок форума, сменил контрольный вопрос при регистрации на форуме.
В этом разделе можно просмотреть все сообщения, сделанные этим пользователем.
Здравствуйте. Попросили передать)Мне нужен исходный скан - чтобы воспроизвести проблему.
Выкладываются ли изменения STF с помощью системы контроля версий на SF или github, чтобы можно было посмотреть, что и как менялось в более удобном формате, через тот же diff3, чем пробираясь через лес комментариев (которые в таком случае вообще оказались бы ненужными)?Нет. Как я уже не раз ранее говорил - нет у меня на это времени.
И еще по экспорту:Всё это правильно, конечно, но руки не доходят до эргономики. Я один столько работы не осилю.
Этого требует выбранная мной модель искажения.
Я имел в виду, что вертикальные границы *изображения* - это не то же самое, что вертикальные границы *контента*. Поэтому нельзя первое использовать там, где ожидается второе.
Да, потому что нам нужна граница контента, а не конкретных строк. В результате правда короткая строка станет плохим кандидатом на роль репрезентативной кривой (так как ее придется удлинять до вертикальной границы), но это лучше, чем обратная ситуация, когда часть строки, обрезанная вертикальной границей не будет распрямлена совсем.
Именно так. Сейчас это побочный эффект от подгонки сплайнов.
Тем самым вы изменяете вертикальную границу. Лучше бы это делать *до* трассировки кривых, поскольку вертикальные границы влияют на трассировку.
Ну и еще это поломает коррекцию перспективы, если вдруг кто снимает фотоаппаратом с рук.
Там кроме вертикальных границ видимо повлияли "плохие" края книги. Помните я говорил, что край страницы - это не то же самое, что край книги, потому как на краю книги виден целый гребень из краев разных страниц.
Это потребует серьезных изменений в архитектуру. Даже я бы не рискнул за такое браться. Видите ли в чем дело: деварпинг должен породить модель искажений. Эта модель может делать и вращения тоже - лишь бы модель была правильной. Класс RasterDewarper берет исходное изображение, берет модель искажений и производит готовое распрямленное изображение. Если после этого нужно дополнительное вращение - значит, модель искажений была неправильная.
Я имею в виду конкретную модель с конкретным позиционированием кривых и вертикальных границ. Можно конечно попробовать повернуть результат RasterDewarper, но потом придется пересчитывать размеры полей и тому подобную хрень. В общем исправить модель будет проще, чем делать дополнительный деварпинг. Взгляд с другой стороны: если дополнительное вращение не было нужно при ручном деварпинге - почему оно нужно при автоматическом? Почему автоматический деварпинг не может породить такую же модель, которая была построена вручную.
А скажите, кроме меня ещё кто-то фиксы Вам шлёт по СТ?На руборде ещё.
OutputGenerator::processWithDewarping(.....
......
//begin of modified by monday2000
//Marginal_Dewarping
}
else if (dewarping_mode == DewarpingMode::MARGINAL)
{
BinaryThreshold bw_threshold(64);
BinaryImage bw_image(input.grayImage(), bw_threshold);
QTransform transform = m_xform.preRotation().transform(bw_image.size());
QTransform inv_transform = transform.inverted();
int degrees = m_xform.preRotation().toDegrees();
bw_image = orthogonalRotation(bw_image, degrees);
setupTrivialDistortionModel(distortion_model);
PageId const& pageId = *p_pageId;
int max_red_points = 5; //the more the curling the more this value
XSpline top_spline;
std::vector<QPointF> const& top_polyline = distortion_model.topCurve().polyline();
QLineF const top_line(transform.map(top_polyline.front()), transform.map(top_polyline.back()));
top_spline.appendControlPoint(top_line.p1(), 0);
if (pageId.subPage() == PageId::SINGLE_PAGE || pageId.subPage() == PageId::LEFT_PAGE)
{
for (int i=29-max_red_points; i<29; i++)
top_spline.appendControlPoint(top_line.pointAt((float)i/29.0), 1);
}
else
{
for (int i=1; i<=max_red_points; i++)
top_spline.appendControlPoint(top_line.pointAt((float)i/29.0), 1);
}
top_spline.appendControlPoint(top_line.p2(), 0);
for (int i=0; i<=top_spline.numSegments(); i++) movePointToTopMargin(bw_image, top_spline, i);
for (int i=0; i<=top_spline.numSegments(); i++)
top_spline.moveControlPoint(i,inv_transform.map(top_spline.controlPointPosition(i)));
distortion_model.setTopCurve(dewarping::Curve(top_spline));
//bottom:
XSpline bottom_spline;
std::vector<QPointF> const& bottom_polyline = distortion_model.bottomCurve().polyline();
QLineF const bottom_line(transform.map(bottom_polyline.front()), transform.map(bottom_polyline.back()));
bottom_spline.appendControlPoint(bottom_line.p1(), 0);
if (pageId.subPage() == PageId::SINGLE_PAGE || pageId.subPage() == PageId::LEFT_PAGE)
{
for (int i=29-max_red_points; i<29; i++)
bottom_spline.appendControlPoint(top_line.pointAt((float)i/29.0), 1);
}
else
{
for (int i=1; i<=max_red_points; i++)
bottom_spline.appendControlPoint(top_line.pointAt((float)i/29.0), 1);
}
bottom_spline.appendControlPoint(bottom_line.p2(), 0);
for (int i=0; i<=bottom_spline.numSegments(); i++) movePointToBottomMargin(bw_image, bottom_spline, i);
for (int i=0; i<=bottom_spline.numSegments(); i++)
bottom_spline.moveControlPoint(i,inv_transform.map(bottom_spline.controlPointPosition(i)));
distortion_model.setBottomCurve(dewarping::Curve(bottom_spline));
if (!distortion_model.isValid()) {
setupTrivialDistortionModel(distortion_model);
}
if (dbg) {
QImage out_image(bw_image.toQImage().convertToFormat(QImage::Format_RGB32));
for (int i=0; i<=top_spline.numSegments(); i++) drawPoint(out_image, top_spline.controlPointPosition(i));
for (int i=0; i<=bottom_spline.numSegments(); i++) drawPoint(out_image, bottom_spline.controlPointPosition(i));
dbg->add(out_image, "marginal dewarping");
}
//end of modified by monday2000
}
//begin of modified by monday2000
//Marginal_Dewarping
void
OutputGenerator::movePointToTopMargin(BinaryImage& bw_image, XSpline& spline, int idx) const //added
{
QPointF pos = spline.controlPointPosition(idx);
for (int j=0; j<pos.y(); j++)
{
if (bw_image.getPixel(pos.x(),j) == WHITE)
{
int count = 0;
int check_num = 16;
for (int jj=j; jj<(j+check_num); jj++)
{
if (bw_image.getPixel(pos.x(),jj) == WHITE)
count++;
}
if (count == check_num)
{
pos.setY(j);
spline.moveControlPoint(idx,pos);
break;
}
}
}
}
void
OutputGenerator::movePointToBottomMargin(BinaryImage& bw_image, XSpline& spline, int idx) const //added
{
QPointF pos = spline.controlPointPosition(idx);
for (int j=bw_image.height()-1; j>pos.y(); j--)
{
if (bw_image.getPixel(pos.x(),j) == WHITE)
{
int count = 0;
int check_num = 16;
for (int jj=j; jj>(j-check_num); jj--)
{
if (bw_image.getPixel(pos.x(),jj) == WHITE)
count++;
}
if (count == check_num)
{
pos.setY(j);
spline.moveControlPoint(idx,pos);
break;
}
}
}
}
void
OutputGenerator::drawPoint(QImage& image, QPointF const& pt) const
{
QPoint pts = pt.toPoint();
for (int i=pts.x()-10;i<pts.x()+10;i++)
{
for (int j=pts.y()-10;j<pts.y()+10;j++)
{
QPoint p1(i,j);
image.setPixel(p1, qRgb(255, 0, 0));
}
}
}
//end of modified by monday2000
Сделать-то можно, только не будет оно хорошо работать. Причина в том, что нет хорошего алгоритма нахождения вертикальных границ контента. Существующий алгоритм предполагает, что весь мусор уже был обрезан рамкой контента.
Сетка переносится без особых проблем. Берется неискженная сетка
и искажается с помощью объекта DewarpingPointMapper. Для его построения нужна DistortionModel - это верхняя и нижняя кривая плюс две линии - левая и
правая граница контента.
Советую начать как раз с определения вертикальных границ (DetectVertContentBounds.cpp). Все остальное зависит он него. Потом продвигайтесь в сторону трассировки строк текста (TextLineTracer.cpp, TextLineRefiner.cpp, TopBottomLineTracer.cpp).
Со сплайнами всегда так. На любую синюю точку влияют аж 4 соседние красные точки. По-другому никак - сплайну нужно поддерживать одинаковость первых двух производных на границах сегментов.
Синяя сетка - это всего лишь сэмплинг модели искажения. Захочется больше узлов - просто измените константу.
Можно вообще отказаться от сплайнов и работать с ломанными (polylines). Понятно, что в этом случае узлов нужно будет гораздо больше.
Это весьма простая модель, которая не справится с сильным изгибом.
Я не использую квадратичные сплайны. В моей схеме:
1. Делается грубое обнаружение строк. Получаем polyline. О плавности на
этом этапе речь не идет.
2. С помощью сдвоенных змей (coupled snakes) улучшаем наш polyline.
3. Подгоняем (fitting) X-spline произвольной сложности, даже не столько ради
дополнительной плавности (хотя это тоже), сколько для возможности ручной правки.
4. Выбираем пару лучших сплайнов (чем они дальше друг от друга, тем
лучше), а остальные отбрасываем.
В общем, если бы не ручное редактирование, сплайнов у меня совсем бы не было.
Змеи дают вполне достаточную гладкость. Идею со змеями я позаимствовал
отсюда:
http://iupr1.cs.uni-kl.de/~shared/publications/2009-bukhari-cbdar-dewarping-document-image.pdf
Общую модель искажений - отсюда:
http://pdf.aminer.org/000/292/904/a_cylindrical_surface_model_to_rectify_the_bound_document_image.pdf
От себя добавил коррекцию перспективы (homographic transform).
Ее можно использовать, если в качестве кривых брать не линии текста,
а верхнюю и нижнюю границы страницы. Я их кстати ищу в TopBottomLineTracer.cpp, но их может и не быть на скане. Строки текста сильно не доходят до этой линии, так что я не пытался искать и использовать именно эту линию. Так или иначе, с противоположной границей все еще сложнее.
Как я уже сказал выше, я их тоже использую, если TopBottomLineTracer их находит.
Для начала нужно откуда-то получить параметры модели, а именно две
кривые (просто набор точек - polyline) и параметр depth perception.
Предполагается, что если соединить первые точки этих двух кривых, получим левую вертикальную границу контента. Если последние точки - тогда правую.
Строго говоря DistortionModel вам не обязательно использовать. Он
существует главным образом для сохранения / загрузки из проекта, а также для проверки равенства моделей. Математика деварпинга находится в классе
CylindricalSurfaceDewarper, который параметризуется не DistortionModel, а отдельными его элементами.
Curve - это что-то типа union { Spline; Polyline }. Есть Spline - хорошо. Нет Spline - сойдет и Polyline. Опять-же, Curve существует главным образом для сохранения в проект и для проверки одинаковости.
CylindricalSurfaceDewarper принимает не XSpline и не Curve, а просто std::vector<QPointF>.
X-spline достаточно новый сплайн - в 90х его по моему придумали. Используют в основном в видео-редакторах для интерполяции выделения между ключевыми кадрами. Я про него только потому и знаю, что на работе мы как раз пишем видеоредактор.
А вообще он гораздо мощнее B-spline'а по многим параметрам.
Красные точки - это контрольные точки XSpline, но програмно я бы стал
работать с Polyline, поскольку там все ясно и предсказуемо. Повторю, что если бы не необходимость визуального редактирования, никаких сплайнов я бы вообще делать не стал. Сплайн все равно в конечном итоге конвертируется
(сэмплируется) в polyline (то есть в просто набор точек).
А это как раз то, что делает X-Spline мощнее B-Spline. Если tension <= 0,
то кривая будет проходить через данную контрольную точку. То есть красная
точка будет на синей кривой. Чем ближе tension к -1, тем более сглаженным будет проход.
При tension = 0 проход будет совсем угловатым, как в многоугольнике.
При tension > 0 кривая уже не будет проходить через контрольную точку, но
будет как бы притягиваться к ней пружиной. Чем ближе tension к единице, тем
слабее пружина. Те XSpline, которые внутри Curve - у них у крайних точек tension 0, а у остальных 1. Почему не -1? Работать бы работало, но нужно было бы больше контрольных точек для достижения нужного эффекта. Опять же, вам класс Curve не особенно нужен, так что вам никто не мешает заиметь свой XSpline со своими параметрами. А можно и вовсе без него обойтись.
Синяя сетка - понятие чисто эфемерное. Есть модель, которая может варпить или деварпить любую точку. Мы ей говорим - вот тебе матрица 30x30 точек в виде неискаженной решетки. Искази каждую из этих точек, а мы это потом нарисуем поверх изображения, соединяя точки (для простоты) прямыми линиями.
Размеры матрицы задаются в файле DewarpingView.cpp:
int const num_vert_grid_lines = 30;
int const num_hor_grid_lines = 30;
Не парьтесь со сплайнами - они для людей, а не для программ. Генерируйте простой polyline в виде набора точек.
От CylindricalSurfaceDewarper вы все равно никуда не убежите, а вот
DistortionModel не особенно нужна, если переносить dewarping
в консольную программу.
Я бы рекомендовал сразу выкидывать XSpline из объекта Curve, и впоследствии работать только с polyline. То есть когда закончите строить свою DistortionModel, XSpline'а в ней не будет вообще - только polyline.
Кстати прилично работающий алгоритм для подгонки сплайнов был изобретен только в 2002 году, а мне пришлось серьезно подучить матанализ, чтобы его
реализовать.
Так или иначе, вы сделали как я советовал - забить на XSpline и работать
только с polyline.
Только если вас чем-то не устраивает авто-подогнанный сплайн.
Там происходит ровно то же самое, что и у вас - автоподгонка сплайна.
Количество контрольных точек этого сплайна зашито в код:
int const initial_spline_points = 5;
в DistortionModelBuilder.cpp
Деварпинг от Rob'а больше похож на деварпинг от Leptonica, чем на тот,
что в Scan Tailor'е. И у Rob'а (точно) и в Leptonica (не уверен, но догадываюсь),
если хоть одна линия текста была плохо протрассирована, результат будет паршивым.
В ST есть неплохая вероятность, что эта плохая линия не будет выбрана в качестве одной из двух репрезентативных. Еще у Rob'а гораздо более сложная
математическая модель сплайна. Если в Leptonica это просто сегмент параболы, то у Rob'а - сложная нелинейная функция, в которой есть и степени, и синус, и чего там только нет.
В результате приходится использовать сложный алгоритм нелинейной
оптимизации Levenberg-Marquard, который весьма неторопливо работает, и не гарантирует (как и другие алгоритмы) нахождения глобального оптимума.
Не добавляет. Процедура подгонки сплайна работает так:
Создается сплайн в виде прямой линии, начинающийся и заканчивающийся там же, где и polyline. Количество контрольных точек (тех самых, которые красные)
и соответствующие им значения tension выбираются заранее. Начальное
положение контрольных точек - равномерно по прямой. Про значения tension я уже писал.
Затем алгоритм подгонки начинает двигать существующие контрольные точки,
чтобы приблизить форму сплайна к форме polyline.
Нет. В этом весь смысл. Чем больше точек в polyline, тем лучше, но это
будет невозможно редактировать вручную. Поэтому к polyline подгоняется сплайн, у которого всего несколько контрольных точек. После ручного редактирования сплайна, он конвертируется (сэмплируется) обратно в polyline.
Сплайн с двумя контрольными точками не будет иметь кривизны - это просто отрезок прямой. Насколько я помню - initNewSpline используется только для ручного редактирования результата авто-деварпинга. Авто-деварпинг все равно будет генерировать сплайны с 5 контрольными точками. Число 5 выбрано
потому, что 4 не хватало при сильном искривлении. А в общем - чем меньше, тем лучше.
Существующий механизм автоподгонки сплайнов все сделает за вас. Просто конструируйте Curve на основе polyline, а когда дело дойдет до ручного
редактирования, к ней будет подогнан сплайн.
У polyline - чем больше точек, тем лучше (потомучто будет лучше качество деварпинга, но производительность может пострадать), а у spline - чем меньше, тем лучше, потому-что будет проще редактировать вручную. Для polyline push_back подойдет. У XSpline свой API.
Алгоритм автоподгонки все сделает за вас (кроме определения оптимального количества красных точек).
Никакой, но неявно подразумевается плавность изгибов, а то деварпинг
получится угловатый.
DistortionModel - это всего-лишь пара объектов Curve. Объект Curve можно построить из XSpline. Ну а XSpline можно построить путем планомерного
добавления контрольных точек. Но осторожнее с параметром tension у контрольных точек - при загрузке проекта делается предположение, что у крайних точек tension будет 0, а у остальных 1. Но tension равный 1, это approximating режим, а в этом режиме контрольные точки будут не на сплайне! То есть то, что вы пытаетесь сделать, работать не будет. Вот если
в Curve::deserializeXSpline() поменять tension с 1 на -1, тогда может и будет, если в других местах нет подобных предположений.
Красные точки и не должны быть на линии, при этом сплайн, порожденный ими - будет. Сплайн, контрольные точки которого всегда находятся на самом сплайне называется interpolating spline. У которого не находятся - approximating spline. X-spline может работать в обоих режимах, чего кстати не умеет ни один другой сплайн. Более того, можно часть контрольных
точек сделать interpolating, а другую часть approximating. Контролируется это параметром tension, про который я уже писал. Так вот, СТ конфигурирует две
крайние контрольные (красные) точки как interpolating, а остальные - как
approximating. Мог бы я все точки сделать interpolating? Мог, но тогда 5ти
не хватило бы для сильных искривлений, так что пришлось бы делать 6 или 7.
Надо улучшать определение вертикальных границ, при этом не используя рамку
контента. Это позволит в будущем перенести деварпинг на стадию Deskew.
Сложных случаев два класса:
1. Мало строк текста или строки не выровнены по правому краю.
2. Строк хватает, но есть контент, вылезающий за логическую вертикальную
границу.
Тот алгоритм, который сейчас в основной ветке ST, может справиться с
первым случаем (но правда использует рамку контента для очистки мусора по
краям). Для второго класса можно использовать преобразование Хафа, но
такого алгоритма, который работал бы в обоих случаях мне пока придумать не
удалось.
Надо сказать, что ни в одной из статей по деварпингу не уделяется должного внимания определению вертикальных границ. Буквально пару предложений этому посвящают, а все примеры в статьях представляют из себя простые случаи.
Во-первых, они не всегда вертикальные, даже не всегда параллельные. Во-вторых, даже когда они вертикальные, всё равно нужно решать, на какие именно x-координаты их поместить. Будут слишком далеко отстоять от строк текста - сплайн будет норовить сделать изгиб, чтобы достать до вертикальной границы наиболее коротким путем. Будут пересекать контент - отрезанные части вообще не будут распрямляться.
С концами строк тяжело, особенно с левыми концами, когда там заглавная буква, да еще красная строка - начало абзаца. Тем не менее, нет такого требования, чтобы все строки хорошо трассировались. Пары хорошо оттрассированных строк будет вполне достаточно, если они на приличном расстоянии друг от друга. Ну а с верхними / нижними гранями вообще такой проблемы нет, так как грань присутствует по обе стороны от вертикальной границы.
В двух словах - сначала сдвоенные змеи, притягивающиеся к размытому вертикальному компоненту градиента (хорошо видно в дебаг режиме), а затем
подгонка сплайна.
Кстати я вспомнил еще одну причину, по которой я делаю подгонку сплайнов -
она у меня также выполняет роль наращивания кривой, чтобы достать до
вертикальной границы.
Изображение, которое передается в DewarpingView - это самое что ни на есть оригинальное изображение страницы (или разворота) прочитанное с диска.
Оно передается в Task::process() как часть объекта FilterData.
Уже при отрисовке оно обрубается по краям (в зависимости от режима разреза)
и вращается на нужный угол. DistortionModel однако задан именно в координатах исходного, неповернутого и необрезанного изображения, так что обрезать вам его никакого смысла нет, разве-что повернуть на 90 градусов при
необходимости, чисто для удобства.
Добрый день.
Я постараюсь восстановить свой код и прислать его вам на следующей неделе.
Но там сразу будет две проблемы с библиотеками:
1) В коде используется внутренняя библиотека ABBYY (аналог STL) для стандартных объектов (массивы, строки, списки, ...). Ее исходников я дать не могу в любом случае. Так что всю эту систему придется переписать на STL.
2) Для построения скелета изображения вызывается сторонняя библиотека, которой у меня тоже нет. Эту библиотеку для построения скелетов разрабатывал мой научный руководитель, можно узнать у него может ли он предоставить ее исходники.
Правда библиотека строила скелет изображения по запросу и грузила его в бинарный файл, а мой код уже грузил скелет из этого файла. Так что библиотеку построения скелета можно использовать более-менее любую.
По поводу библиотеки построения скелета можно обратиться:
Леонид Местецкий, l.mest@ru.net
Здравствуйте, уважаемый коллега!
Вы не подписываетесь своим именем, поэтому обращаюсь к Вам таким образом.
Исходные коды библиотеки скелетизации я предоставить пока не готов, они написаны "для себя" и слишком сложны для использования коллегами без специальной авторской доработки. В открытом доступе у меня есть эта библиотека в виде DLL, которая может использоваться из си++ и паскаль-программ. Так что если Антон поможет Вам с адаптацией его программы, то с подключением моей библиотеки проблем не будет.
Библиотека находится в разделе Библиотека программ на странице
http://www.machinelearning.ru/wiki/index.php?title=%D0%9D%D0%B5%D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BC%D0%BE%D1%80%D1%84%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5_%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8_%D0%B8_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B_%28%D0%BA%D1%83%D1%80%D1%81_%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D0%B9%2C_%D0%9B.%D0%9C._%D0%9C%D0%B5%D1%81%D1%82%D0%B5%D1%86%D0%BA%D0%B8%D0%B9%29#.D0.91.D0.B8.D0.B1.D0.BB.D0.B8.D0.BE.D1.82.D0.B5.D0.BA.D0.B0_.D0.BF.D1.80.D0.BE.D0.B3.D1.80.D0.B0.D0.BC.D0.BC
С тех пор, как Антон закончил свою работу по этой теме, я ею не занимался. Однако сейчас у меня появился студент, который заинтересовался этой задачей, правда в несколько более узкой постановке, и даже принял участие в программистском соревновании на близкую тему, которое проводится в рамках конференции ICDAR-2013 http://users.iit.demokritos.gr/~alexpap/DISEC13/
Я готов вернуться к исследованиям по этой тематике, если здесь имеются какие-то реальные задачи. Если хотите, мы можем поискать задачу для возможного сотрудничества.
С уважением
Леонид Местецкий
Вот по этой ссылке лежит архив с моей программой.
В архиве 4 папки:
1) batch_xml - несколько тестовых изображений с размеченными тестовыми блоками.
1) Unbender - собственно исходники моего распрямителя. Проект написан по VisualStudio 2008, запускать студию лучше cmd-файлом из корня (Unbender.cmd), тогда все нужные для проекта пути будут проставлены правильно.
2) TranslatorDLL - транслятор данных от библиотеки построения скелета в нормальный формат. Библиотека построения скелета от Местецкого, которая была у меня на выходе выдавала поток данных в каком-то стандартном формате для борландовских библиотек. Пришлось писать транслятор под Borland C++, который брал этот поток и складывал в файл на диск в формате нужном мне. Если сейчас библиотека построения скелета выдает более общеизвестные данные, то транслятор можно будет с чистой совестью выкинуть.
3) WinRelease - это полностью собранный, работающий мой проект со всеми нужными библиотеками. На нем можно смотреть как должна работать моя программа.
По поводу моего кода:
1) Он сейчас не собирается, потому что нет исходников для трех вспомогательных библиотек - FineObj (реализация массивов, списков и других контейнеров), AWL (реализация оконного интерфейса), RLE (реализация RLE представления изображения). По поводу RLE - это представление изображения в виде последовательности черных штрихов. В каждом штрихе записаны его начало и конец. Само изображение - это массив штрихов. Начало новых строк в массиве отмечается специальным штрихом Sentinel_Stroke. Мне кажется что для для такого представления изображения должно быть много реализаций с открытым кодом.
2) Моя программа работает из расчета, что на изображении выделен один большой текстовый блок, который она и тыается распрямить. То есть, к примеру, на большой газетный лист ее запускать бессмысленно. При загрузке изображения программа пытается найти и загрузить из xml-файла с описанием текстового блока на странице. Если описания нет, то программа считает все изображние рабочей областью. После загрузке изображения блок можно поправлять вручную.
3) При работе можно делать обработку изображения по шагам, а можно сразу целиком. Обработка по шагам делалась в основном для тестирования программы и для создания иллюстраций для диссертации, полная обработка должна работать совершенно нормально.
4) Основная логика обработки одного изображения описана в классе CSkeletForm - он создает скелет изображения и обрабатывает изображение по шагам.
Я извиняюсь, но код достаточно мало комментированный и не всегда очень чистый. К сожалению, у меня сейчас нет времени на то, чтобы его переписывать в более удобном виде и на написание подробной документации.
Но я готов отвечать на любые вопросы, когда они возникнут - так что можно смело спрашивать.
С уважением,
Антон.