Тут длинно. Если в кратце, я начал сомневаться, что DjVuLibre/minidjvu кодирует многостраничные документы хуже коммерческих кодировщиков. Он просто жутко медленный. Мотайте до красного - до тестов варианта minidjvu с открученной в нём оптимизацией скорости.
Год назад я решил запихнуть в Scab Tailor Universal еще один этап обработки - создание DjVu документа.
Хотелось достичь следующего:
1. Делать кодирование по методу разделенных сканов (background и foreground отдельно) напрямую из STU, избавив от необходимости делать экспорт и пользоваться DjVu Imager.
2. Позволить устанавливать индивидуальные параметры кодирования для каждой страницы.
3. В реальном времени видеть изменения, чтобы ирать с параметрами.
С прицелом на дальнейшее прикручивание добавления Оглавления и внедрения слоя распознанного текста в hOCR.
Я еще и поддержку различных кодеров вынес в QML (скрипт) плагины, чтобы их без меня могли добавлять. На Linux'ах, а для меня это - основная платформа, все это должно было работать на кодерах пакета DjVuLibre.
В общем, замах был на рубль.
В результате получалось так (кликабельно):
Все работало, странички кодировались в single-page DjVu, бодро отображались и я дошел до сборки их в готовый документ. И тут я обнаружил, что страшно лоханулся. Собираемый утилитой djvm из пакета DjVuLibre bundled multi-page DjVu документ оказался неподъёмно большого размера. Все от того, что он просто составляет djvu страницы стопочкой, но не производит перекодирования их словарей и создания общего словаря.
djvm используется в DjVu Imager (он меня и запутал), но там он потрошит уже готовый bundled multi-page djvu, по сути превращая его в indirect multi-page DjVu. Затем меняет/добавляет чанки картинок в этих отдельных страницах с помощью djvumake и собирает их обратно в bundled multi-page DjVu при помощи djvm. В общем, словари сохраняются as is.
В DjVu Libre никакой возможности создания Djbz словарей я не обнаружил. Проект minidjvu я в тот момент вообще не рассматривал, а может и рассматривал - попробовал, увидел результат и расстроился еще больше.
Делать дефолтным djvu кодировщиком проприетарный кодировщик, который собран только под винду, и тащить зависимость от wine, необходимого для запуска виндовых программ под линуксом - это не в какие ворота не лезет. В итоге я расстроился и забил на весь этот зоопарк кодеров, на данный функционал, и, закинув код в отдельную ветку, прекратил все это делать.
И вот, прошло еще пол года, и неделю назад с мыслью "Да чеж там так всё плохо-то?" я полез разбираться в свободные opensource'ные кодировщики DjVu и DjVu формате вообще.
Я в этом деле профан, могу ошибаться. Вот чему я пришел.Проприетарный кодировщик multi-page документа состоит из зашитого в него сегментатора, кодировщиков и сборщика djvu документа из отдельных чанков в страницы. В пакете DjVuLibre все это представлено россыпью - отдельными консольными утилитами. Всё, кроме сегментатора - его там нет.
Сегментатор отделяет на исходном изображении текст от цветных/серых изображений, чтобы позднее можно было кодировать их отдельно с помощью разных алгоритмов в разные слои страницы. Свободного специализированного opensource'ного сегментатора я не встретил, но он мне нафиг и не нужен был, так как планировалось использовать кодирование по методу раздельных сканов. И вообще - в ST есть свой, по-сути, сегментатор, который авто-поиском изображений и занимается. ST - один большой сегментатор и есть.
Что касается кодировщиков, то я не проверял, но уверен, что разница между кодировщиками видна только при кодировании
текста. Не верю, что авторы в реализации кодеров смогли изобрести что-то новое в кодировании слоев изображений. Они могут иметь разные профили с разными дефолтными параметрами кодирования изображений по
стандартному алгоритму и всё. Но вот менять сам алгоритм - вряд ли. Кроме того, кодирование изображений в single-page и multi-page ничем не отличается - нет там ни словарей и ничего такого, что позволяло бы выиграть от помещения изображений в один файл.
А вот где есть место для творчества - так это при кодирования слоя текста в изображение формата JB2. Там может быть разная организация словаря и вообще есть где развернуться.
Итак, сосредоточимся на кодировщиках для foreground слоев с id Djbz/Sjbz.DjVuLibre не умеет кодировать multi-page документы сходу. Он умеет собирать их из single-page документов, но от этого лучше не становится, т.к. он их просто копирует внутрь без создания общего словаря.
Я полез изучать эти словари. Краткое описание того, как оно все устроено, я приводил недавно тут:
http://publ.lib.ru/cgi/forum/YaBB.pl?num=1559575438Сходу нагуглилась opensource утилита JB2unify, позволяющая не просто собирать single-page DjVu в multi-page DjVu, но и создающая общие словари для них. (
https://github.com/velwant/jb2unify) Результат ее работы оказался недостаточно хорош, т.к. она объединяет в общий словарь только символы
абсолютно идентичные на обеих страницах. А они обычно, хоть на бит, но отличаются. В общем, слишком бесхитростно и соответствующе плохой результат.
Затем я обратил внимание на кодировщик minidjvu, который до этого не рассматривал, т.к. когда я о нем читал, он назван "игрушечным". Цитирую (
http://www.djvu-scan.ru/forum/index.php?topic=52.0): Цитата:Использовать minidjvu v0.8 (MiniDjVu Plus) всерьёз для реального чёрно-белого DjVu-кодирования КРАЙНЕ не рекомендуется!!! (Для этого существуют коммерческие DjVu-кодировщики.)
<...>
Т.е. minidjvu v0.8 (в лице MiniDjVu Plus) - это "игрушечный" чёрно-белый DjVu-кодировщик.
minidjvu умеет кодировать multi-page DjVu из чего угодно, включая single-page DjVu, но есть одно но - только чёрно-белые.
В принципе, больше-то и не нужно, т.к. инструменты DjVuLibre позволяют сделать все остальное. Т.е. minidjvu способен сделать ч./б. multi-page DjVu, в который подобно DjVu Imager все остальное можно допихать. DjVu Imager в общем-то именно через утилиты DjVuLibre и работает.
Попробовал кодировать ч.б. документ minidjvu - получается плохо. Файл слишком большой на выходе.
Вот тут я начал сравнивать его словарь с словарем такого-же документа, кодированным проприетарным кодировщиком. Написал даже утилиту djvudict, чтобы оценивать результаты экспериментов и полез в код minidjvu.
Тут, если еще не читали, можно прочесть про формат JB2 из моего поста
http://publ.lib.ru/cgi/forum/YaBB.pl?num=1559575438 и станет понятно, что Djbz от Sjbz принципиально не отличается
ничем. Вообще.
Djbz в стандарт bundled multi-page DjVu был добавлен позже и, видимо, поэтому его поддержку не реализовали в DjVuLibre сразу (ничем не подтвержденное предположение). Но все алгоритмы, необходимые для составления общих словарей там есть, потому что они же необходимы для составления локального словаря страницы.
Т.о. нельзя сделать single-page кодировщик, который невозможно было бы "легким движением руки" превратить в multi-page кодировщик с поддержкой Djbz словарей. И качество этих словарей не может быть принципиально хуже качества словарей в single-page djvu.
Возникает парадокс, результаты "игрушечного" minidjvu действительно не ахти, а код в нем от "серьезного" DjVuLibre. И я полез этот код смотреть.
Выяснился даже более интересный факт - основанный на DjVuLibre код игрушечного minidjvu чуть ли не полностью этим же DjVulibre включен в себя, и используется именно для организации словаря, но только локального - Sjbz...
Кодирование multi-page документа по сути заключается в следующем:
Спойлер:
1. Берем ч.б. изображение с текстом. Применяем всякие despeckle, если нужно.
2. Режем ее на буквы/символы. Слишком большие откладываем как какую-то графику.
3. Классифицируем все символы на всех страницах (по дефолту пучок из 10 страниц) по классам. Класс объединяет очень похожие символы. Т.е. заглавная "А", строчная "а", греческая альфа, "а" курсивом, "а" другим шрифтом, "а" с грязью - это все разные классы. Это важно, т.к. все вхождения экземпляров класса на страницах будут заменены одним изображением. Вот за счет именно этого достигается львиная доля компрессии. И именно этот этап может породить проблему "ННЙ", если в класс "И" отнесут поврежденную "Н".
4. Смотрим на наши классы, если класс встречается лишь раз, то кодируем тупо в страницу. Если более двух раз появляется на странице, но нет на других - кодируем в локальный словарь страницы (т.е. тупо кодируем в страницу, но помечаем, что символ нужно запомнить и в дальнейшим ссылаемся на него по индексу). Если класс обнаружился более чем на 1й странице - кодируем в общий словарь (т.е. в локальный словарь виртуальной страницы нулевого размера).
5. Для каждого класса выбираем изображение символа, который заменит все его вхождения. minidjvu берет первое попавшееся, но по ключу -A может пробежать по всем изображениям и сделать из них усредненное. Это должно выручать, если потертая Н правильно попала в класс Н, но первым изображением этого класса в кодируемом пучке из 10 страниц встречается именно это потертое изображение.
5. Ищем в рамках страниц (включая виртуальную страницу общего словаря) прототипы для уточняющего кодирования. Важно понимать, что этот этап не может быть источником проблемы "ННЙ", т.к. тут идет lossless кодирование изображений и ноль может уточняюще кодироваться на основе "О" из соображений экономии места, но выглядеть он будет нормально. Судя по-всему, этот этап дает сравнительно малый вклад в сжатие файла по ср. с этапом 3.
6. Пишем все это.
Теперь лезем в код minidjvu, естественно в классификатор - самое главное.
А там (
https://github.com/barak/minidjvu/blob/master/src/alg/classify.c#L179):Code (C++): if (c->last_page < page - 1) continue; /* multipage optimization */
в цикле, объединающем одинаковые классы на разных страницах в один.
Т.е. в угоду скорости поиск осуществляется не по всем кодируемым единым общим словарем страницам, а только по соседним страницам. Если вы нашли "А" на странице 2, но не встретили её на странице 3, то такая же "А" на странице 4 будет ей не равна. Если они были также на страницах 1 и 5, то в общий словарь попадут обе. А если нет, то они останутся в локальных словарях, а то и вообще закодируются в страницу как единичные образцы.
Казалось бы, случай редкий. А давайте потеструем.Комментируем эту строку, компиллируем minidjvu (я обозвал эту версию minidjvu_mod) и сравниваем с оригинальным minidjvu и с documenttodjvum.exe, который я вытащил из djvu_small_v0_4_4 и использую для изготовления книг по методу разделенных сканов с профилем User B&W 600.
Учтем, что documenttodjvum.exe программа виндовая и вызовы ее будут исполняться в Wine, что снизит её производительность.
documenttodjvum.exe передадим ключ --profile=my_600 (подсмотрено в логе DjVu Small).
Дальше я написал bash скрипт, который вызывает их для кодирования всех tiff файлов из папки и сохраняет время работы + размер результата. И посмотрел, сколько у меня в папках со сканами осталось подпапок ./out/export/txt/ (туда STU сохраняет foreground слой при экспорте). Нашлось аж
38
.
Результаты тестирования (кликабельно):
Здесь small - это documenttodjvum.exe из DjVu Small 0.4.4
orig - minidjvu
mod - minidjvu_mod (с отключенной оптимизацией в составлении Djbz)
Проценты размера документа берутся от сравнения с результатом documenttodjvum.exe. Размеры в байтах.
Проценты изменения скорости minidjvu_small берутся от скорости minidjvu (т.к. с documenttodjvum.exe сравнивать бессмысленно).
Признаюсь, что тестов было больше 38, но на двух завис documenttodjvum.exe, а на 2-х не справился minidjvu*, заявив, что tiff'ы не ч./б. Но это скорее всего у меня там tiff'ы с альфа каналом попались. Помню правил что-то такое в STU.
Итого, minidjvu с отключенной оптимизацией скорости, не просто ожидаемо превосходит оригинальный minidjvu везде, но и в среднем отстает от коммерческого documenttodjvum.exe всего на
+0.81%
.
А если выкинуть явный выброс с каким-то одностраничником на 7кб, который documenttodjvum.exe сжал в 2 раза лучше, то minidjvu еще и обойдет documenttodjvum.exe с профилем User B&W 600 на
-1.87%
.
Если рассмотреть вопрос производительности, то "оптимизация" дала minidjvu в среднем 22.85% прирост в скорости, но увеличила размер документа на 23.42% и даже после этого он остался в 6 раз медленнее documenttodjvum.exe Т.о. проблему производительности minidjvu не решил, а качество словаря потерял. (оптимизация, судя по всему, появилась в minidjvu 0.4 от 2005-06-15). DjVuLibre, содержащий minidjvu, естественно никакой оптимизации не содержит, т.к. многостраничные документы кодировать и не пытается.
Отключенная оптимизация не должна повлиять на вероятность возникновения проблемы "ННЙ" (если она вообще возникает - я ни разу с ней не сталкивался, но я OCR не занимаюсь и мог не заметить. Чукча не читатель). Наоборот, если раньше эта проблема могла проявится в масштабе пары страниц, то теперь она расползётся на 10 и ее будет проще обнаружить.
Касательно производительности, мне кажется, в наш век эта медлительность уже не так критична. Вот при царе горохе, когда documenttodjvum.exe кодировал бы что-то 5 минут, а minidjvu бы по пол часа, то да. А при нынешних CPU уже не так страшно. К тому же есть как минимум один тупой способ ускорить все в 2-3 раза - разбить кодирование и кодировать 10-ти страничные словари в 2х-4х параллельных потоках, чтобы они могли разойтись по разным ядрам процессора. Ну и в целом, в коде minidjvu много TODO пометок о неоптимальности кода и необходимости дальнейшей оптимизации.
А вообще, я бы не отказался от сверхмедленного, но сверхэффективного кодировщика. Пусть он хоть всю ночь кодирует (или перекодирует имеющийся документ), но выдаст лучший словарь и файл на 30% меньше коммерческого кодера.
ИтогоУчитывая, что сегментер мне не нужен, что в серьёзную разницу нетекстовых кодировщиков я не верю и они имеются в DjVuLibre, и что minidjvu как кодировщик foreground слоя может показывать результат не хуже коммерческого, то может я и допилю когда нибудь платформу для сборки книжки из STU.
Собранные бинарники модифицированного minidjvu я прилеплю сюда чуть позднее (надо для Win собирать). Но сборку под Винду я настроил только через cygwin и будет куча лишних библиотек, а возможно и еще бОльшая просадка по производительности. Ну, посмотрим. Версии для Win32 не будет.
Все дальнейшие измышлизмы по поводу minidjvu я продолжу писать в этой ветке.