Недавно мы с научным руководителем задались вопросами: Какая лексика чаще всего встречается в учебнике, а какая появляется всего один раз? Какие упражнения присутствуют чаще – языковые или коммуникативные? Соответствует ли лексика в учебнике заявленному уровню? Сколько всего текстов в учебнике? О чем большинство?

Чтобы дать ответ на все эти вопросы, нам на помощь приходит Python для задач NLP (Natural language processing). Разберем код, решающий задачу форматирования PDF в TXT. Ведь именно хорошо представленные данные позволяют нам проводить качественный и количественный анализ текста.
Алгоритм следующий:
-
Загружаем страницу учебника и конвертируем в PDF.
-
Если изображение плохого качества, препроцессим — улучшая качество(preprocess_image).
-
Используем EasyOCR для распознавания текста.
-
Собираем текст в блоки с номером страницы.
-
Если страница “Битая” — обрабатываем ошибки.
Для того, чтобы наш результат был максимально точным используем OCR (Optical Character Recognition), предназначенного для оптического распознавания текста, даже самый «сложный» PDF-учебник можно быстро превратить в текстовый файл.
Для чего это нужно:
-
Учитель получает готовый текст для анализа словарного материала.
-
Разработчик видит пример работы с PDF, изображениями, OCR и многопоточностью.
-
Методист получает инструмент для проверки учебников и сопоставления лексики между изданиями.
Реализация
Для начала необходимо импортировать библиотеки для работы с файлами, потоками, временем (os, io, time), а также библиотеку concurrent.futures для многопоточных задач, а также: numpy, cv2 (про open cv и его возможности). Для работы с PDF необходимы fitz и easyocr для извлечения текстов с картинок и PIL для работы с изображениями.
Пример для Visual Code (version 3.11.9.).
Начнем с конфигурации:
#Конфигурация PDF_PATH = r"C:\Учебники\Кузовлев_3кл.pdf" # путь к учебнику OUTPUT_TXT = os.path.join(os.path.dirname(PDF_PATH), "kuzovlev_output.txt") LANGUAGES = ['en', 'ru'] THREADS = 4 # количество потоков. можно менять числа, но распознавание не особо улучшается USE_GPU = True #GPU для ускорения обработки PREPROCESS_IMAGES = True #улучшение качества картинок
Препроцессинг изображения
Если с конфигурацией всё достаточно прозрачно, то дальше об основной части кода. Чтобы повысить точность распознавания текста, необходимо улучшить качество изображения. Функция предобработки текста переводит картинку в градации серого (для уменьшения шума), увеличивает масштаб. нормализует яркость и методом Оцу применяет бинаризацию, чтобы изображение стало более контрастным и читаемым.
Важно: Если скан бледный(ура, у учебников это в большинстве случаев), то пиксели занимают узкий диапазон 50–180 и буквы не четко отделяются. Растяжение даёт полный 0–255 диапазон, повышая контраст. Однако возможна ошибка деления на ноль, если img.max() == img.min() (однотонная картинка). Поэтому необходима нормализация яркости— линейное растяжение контрастного диапазона (contrast stretching).
Альтернативы: cv2.normalize, skimage.exposure.rescale_intensity, CLAHE (локальное усиление контраста).
#Улучшение качества изображения def preprocess_image(img): img = img.convert('L') # перевод в градации серого img = img.resize((img.width 2, img.height 2), Image.LANCZOS) # масштабирование ×2 с сохранением качества img = np.array(img) #необходим перевод PIL.Image в NumPy-массив, потому что OpenCV функции работают с массивами. # нормализация яркости (растяжение контраста до 0–255) img = img.astype(np.float32) img = (img - img.min()) * (255 / (img.max() - img.min())) img = np.clip(img, 0, 255).astype(np.uint8) # бинаризация (OTSU подбирает оптимальный порог) img = cv2.threshold(img, 0, 255, cv2.THRESHBINARY + cv2.THRESH_OTSU) return Image.fromarray(img)
Конвертация PDF в текст
Функция обработки страницы отвечает за извлечение текста с одной страницы PDF. Она загружает страницу по номеру и конвертирует её в изображение с высоким разрешением (300 DPI), затем превращает это изображение в пискельный объект. При включённой опции предобработки изображение улучшается: переводится в градации серого, масштабируется, нормализуется яркость и бинаризуется методом Оцу (об этом читать выше) для лучшего распознавания. Далее изображение передаётся в EasyOCR, который распознаёт текст блоками, объединяет строки в абзацы и игнорирует мелкие элементы.
Функция возвращает текст с нумерацией страниц и аккуратно обрабатывает возможные ошибки, чтобы скрипт не прерывался на проблемных страницах.
#Обработка одной страницы def process_page(args): page_num, doc, reader = args try: page = doc.load_page(page_num) pix = page.get_pixmap(dpi=300) img_data = Image.open(io.BytesIO(pix.tobytes("png"))) if PREPROCESS_IMAGES: img_data = preprocess_image(img_data) result = reader.readtext(np.array(img_data), batch_size=10, #обрабатываем блоками для ускорения detail=0, #возвращаем только текст, без координат paragraph=True, #paragraph=True — объединяем строки в абзацы min_size=10, #min_size=10 — минимальный размер текста для распознавания contrast_ths=0.3) #порог чувствительности к контрасту return f"\n=== Страница {page_num+1} ===\n" + "\n".join(result) except Exception as e: return f"\nОшибка на странице {page_num+1}: {str(e)}"
Основной код
Спойлер: В начале инициализируется OCR-модель и открывается документ, затем подсчитывается количество страниц. Обработка страниц выполняется параллельно через ThreadPoolExecutor (класс из модуля concurrent.futures), где каждая страница распознаётся предыдущей функцией process_page. Результаты собираются в список, сохраняются в текстовый файл и выводится прогресс обработки. В конце показывается затраченное время и средняя скорость распознавания страниц.
#Главная функция def main(): #Запоминаем время начала выполнения программы для последующего расчета времени обработки start_time = time.time() #Выводим сообщение о начале инициализации EasyOCR print("Инициализация EasyOCR...") #Создаем объект reader для распознавания текста с использованием EasyOCR # LANGUAGES - список языков, которые будут использоваться для распознавания # USE_GPU - флаг, указывающий, использовать ли GPU для ускорения обработки # model_storage_directory - путь к директории хранения модели (None означает использование стандартной) # download_enabled - если True, разрешает автоматическую загрузку моделей (False отключает) reader = easyocr.Reader(LANGUAGES, gpu=USE_GPU, model_storage_directory=None, download_enabled=False) #Открываем PDF-документ по заданному пути doc = fitz.open(PDF_PATH) #Получаем общее количество страниц в документе total_pages = len(doc) print(f"Начало обработки {total_pages} страниц...") #Список для хранения результатов обработки страниц results = [] #Создаем пул потоков для параллельной обработки страниц with concurrent.futures.ThreadPoolExecutor(max_workers=THREADS) as executor: #Отправляем задачи на обработку каждой страницы в пул потоков futures = [executor.submit(process_page, (page_num, doc, reader)) for page_num in range(total_pages)] #Обрабатываем завершенные задачи по мере их завершения for i, future in enumerate(concurrent.futures.as_completed(futures), 1): #Получаем результат обработки страницы и добавляем его в список результатов results.append(future.result()) #Выводим информацию о количестве обработанных страниц, обновляя строку в терминале print(f"Обработано {i}/{total_pages} страниц", end='\r') #Открываем файл для записи результатов в текстовом формате с кодировкой UTF-8 with open(OUTPUT_TXT, "w", encoding="utf-8") as f: #Записываем все результаты, разделенные новой строкой f.write("\n".join(results)) #Вычисляем общее время выполнения программы elapsed = time.time() - start_time print(f"\nГотово! Время обработки: {elapsed//60:.0f} мин {elapsed%60:.2f} сек") print(f"Средняя скорость: {total_pages/(elapsed/60):.1f} страниц/мин") print(f"Результат сохранен в: {OUTPUT_TXT}") #Проверяем, что этот файл запускается как основная программа if __name__ == "__main__": main()
Итого

Так, Python, EasyOCR и простые приёмы предобработки изображений позволяют превратить даже PDF-учебник в удобный текстовый файл для дальнейшего лингвистического анализа (частота встречаемости лексики, типы упражнений, составление учебных корпусов текстов и т.д.).
В следующей статье разберём методы предобработки текста: очистку, нормализацию, лемматизацию и другие шаги, которые делают данные более удобными для последующих nlp и ml движений, уже напрямую связанных с анализом.
Ссылки на нас: Git проекта , EduText Analyzer , Тг-канал
ссылка на оригинал статьи https://habr.com/ru/articles/942866/
Добавить комментарий