ShadowGlyph & Coder
Слушай, ты вообще копался, как средневековые писцы прятали сообщения в иллюминированных рукописях, используя шифрование с использованием невидимых чернил? Там такая интересная штука, как они тексты маскировали, используя узоры.
Я пробежался глазами по нескольким этим подсвеченным кодексам. Как позолота наложена, как завитушки в полях обтекают невидимую строчку текста – это словно тихий шифр, спрятанный на виду. То, что кажется украшением, на самом деле зашифрованное слово. Я отслеживал частоту повторений в нескольких томах, и она удивительно стабильна. Забавная головоломка, надо сказать.
Забавная задачка, надо сказать. Может, попробуешь сопоставить плотность цикла с шифром подстановки, или быстро скрипт напишешь, чтобы частоты паттернов автоматически посчитать? Кто-нибудь уже пытался анализ автоматизировать?
Несколько ребят уже подкрутили это дело с помощью Python, используя OpenCV для сканирования пергамента и подсчета штрихов. Они сопоставляют плотность линий с таблицей частот и подают это на решатель подстановок. Пока что сыровато, но код уже может выделять странные узоры, похожие на скрытый текст. Специальной библиотеки нет, только несколько скриптов, которыми поделились на GitHub. Если хочешь покопаться глубже, могу скинуть кусочки кода.
Звучит интересно – если поделишься кусочком кода, я гляну, может, замечу какие-нибудь нюансы или подскажу, как улучшить обнаружение циклов. Просто скинь, что получилось.
Вот набросок, который я использовал в быстром прототипе. Пока что не идеально, но должно дать тебе отправную точку для подсчета циклов и сопоставления частот.
```python
import cv2
import numpy as np
from collections import Counter
# Загружаем высококачественное сканирование рукописи
img = cv2.imread('manuscript.png', cv2.IMREAD_GRAYSCALE)
# Бинаризуем – подкорректируй порог под текстуру бумаги
_, binary = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY_INV)
# Ищем контуры – они часто соответствуют чернильным штрихам
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Считаем циклы: цикл – это контур, содержащий дочерний контур
loop_counts = []
for cnt in contours:
# Приближаем форму для уменьшения шума
approx = cv2.approxPolyDP(cnt, 0.01*cv2.arcLength(cnt, True), True)
area = cv2.contourArea(approx)
if area < 50: # игнорируем мелкие точки
continue
# Грубое эвристическое правило: если контур имеет отверстие, это цикл
mask = np.zeros_like(binary)
cv2.drawContours(mask, [cnt], -1, 255, -1)
holes = cv2.findContours(cv2.bitwise_not(mask), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)[0]
if len(holes) > 1:
loop_counts.append(len(holes)-1)
# Теперь у тебя есть список количества циклов на штрих; сопоставляем с буквами
freq = Counter(loop_counts)
print(freq.most_common())
```
Подстрой порог, отсечку по площади и логику обнаружения отверстий под твои рукописи. Дай знать, что думаешь.
Привет!
Неплохая структура – пара небольших правок может помочь. Во-первых, вместо фиксированного порога в 200 попробуй метод Оцу или адаптивную пороговую обработку; пергамент часто неравномерно освещен, поэтому одно глобальное значение может пропустить слабые чернила. Что касается отсечки по площади, то 50 пикселей – довольно произвольная величина; можно вычислить процентный рейтинг площадей контуров (например, оставлять только верхние 90%), чтобы сохранить крупные штрихи, но при этом убрать мелкие точки. Трюк с подсчетом дыр работает, но в OpenCV иерархия контуров удобнее: когда ты вызываешь `findContours` с `RETR_CCOMP`, сразу получаешь отношение родитель/потомок – просто проверь, есть ли у контура потомок в этой иерархии; это более чистый индикатор "петли". Также, если чернила толстые, у тебя могут появиться вложенные петли; возможно, стоит объединять последовательные дыры в единый счетчик петель. И последнее: можно немного сгладить бинарное изображение медианным фильтром перед определением контуров – это уменьшит шум. Попробуй эти изменения и посмотри, станет ли распределение частот более стабильным на твоих тестовых изображениях.