Используй этот навык для оцифровки отсканированных технических документов — методичек, конспектов, учебников, курсовых и лабораторных работ, статей — присланных как PDF (включая сканы без текстового слоя и развороты книги по два листа на одном изображении) или как набор PNG/JPG-фотографий страниц. Задача навыка — сохранить ВЕСЬ текст без потерь, перевести ВСЕ формулы в корректный LaTeX, а графики/номограммы приблизить полиномами с указанием диапазона применимости — а не оставлять картинками или искажённым результатом обычного OCR. Применяй, когда просят «оцифровать скан/методичку», «распознать текст со страниц», «перевести формулы в латех», «снять график полиномом», «сделать конспект из фото», «вытащить текст и формулы из PDF», когда документ содержит математику, физику, химию, инженерные расчёты, графики/номограммы или много формул, а также когда часть страниц — нечёткие фотографии тетради или книги. Для документов без формул и без визуальной сложности достаточно обычного pdftotext — этот навык не для них.
Resources
4Install
npx skillscat add mikiway902/compressors-kurs-part-2 Install via the SkillsCat registry.
Оцифровка сканов технических методичек (PDF/PNG → Markdown + LaTeX)
Что на выходе
Один Markdown-файл, в котором:
- сохранён весь текст документа — без сокращений, пересказа или «причёсывания» формулировок;
- все формулы записаны в LaTeX (
$...$для формул внутри строки,$$...$$для формул на отдельной строке) и реально компилируются, а не остаются картинками; - все графики и характеристические кривые (номограммы, экспериментальные зависимости) приближены полиномами с указанием диапазона применимости — рядом с самим изображением графика, а не вместо него;
- сохранена структура оригинала — заголовки, списки, нумерация формул, таблицы;
- схемы, чертежи и фотографии (то, что не является графиком зависимости одной величины от другой) вырезаны как отдельные изображения и вставлены по месту, либо отмечены подписью, если вырезать не получилось.
Почему не обычный OCR
Tesseract и подобные движки обучены на обычном тексте. На математической нотации они систематически ошибаются: путают надстрочные и подстрочные индексы, теряют дробные черты и корни, превращают греческие буквы в случайные символы, разваливают порядок при многоколоночной вёрстке. Для методички с формулами это не «слегка грязный» результат — это потеря смысла, потому что одна перепутанная степень или индекс делает формулу неверной.
Поэтому здесь формулы не распознаются OCR-движком, а прочитываются и переписываются — так же, как это сделал бы человек, набирающий текст в LaTeX по бумажному оригиналу: посмотреть на страницу, понять, что на ней написано, и записать это с правильной семантикой. Это медленнее, чем прогнать PDF через tesseract, но результат можно реально использовать.
Общая схема работы
- Инвентаризация — посчитать страницы, понять объём работы.
- Нормализация входа — привести PDF и/или набор PNG/JPG к единой последовательности
page_001.png,page_002.png, … - Проверка качества изображений — убедиться, что текст и формулы читаемы; при необходимости пересчитать с большим DPI или выровнять.
- Постраничная транскрипция — для каждой страницы: посмотреть → записать весь текст → перевести формулы в LaTeX → дописать в выходной файл.
- Нетекстовые элементы — рисунки, графики, схемы, таблицы.
- Финальная проверка — прогнать
scripts/check_document.py, перепроверить отмеченные места. - (опционально) Экспорт — Pandoc в .docx или .pdf, если нужен не Markdown, а готовый документ.
Шаг 1. Инвентаризация
Прежде чем что-то конвертировать, пойми объём задачи:
# Для PDF
pdfinfo document.pdf | grep -i pages
pdffonts document.pdf | head -5 # если таблица пустая — это скан без текстового слоя, как и ожидается
# Для набора PNG/JPG
ls -1 photos/ | wc -lЕсли страниц немного (до ~15–20) — можно обрабатывать одним непрерывным проходом. Если документ большой (50, 100, 200+ страниц), сразу предупреди пользователя, что это займёт много шагов, и работай порциями — см. раздел «Большие документы» ниже.
Шаг 2. Нормализация входа
Независимо от того, что прислал пользователь — PDF или папку с фотографиями страниц, — приведи всё к одинаковому виду: пронумерованной последовательности PNG-файлов. Дальше весь процесс не будет зависеть от исходного формата.
python scripts/pdf_to_pages.py document.pdf pages/ --dpi 300
# или, если на входе уже изображения:
python scripts/pdf_to_pages.py photos/ pages/Скрипт сам выбирает доступный способ растеризации (PyMuPDF либо pdftoppm) и пишет pages/manifest.json с числом страниц — это пригодится на финальной проверке.
DPI 300 достаточно для большинства методичек. Если в документе много мелких индексов, подстрочных формул или это фотография книги, а не чистый скан, увеличь до 400–600: --dpi 450. Слишком низкое разрешение — самая частая причина того, что формула прочитана неверно.
Если входные файлы — это уже фотографии (а не скан), их порядок в папке может не совпадать с порядком страниц (имена вида IMG_2031.jpg, IMG_1987.jpg). Прежде чем нормализовать, проверь сортировку и при необходимости переименуй/переставь файлы вручную — скрипт просто берёт файлы по алфавиту.
Шаг 3. Проверка качества изображений
Перед тем как обрабатывать весь документ, посмотри 2–3 показательные страницы (одну с обычным текстом, одну с формулами, одну с рисунком, если есть). Это экономит время: лучше один раз поднять DPI или выровнять скан, чем переделывать транскрипцию 80 страниц.
Типичные проблемы и что с ними делать:
- Текст мелкий или нечёткий → пересчитай страницы с большим
--dpi. - Скан перекошен (страница под углом) → выровняй перед обработкой:
magick page_005.png -deskew 40% page_005.png(если установлен ImageMagick). - Низкий контраст (серая бумага, тень от телефона) →
magick page_005.png -contrast-stretch 2%x2% page_005.png. - Часть формулы видна, но мелкая даже на крупном плане → используй
scripts/crop_region.py, чтобы вырезать и увеличить именно этот фрагмент (см. раздел «Если что-то не читается» ниже).
Разворот книги — два листа на одном изображении. Старые методички часто сканируют, фотографируя раскрытую книгу целиком: на одном файле-странице оказываются сразу два бумажных листа рядом. Узнать это легко — внизу страницы видно два разных номера (например, «18» слева и «19» справа), а посередине изображения проходит шов переплёта или тень от него. Это не то же самое, что вёрстка в две колонки на одном листе (см. ниже) — это буквально два разных листа.
Не все страницы документа обязательно идут разворотами — титульные листы и широкие таблицы на всю ширину снимка часто оказываются одним полноразмерным листом. Поэтому решай для каждой страницы отдельно, а не один раз для всего документа.
Когда это разворот, эффективное разрешение на каждый лист — половина от номинального DPI, поэтому страницы такого типа стоит, во-первых, изначально рендерить с DPI заметно выше обычного (450-600 вместо 300), а во-вторых, перед транскрипцией разбить на два отдельных изображения — левый и правый лист — так с каждым удобнее работать по отдельности:
python scripts/crop_region.py pages/page_006.png /tmp/left.png --frac 0,0,0.5,1
python scripts/crop_region.py pages/page_006.png /tmp/right.png --frac 0.5,0,1,1Транскрибируй левый лист полностью, затем правый, в этом порядке. Маркер <!-- page N --> всё равно ставь один на файл-страницу (N — номер по сквозной нумерации page_NNN.png, как в manifest.json) — а если в самом документе видны печатные номера страниц (например «18» и «19»), упомяни их в тексте сразу после маркера, это поможет потом сверяться с бумажным оригиналом: <!-- page 6 --> <!-- печатные стр. 18-19 -->.
Шаг 4. Постраничная транскрипция
Это основная часть работы. Для каждой страницы:
- Посмотри изображение страницы.
- Перепиши весь текст в Markdown, сохраняя структуру: заголовки разделов как
##/###по уровню вложенности, списки как списки, абзацы как абзацы. - Каждую формулу переведи в LaTeX по правилам из раздела «Формулы → LaTeX» ниже.
- Дозапиши результат страницы в конец общего файла (не держи весь документ в одной операции — пиши по мере прохождения).
- Перед текстом каждой страницы оставь невидимый маркер
<!-- page N -->— он не виден при обычном чтении файла, но по немуcheck_document.pyпроверит, что ни одна страница не потеряна, и по нему легко найти нужное место при правках.
Пример фрагмента результата:
<!-- page 12 -->
## 3.2. Уравнение движения тела под действием силы тяжести
Рассмотрим тело, брошенное вертикально вверх с начальной скоростью $v_0$.
Уравнение движения имеет вид
$$
y(t) = v_0 t - \frac{g t^2}{2}
$$
(3.4)
где $g \approx 9{,}8$ м/с² — ускорение свободного падения.Многоколоночная вёрстка. Если страница свёрстана в две колонки (часто бывает в конспектах и сборниках лекций), читай не построчно через всю ширину страницы, а колонку целиком сверху вниз, и только потом переходи к следующей — иначе фразы из разных колонок склеятся в бессмысленный текст. Не путай это с разворотом книги (см. шаг 3) — там это не колонки одного листа, а буквально два разных листа.
Таблица или схема напечатана боком. Иногда широкую таблицу печатают повёрнутой на 90°, чтобы она влезла на лист книжной ориентации (текст в ней идёт вертикально). Вырежи область с такой таблицей и поверни вырезку, прежде чем читать:
python scripts/crop_region.py pages/page_009.png /tmp/table.png --frac 0.0,0.0,0.25,1.0 --rotate 270Если после поворота текст оказался «вверх ногами» — направление было выбрано неверно, пересохрани с --rotate 90 вместо 270 (или наоборот).
Размер партии. Для большинства методичек обрабатывай страницы по одной — это заметно повышает точность переноса формул, потому что внимание не распределяется между несколькими страницами сразу. Несколько подряд идущих страниц без единой формулы (титульный лист, аннотация, список литературы) можно обработать вместе. Не увеличивай размер партии только ради скорости: пропущенный значок или индекс в формуле обходится дороже, чем лишние секунды на чтение страницы.
Не угадывай нечитаемое. Если фрагмент текста или символ в формуле действительно не разобрать (смазан, перекрыт пятном, обрезан полем), не подставляй то, что «скорее всего там должно быть» — отметь явно: [неразборчиво] в тексте или \text{(?)} внутри формулы. Это честнее и позволяет пользователю целенаправленно проверить именно эти места, вместо того чтобы он доверял незаметно угаданному содержимому.
Если что-то не читается на полном изображении страницы — вырежи и увеличь этот фрагмент перед тем, как помечать его неразборчивым:
python scripts/crop_region.py pages/page_012.png /tmp/zoom.png --frac 0.55,0.30,0.95,0.45 --zoom 3Координаты --frac задаются в долях ширины/высоты изображения (left,top,right,bottom, от 0 до 1) — их легко прикинуть на глаз, глядя на страницу. После этого посмотри /tmp/zoom.png.
Формулы → LaTeX
Базовые правила:
Формула внутри предложения →
$...$. Формула на отдельной строке в оригинале →$$...$$на отдельной строке в Markdown.Если у формулы в оригинале есть номер (например, «(3.4)» справа от формулы), сохрани его — но не используй
\tag{}(он рендерится не везде одинаково). Самый надёжный вариант — обычным текстом сразу под формулой, как в примере выше.Многошаговые выводы (когда в оригинале видна цепочка преобразований «= … = … = …») оформляй через
aligned, а не отдельными формулами для каждого шага:$$ \begin{aligned} v &= v_0 + at \\ &= 5 + 2 \cdot 3 \\ &= 11 \ \text{м/с} \end{aligned} $$Единицы измерения не курсивятся и не входят в формулу как переменные — пиши их обычным текстом рядом с формулой или через
\text{...}внутри неё:$a = 9{,}8\ \text{м/с}^2$, а не$a = 9.8 m/c^2$.Десятичный разделитель в исходном русскоязычном документе — запятая («3,14»). Сохраняй запятую в обычном тексте, но внутри формул LaTeX используй точку (
3.14) или экранированную запятую (3{,}14) — иначе движок отрисовки формул интерпретирует запятую как разделитель элементов списка и добавит лишний пробел.Если переменная в оригинале обозначена кириллической буквой (старая традиция в некоторых учебниках, например «С» для емкости вместо «C»), оборачивай её в
\text{}:\text{С} = 10мкФ — иначе в стандартных математических шрифтах кириллица может отрисоваться некорректно.В термодинамике, газовой динамике и расчётах турбомашин почти всегда встречаются черта сверху для осреднённых величин ($\bar{c}_a$ →
\bar{c}_a) и звёздочка для параметров заторможенного потока ($T^*$ →T^*) — это не опечатки и не степени по смыслу, переноси их как есть.Полную таблицу соответствий (греческие буквы, осреднённые/полные параметры, производные и интегралы, векторы и матрицы, тригонометрия, стрелки, спецсимволы, оформление графиков-полиномов и частые ошибки) смотри в
references/latex-symbols.md. Открывай этот файл, когда встречаешь нотацию, в которой не уверен — не угадывай синтаксис LaTeX по памяти, если есть малейшее сомнение.
Шаг 5. Нетекстовые элементы
Схемы, чертежи, фотографии
Если можно аккуратно вырезать область с рисунком — сделай это и вставь как изображение по месту:
python scripts/crop_region.py pages/page_018.png images/fig_3_2.png --frac 0.10,0.15,0.90,0.55Если вырезать аккуратно не получается (рисунок сложной формы, занимает почти всю страницу, перекрыт текстом с двух сторон) — не пытайся идеально его восстановить, а оставь явную пометку с подписью, которая обычно остаётся читаемой даже когда сам рисунок не воспроизвести:
> **[Рисунок 3.2]** Конструктивная схема узла.Это касается схем, чертежей, конструктивных разрезов, диаграмм треугольников скоростей и фотографий — всего, что описывает форму/устройство, а не численную зависимость одной величины от другой.
Графики и характеристики — перевод в полином
Если на рисунке есть оси с числовой шкалой и кривая, которая выражает зависимость одной величины от другой (характеристика, номограмма, экспериментальная кривая) — такой график не просто вставляется картинкой, а дополнительно приближается полиномом, чтобы зависимостью можно было пользоваться в расчётах, а не только смотреть на неё глазами.
Сначала отличи график от схемы: на одном рисунке методички часто соседствуют схематический чертёж (например, треугольник скоростей) и собственно график — это разные объекты, обрабатывай их по отдельности: чертёж — как обычное изображение (см. выше), график — по схеме ниже.
Вырежи и увеличь именно график, отдельно от соседних схем на том же рисунке:
python scripts/crop_region.py pages/page_028.png images/fig_12.png --frac 0.55,0.05,0.98,0.55 --zoom 2Определи оси: что отложено по горизонтали и вертикали (с обозначением и единицами), числовой диапазон по сетке, и убедись, что шкала линейная (если шкала логарифмическая — это будет видно по неравномерному шагу подписей вроде 1, 10, 100 — в этом случае подбирать обычный полином по сырым значениям бессмысленно, нужно сначала прикинуть точки в логарифмических координатах; такие графики реже встречаются в учебных методичках, но встретиться могут).
Определи, сколько кривых на графике и чем они различаются — одна кривая, семейство кривых с числовым параметром (например, подписи 2; 1,5; 1,0; 0,5 у каждой линии), или кривые, различающиеся типом линии или подписанным названием (сплошная/штриховая, «NACA-65», «Р-45 окр»). Каждую кривую приближай отдельным полиномом — не смешивай точки разных кривых в одну выборку.
Для каждой кривой сними 6-12 точек прямо по сетке графика, в единицах осей (не в пикселях изображения — сетка на чертеже и есть линейка). Точки бери гуще там, где кривая заметно изгибается, и обязательно включи видимые на чертеже концы кривой.
Подбери полином:
python scripts/fit_polynomial.py --points "20,4.0;30,5.2;40,6.8;55,8.5;70,9.6;80,9.4" --auto --var "\\beta_1,i_{0\\Sigma}"Посмотри на
R^2в выводе. Если он заметно меньше 0,99 — либо степень не подходит форме кривой, либо точки сняты неточно; пересмотри то и другое, прежде чем фиксировать результат. Не гонись за более высокой степенью ради идеальногоR^2— лишние степени почти всегда переобучаются под погрешность считывания точек, а не отражают реальную форму кривой; бери низшую степень, которая визуально похожа на кривую и даёт приемлемыйR^2.Запиши результат: изображение графика оставь для наглядности и проверки, а сразу под ним добавь полином (или несколько — по одному на кривую) с указанием диапазона применимости и пометкой, что это считанное по графику приближение, а не формула из оригинала:
 Приближение кривых рис. 12 многочленами (снято по графику, диапазон применимости указан для каждой кривой): при $\bar t/\bar b = 0{,}5$: $$i_{0\Sigma} = -0.0021\,\beta_1^{2} + 0.31\,\beta_1 - 2.1$$ (для $\beta_1 \in [20,\ 80]$) при $\bar t/\bar b = 1{,}0$: $$i_{0\Sigma} = -0.0018\,\beta_1^{2} + 0.27\,\beta_1 - 1.4$$ (для $\beta_1 \in [20,\ 80]$)
Полином, полученный таким способом, — это приближение по нескольким считанным глазом точкам, а не точная оцифровка кривой. Не используй его за пределами указанного диапазона: полином может вести себя совершенно непредсказуемо сразу за границей подобранных точек, даже если внутри диапазона совпадение хорошее.
Таблицы
Переноси как обычные Markdown-таблицы. Формулы внутри ячеек оформляй так же, как в тексте ($...$), включая дроби и индексы, если в оригинале в ячейке таблицы стоит составное выражение, а не просто число. Если в оригинале есть объединённые ячейки, которые Markdown-таблица не умеет выразить — выбери разумное приближение (повтори значение или раздели на несколько строк) и, если это меняет смысл таблицы, кратко поясни в сноске под таблицей.
Шаг 6. Финальная проверка
После того как все страницы обработаны, прогони автоматическую проверку:
python scripts/check_document.py итоговый_файл.md --expected-pages 48Скрипт проверяет баланс $/$$/{} (типичный признак того, что где-то формула не закрыта), сверяет маркеры <!-- page N --> с ожидаемым числом страниц и находит места, помеченные как неразборчивые. Пройди по каждому пункту из отчёта вручную — автоматическая проверка находит подозрительные места, а не гарантирует их правильность.
Дополнительно: если документ важен (диплом, статья, материалы к экзамену), стоит ещё раз внимательно сверить итоговый текст с оригиналом постранично — автоматизация ловит структурные ошибки (пропущенная страница, незакрытая формула), но не семантические (формула технически корректна, но не совпадает с тем, что написано в оригинале).
Шаблон итогового файла
# <Название документа>
> Оцифровано из: `<имя_исходного_файла>` (<N> страниц)
<!-- page 1 -->
...
<!-- page 2 -->
...Сохраняй итог рядом с исходным файлом, с понятным именем: <имя_исходника>_оцифровано.md.
Большие документы (50+ страниц)
Не пытайся обработать всё за один проход. Сообщай пользователю о прогрессе каждые 10–15 страниц. Поскольку результат дозаписывается в файл постранично (шаг 4), а не собирается целиком в памяти, работу всегда можно прервать и продолжить — для этого после паузы посмотри на последний маркер <!-- page N --> в выходном файле и начни со страницы N+1.
(Опционально) Экспорт в Word или PDF
Если пользователю нужен не Markdown, а готовый документ, итоговый файл можно прогнать через Pandoc — он сам превращает $...$/$$...$$ в нормальные формулы Word (OMML) или в LaTeX-формулы при сборке PDF:
pandoc итог.md -o итог.docx
pandoc итог.md -o итог.pdf --pdf-engine=xelatex # нужен установленный LaTeX (texlive)Для .docx это работает «из коробки». Для .pdf нужен установленный дистрибутив LaTeX — если его нет, оставь результат в Markdown и предложи пользователю либо поставить LaTeX, либо использовать .docx.