Виявлення «річки» в тексті


175

На тексах обміну статтею TeX ми обговорювали, як виявити "річки" в параграфах цього питання .

У цьому контексті річки - це смуги білого простору, які виникають внаслідок випадкового вирівнювання міжмовних просторів у тексті. Оскільки це може бути досить відволікає читача, погані річки вважаються симптомом поганої типографії. Прикладом тексту з річками є ця, де дві річки течуть по діагоналі.

введіть тут опис зображення

Існує інтерес до автоматичного виявлення цих річок, щоб уникнути їх (можливо, шляхом ручного редагування тексту). Raphink досягає певного прогресу на рівні TeX (який знає лише положення гліфів та обмежувальні поля), але я впевнений, що найкращий спосіб виявити річки - це деяка обробка зображень (оскільки форми глифів дуже важливі і не доступні TeX) . Я спробував різні способи видобути річки з наведеного зображення, але моя проста ідея застосувати невелику кількість еліпсоїдальних розмиття здається недостатньо хорошою. Я також спробував якийсь РадонХотка фільтрація на основі трансформації, але з ними я ніде не потрапив. Річки дуже добре помітні в ланцюгах виявлення функцій людського ока / сітківки / мозку, і я якось думаю, що це може бути переведено на якусь фільтрувальну операцію, але я не в змозі змусити її працювати. Якісь ідеї?

Щоб бути конкретним, я шукаю деяку операцію, яка виявить дві річки на наведеному вище зображенні, але не матиме занадто багато інших помилкових позитивних виявлень.

EDIT: ендоліт запитав, чому я дотримуюся підходу на основі обробки зображень, враховуючи, що в TeX ми маємо доступ до гліфних позицій, проміжків тощо, і, можливо, набагато швидше і надійніше використовувати алгоритм, який вивчає фактичний текст. Моя причина робити інший спосіб - це формаз гліфів може вплинути на те, наскільки річ помітна, і на рівні тексту дуже важко розглянути цю форму (яка залежить від шрифту, лігатури тощо). Для прикладу того, як форма гліфів може бути важливою, розглянемо наступні два приклади, де різниця між ними полягає в тому, що я замінив кілька гліфів на інші майже однакової ширини, щоб текстовий аналіз міг би врахувати вони однаково хороші / погані. Однак зауважте, що річки в першому прикладі значно гірші, ніж у другому.

введіть тут опис зображення

введіть тут опис зображення


5
+1 Мені це питання подобається. Моя перша думка - це Хоф трансформація , але, ймовірно, знадобиться попередня обробка. Можливо спочатку фільтр розширення .
datageist

Я здивований, що перетворення Радона насправді не спрацювало. Як ти це зробив?
ендоліт

@endolith: Нічого складного. Я використовував ImageLines[]з Mathematica, з попередньою обробкою і без неї. Я думаю, це технічно використовує перетворення Хафа, а не Радона. Я не буду здивований, якщо правильна попередня обробка (я не спробував запропонований фільтром розширення datageist) та / або параметри параметрів зможуть зробити цю роботу.
Левський єпископ

Пошук зображень Google для річок також показує "звивисті" річки. Ви хочете їх знайти? cdn.ilovetypography.com/img/text-river1.gif
ендоліти

@endolith Я думаю, що в кінцевому рахунку я хочу повторити обробку зорової системи людини, яка робить певні конфігурації просторів відволікаючими. Оскільки це може трапитися і для мінливих річок, то я хотів би зловити їх, хоча прямі здаються більше проблемою. Ще кращим було б спосіб кількісної оцінки «поганості» річок таким чином, який відповідає тому, наскільки сильно вони помітні при читанні тексту. Але це все дуже суб'єктивно і їх важко оцінити. По-перше, просто зловити справді всі погані річки без занадто багато помилкових позитивів.
Левський єпископ

Відповіді:


135

Я ще дещо подумав над цим, і вважаю, що наступне має бути досить стабільним. Зауважте, що я обмежився морфологічними операціями, оскільки вони повинні бути доступні в будь-якій стандартній бібліотеці обробки зображень.

(1) Відкрийте зображення за допомогою маски nPix-by-1, де nPix - приблизно вертикальної відстані між літерами

#% read image
img = rgb2gray('http://i.stack.imgur.com/4ShOW.png');

%# threshold and open with a rectangle
%# that is roughly letter sized
bwImg = img > 200; %# threshold of 200 is better than 128

opImg = imopen(bwImg,ones(13,1));

введіть тут опис зображення

(2) Відкрийте зображення маскою 1 на mPix, щоб усунути занадто вузьке, щоб бути річкою.

opImg = imopen(opImg,ones(1,5));

введіть тут опис зображення

(3) Видаліть горизонтальні "річки та озера", які виникають через пробіл між абзацами чи відступом. Для цього ми видаляємо всі рядки, що відповідають дійсності, і відкриваємо маску nPix-by-1, яка, як ми знаємо, не вплине на річки, які ми знайшли раніше.

Щоб видалити озера, ми можемо використовувати маску відкриття, яка трохи більше, ніж nPix-за-nPix.

На цьому кроці ми також можемо викинути все, що занадто мало, щоб бути справжньою річкою, тобто все, що займає меншу площу, ніж (nPix + 2) * (mPix + 2) * 4 (що дасть нам ~ 3 рядки). +2 є, тому що ми знаємо, що всі об’єкти мають принаймні nPix у висоту, а mPix у ширину, і ми хочемо трохи вище цього.

%# horizontal river: just look for rows that are all true
opImg(all(opImg,2),:) = false;
%# open with line spacing (nPix)
opImg = imopen(opImg,ones(13,1));

%# remove lakes with nPix+2
opImg = opImg & ~imopen(opImg,ones(15,15)); 

%# remove small fry
opImg = bwareaopen(opImg,7*15*4);

введіть тут опис зображення

(4) Якщо нас цікавить не тільки довжина, але й ширина річки, ми можемо поєднати перетворення відстані зі скелетом.

   dt = bwdist(~opImg);
   sk = bwmorph(opImg,'skel',inf);
   %# prune the skeleton a bit to remove branches
   sk = bwmorph(sk,'spur',7);

   riversWithWidth = dt.*sk;

введіть тут опис зображення (кольори відповідають ширині річки (хоча кольорова смуга відключається в 2 рази)

Тепер ви можете отримати приблизну довжину річок, підрахувавши кількість пікселів у кожному підключеному компоненті та середню ширину шляхом усереднення їх піксельних значень.


Ось такий самий аналіз, застосований до другого зображення "без річки":

введіть тут опис зображення


Дякую. У мене є Matlab, тому я спробую це на деяких інших текстах, щоб побачити, наскільки він буде надійним.
Лев Єпископ

Інтеграція його назад у TeX може бути іншою проблемою, якщо ми не зможемо якось перенести це в Lua.
ℝaphink

@LevBishop: Я думаю, що я розумію проблему трохи краще. Нове рішення має бути досить надійним.
Йонас

@levBishop: Ще одне оновлення.
Йонас

1
@LevBishop: Щойно помітив друге зображення. Виявляється, аналіз на основі морфології робить свою справу.
Йонас

56

У Mathematica, використовуючи ерозію та перетворення Хаффа:

(*Get Your Images*)
i = Import /@ {"http://i.stack.imgur.com/4ShOW.png", 
               "http://i.stack.imgur.com/5UQwb.png"};

(*Erode and binarize*)
i1 = Binarize /@ (Erosion[#, 2] & /@ i);

(*Hough transform*)
lines = ImageLines[#, .5, "Segmented" -> True] & /@ i1;

(*Ready, show them*)
Show[#[[1]],Graphics[{Thick,Orange, Line /@ #[[2]]}]] & /@ Transpose[{i, lines}]

введіть тут опис зображення

Редагувати Відповідь на коментар містера Майстра

Якщо ви хочете позбутися горизонтальних ліній, просто замість цього зробіть щось подібне (можливо, хтось може зробити це простіше):

Show[#[[1]], Graphics[{Thick, Orange, Line /@ #[[2]]}]] & /@ 
 Transpose[{i, Select[Flatten[#, 1], Chop@Last@(Subtract @@ #) != 0 &] & /@ lines}]

введіть тут опис зображення


1
Чому б не позбутися всіх горизонтальних ліній? (+1)
Mr.Wizard

@Містер. Тільки щоб показати, що всі рядки виявляються ...
Доктор belisarius

1
Однак це не є частиною проблеми, чи не так?
Mr.Wizard

@Містер. Відредаговано за запитом
доктор belisarius

4
@belisarius Система координат, що використовується в перетворенні Хоффа, змінилася після 8.0.0, щоб відповідати трансформації Радона. Це в свою чергу змінило поведінку ImageLines. В цілому це є покращенням, хоча в цьому випадку варто віддати перевагу попередній поведінці. Якщо ви не хочете експериментувати з піковими виявлень, ви можете змінити співвідношення сторін вхідного зображення , щоб бути ближче до 1 і отримати результат , аналогічний 8.0.0: lines = ImageLines[ImageResize[#, {300, 300}], .6, "Segmented" -> True] & /@ i1;. Враховуючи це, для цієї проблеми морфологічний підхід видається більш надійним.
Маттіас Одісіо

29

Гммм ... я думаю, що перетворення Радона не так просто витягти з. (Перетворення Радона в основному обертає зображення, в той час як "дивиться через нього" на край. Це принцип, який лежить в основі сканування CAT.) Перетворення вашого зображення створює цю синограму, "річки" утворюють яскраві вершини, які обводяться:

введіть тут опис зображення

При повороті на 70 градусів видно досить чітко, як пік зліва від цієї ділянки зрізу вздовж горизонтальної осі:

введіть тут опис зображення

Особливо, якщо текст був першим розмитим Гауссом:

введіть тут опис зображення

Але я не впевнений, як надійно витягти ці вершини з решти шуму. Яскравий верхній і нижній кінці синограми представляють «річки» між горизонтальними рядками тексту, які вам, очевидно, не цікавлять. Може бути, функція зважування проти кута, яка підкреслює більше вертикальних ліній і мінімізує горизонтальні?

Проста функція зважування косинусів добре працює на цьому зображенні:

введіть тут опис зображення

знаходження вертикальної річки на 90 градусів, що є глобальними максимумами в синограмі:

введіть тут опис зображення

і на цьому зображенні виявлення зображення в 104 градусах, хоча розмивання спочатку робить його більш точним:

введіть тут опис зображення введіть тут опис зображення

( radon()Функція SciPy - це ніби тупа , або я б відображав цей пік назад на оригінальне зображення у вигляді лінії, що проходить через середину річки.)

Але після розмиття та зважування він не знаходить жодної з двох основних вершин синограми для вашого зображення:

введіть тут опис зображення

Вони там, але вони переповнені матеріалами біля середнього піку функції зважування. При правильному зважуванні та налаштуванні цей метод, ймовірно, міг би працювати, але я не впевнений, що таке правильне налаштування. Це, мабуть, залежить і від властивостей сканів сторінки. Можливо, зважування потрібно отримувати із загальної енергії, яка знаходиться в шматочку, або чогось іншого, як нормалізація.

from pylab import *
from scipy.misc import radon
import Image

filename = 'rivers.png'
I = asarray(Image.open(filename).convert('L').rotate(90))

# Do the radon transform and display the result
a = radon(I, theta = mgrid[0:180])

# Remove offset
a = a - min(a.flat)

# Weight it to emphasize vertical lines
b = arange(shape(a)[1]) #
d = (0.5-0.5*cos(b*pi/90))*a

figure()
imshow(d.T)
gray()
show()

# Find the global maximum, plot it, print it
peak_x, peak_y = unravel_index(argmax(d),shape(d))
plot(peak_x, peak_y,'ro')
print len(d)- peak_x, 'pixels', peak_y, 'degrees'

Що робити, якщо ви спочатку розмилися з асиметричним Гауссом? Тобто вузькі в горизонтальному напрямку, широкі у вертикальному напрямку.
Йонас

@Jonas: Це, мабуть, допоможе. Основна проблема - це автоматично вибирати піки з фону, коли фон сильно змінюється при обертанні. Асиметричне розмиття може згладити горизонтальні смуги від лінії до лінії.
ендоліт

Це добре працює для виявлення обертання ліній в тексті, по крайней мере: gist.github.com/endolith/334196bac1cac45a4893
ендоліти

16

Я тренував дискримінаційний класифікатор на пікселях, використовуючи похідні функції (до 2-го порядку) в різних масштабах.

Мої мітки:

Маркування

Прогноз на тренувальному зображенні:

введіть тут опис зображення

Прогноз на двох інших зображеннях:

введіть тут опис зображення

введіть тут опис зображення

Я думаю, це виглядає багатообіцяюче і може дати корисні результати, отримавши більше навчальних даних і, можливо, розумніші функції. З іншого боку, мені знадобилося лише кілька хвилин, щоб отримати ці результати. Ви можете відтворити результати самостійно, використовуючи програмне забезпечення з відкритим кодом ilastik . [Відмова від відповідальності: Я один з основних розробників.]


2

(Вибачте, ця публікація не надходить з приголомшливими демонстраціями.)

Якщо ви хотіли працювати з інформацією, яка вже є у TeX (літери та позиції), ви можете вручну класифікувати літери та пари букв як "нахилені" в ту чи іншу сторону. Наприклад, "w" має кутові схили SW та SE, комбо "al" має кутовий нахил NW, "k" має кут схилу NE. (Не забувайте пунктуацію - цитата, що супроводжується буквою, яка заповнює нижню половину поля глифіки, встановлює хороший нахил; цитата, за якою q, особливо сильна.)

Потім знайдіть випадки відповідних схилів на протилежних сторонах простору - "w al" для річки SW-NE-NE або "k T" для річки NW-SE. Коли ви знайдете його на лінії, подивіться, чи відбувається подібне, відповідним чином зрушеним ліворуч або праворуч, на лініях вище / нижче; коли ти знайдеш біг із них, ймовірно, річка.

Очевидно, також просто шукайте простори, розташовані майже вертикально, для рівнинних вертикальних річок.

Ви можете отримати трохи складніше, вимірявши «міцність» схилу: яка частина авансової коробки «порожня» завдяки схилу і таким чином сприяє ширині річки. "w" досить невеликий, оскільки у нього є лише невеликий куточок його попереднього вікна, щоб сприяти річці, але "V" дуже сильний. "b" трохи сильніше, ніж "k"; крива більш ніжної форми дає більш візуально безперервний край річки, роблячи її сильнішою та візуально ширшою.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.