В этом примере мы рассмотрим, как определить области изображения, содержащие текст для дальнейшего его распознавания в Matlab. Эта общая задача выполняется на неструктурированных сценах. Неструктурированные сцены изображения, содержащие неопределенные или случайные сценарии. Например, вы можете обнаружить и автоматически распознать текст из захваченного видео, чтобы предупредить водителя о дорожном знаке. Это отличается от структурированной сцены, которые содержат известные сценарии, где положение текста заранее известно.
Сегментирование текста из неструктурированной сцены значительно помогает решать дополнительные задачи, такие как оптическое распознавание символов (OCR). Автоматизированное распознавание текста в данном примере обнаруживает большое количество регионов в которых может содержаться текст и постепенно удаляет те участки, на которых большая вероятность отсутствия текста.
В функции MSER детектор хорошо работает для поиска регионов с содержанием текстовых символов. Она неплохо выполняет свою работу, поскольку последовательные цвет и высокая контрастность текста приводит к прочным профилям интенсивности.
Используя функцию detectMSERFeatures найдем образы и сюжет всех регионов. Обратите внимание, что функция также выделяет много нетекстовых областей:
colorImage = imread('handicapSign.jpg'); I = rgb2gray(colorImage); % Detect MSER regions. [mserRegions, mserConnComp] = detectMSERFeatures(I, ... 'RegionAreaRange',[200 8000],'ThresholdDelta',4); figure imshow(I) hold on plot(mserRegions, 'showPixelList', true,'showEllipses',false) title('MSER regions') hold off
Функция MSER направлена на распознавание текстовых областей, но во время своей работы он также обнаруживает множество других нетекстовых регионов. Мы можем обойти все выделенные области для того, чтобы удалить ненужные, нетекстовые регионы. Для фильтрации нетекстовых областей можно использовать геометрические свойства текста. Также мы можем воспользоваться подходом машинного обучения для подготовки текстовых и нетекстовых классификаторов. Как правило, сочетание этих двух подходов дает лучшие результаты. В этом примере мы будем использовать простой подход фильтрации нетекстовых регионов на основе геометрических свойств.
Существует несколько геометрических свойств, которые необходимы для распознания текста и нетекстовых областей:
Воспользуемся функцией regionprops для измерения этих свойств, а затем начнем удалять регионы, которые нам не подходят:
% Use regionprops to measure MSER properties mserStats = regionprops(mserConnComp, 'BoundingBox', 'Eccentricity', ... 'Solidity', 'Extent', 'Euler', 'Image'); % Compute the aspect ratio using bounding box data. bbox = vertcat(mserStats.BoundingBox); w = bbox(:,3); h = bbox(:,4); aspectRatio = w./h; % Threshold the data to determine which regions to remove. These thresholds % may need to be tuned for other images. filterIdx = aspectRatio' > 3; filterIdx = filterIdx | [mserStats.Eccentricity] > .995 ; filterIdx = filterIdx | [mserStats.Solidity] < .3; filterIdx = filterIdx | [mserStats.Extent] < 0.2 | [mserStats.Extent] > 0.9; filterIdx = filterIdx | [mserStats.EulerNumber] < -4; % Remove regions mserStats(filterIdx) = []; mserRegions(filterIdx) = []; % Show remaining regions figure imshow(I) hold on plot(mserRegions, 'showPixelList', true,'showEllipses',false) title('After Removing Non-Text Regions Based On Geometric Properties') hold off
Другой распространенный способ, используемый для распознавания текста, является ширина обводки. Ширина обводки — это мера ширины кривых и линий, которые составляют характер изображения. Регионы содержащие текст, как правило, имеют небольшой разброс ширины обводки, а нетекстовые регионы имеют больше вариаций.
Чтобы понять, как ширина штриха изменяется, чтобы удалить нетекстовые регионы, необходимо оценить ширину обводки всех областей обнаруженных MSER функцией. Мы можем сделать это с помощью расчета расстояния и бинарной операцией прореживания:
% Get a binary image of the a region, and pad it to avoid boundary effects % during the stroke width computation. regionImage = mserStats(6).Image; regionImage = padarray(regionImage, [1 1]); % Compute the stroke width image. distanceImage = bwdist(~regionImage); skeletonImage = bwmorph(regionImage, 'thin', inf); strokeWidthImage = distanceImage; strokeWidthImage(~skeletonImage) = 0; % Show the region image alongside the stroke width image. figure subplot(1,2,1) imagesc(regionImage) title('Region Image') subplot(1,2,2) imagesc(strokeWidthImage) title('Stroke Width Image')
На изображениях выше показано , как ширина штриха имеет очень незначительные изменения по большей части региона. Это означает, что регион имеет больше шансов быть текстовой областью, так как линии и кривые, которые обводят регион имеют похожую ширину.
Для того, чтобы использовать метод изменения ширины штриха, необходимо ввести пороговое значение, изменения по всей области следующим образом:
% Compute the stroke width variation metric strokeWidthValues = distanceImage(skeletonImage); strokeWidthMetric = std(strokeWidthValues)/mean(strokeWidthValues);
Затем, порог может быть применен, чтобы удалить нетекстовые регионы. Обратите внимание, что это пороговое значение может требовать настройки для изображений с различными стилями шрифта.
% Threshold the stroke width variation metric strokeWidthThreshold = 0.4; strokeWidthFilterIdx = strokeWidthMetric > strokeWidthThreshold;
Процедура, приведенная выше, должна осуществляться отдельно для каждой обнаруженной MSER области. Следующий цикл for обрабатывает все регионы, а потом показывает результаты удаления нетекстовых областей с помощью изменения ширины хода.
% Process the remaining regions for j = 1:numel(mserStats) regionImage = mserStats(j).Image; regionImage = padarray(regionImage, [1 1], 0); distanceImage = bwdist(~regionImage); skeletonImage = bwmorph(regionImage, 'thin', inf); strokeWidthValues = distanceImage(skeletonImage); strokeWidthMetric = std(strokeWidthValues)/mean(strokeWidthValues); strokeWidthFilterIdx(j) = strokeWidthMetric > strokeWidthThreshold; end % Remove regions based on the stroke width variation mserRegions(strokeWidthFilterIdx) = []; mserStats(strokeWidthFilterIdx) = []; % Show remaining regions figure imshow(I) hold on plot(mserRegions, 'showPixelList', true,'showEllipses',false) title('After Removing Non-Text Regions Based On Stroke Width Variation') hold off
На данный момент, все результаты обнаружения состоят из отдельных текстовых символов. Чтобы использовать эти результаты для задач распознавания, отдельные символы текста должны быть объединены в слова или строки. Это позволяет распознавать слова в изображении, которые несут более значимую информацию, чем просто отдельные буквы.
Для того чтобы, объединить отдельные регионы в слова или строки текста, необходимо сначала найти текст из соседних регионов, а затем сформировать рамку вокруг этих регионов. Чтобы найти соседние регионы нужно расширить рамки, вычисленные ранее regionprops.
% Get bounding boxes for all the regions bboxes = vertcat(mserStats.BoundingBox); % Convert from the [x y width height] bounding box format to the [xmin ymin % xmax ymax] format for convenience. xmin = bboxes(:,1); ymin = bboxes(:,2); xmax = xmin + bboxes(:,3) - 1; ymax = ymin + bboxes(:,4) - 1; % Expand the bounding boxes by a small amount. expansionAmount = 0.02; xmin = (1-expansionAmount) * xmin; ymin = (1-expansionAmount) * ymin; xmax = (1+expansionAmount) * xmax; ymax = (1+expansionAmount) * ymax; % Clip the bounding boxes to be within the image bounds xmin = max(xmin, 1); ymin = max(ymin, 1); xmax = min(xmax, size(I,2)); ymax = min(ymax, size(I,1)); % Show the expanded bounding boxes expandedBBoxes = [xmin ymin xmax-xmin+1 ymax-ymin+1]; IExpandedBBoxes = insertShape(colorImage,'Rectangle',expandedBBoxes,'LineWidth',3); figure imshow(IExpandedBBoxes) title('Expanded Bounding Boxes Text')
Теперь, перекрывающиеся рамки могут быть объединены вместе, чтобы сформировать один ограничивающий прямоугольник вокруг отдельных слов или строк текста. Для этого вычисляют коэффициент перекрытия между всеми парами ограничивающего прямоугольника. Это определяет расстояние между всеми парами текстовых регионов, так чтобы в нем можно найти группы из соседних регионов имеющие ненулевые коэффициенты перекрытия. После попарного перекрытия используя graph вычисляются коэффициенты, чтобы найти все текстовые регионов «связанные» с ненулевыми коэффициентами перекрытия.
Мы будем использовать функцию bboxOverlapRatio, для вычисления парных коэффициентов перекрытия для всех расширенных рамок, а затем воспользуемся graph для поиска всех подключенных регионов.
% Compute the overlap ratio overlapRatio = bboxOverlapRatio(expandedBBoxes, expandedBBoxes); % Set the overlap ratio between a bounding box and itself to zero to % simplify the graph representation. n = size(overlapRatio,1); overlapRatio(1:n+1:n^2) = 0; % Create the graph g = graph(overlapRatio); % Find the connected text regions within the graph componentIndices = conncomp(g);
Выходные данные conncomp являются индекса регионов содержащих текст ограниченные рамками. Используя эти показатели, мы можем объединить несколько соседних рамок, в единую рамку путем вычисления минимального и максимального из индивидуальных ограничительных блоков, которые составляют каждую компоненту связности.
% Merge the boxes based on the minimum and maximum dimensions. xmin = accumarray(componentIndices', xmin, [], @min); ymin = accumarray(componentIndices', ymin, [], @min); xmax = accumarray(componentIndices', xmax, [], @max); ymax = accumarray(componentIndices', ymax, [], @max); % Compose the merged bounding boxes using the [x y width height] format. textBBoxes = [xmin ymin xmax-xmin+1 ymax-ymin+1];
Наконец, прежде чем показывать окончательные результаты обнаружения, необходимо избавиться от плохо обнаруженного текста.
% Remove bounding boxes that only contain one text region numRegionsInGroup = histcounts(componentIndices); textBBoxes(numRegionsInGroup == 1, :) = []; % Show the final text detection result. ITextRegion = insertShape(colorImage, 'Rectangle', textBBoxes,'LineWidth',3); figure imshow(ITextRegion) title('Detected Text')
После обнаружения текстовых областей, используем функцию ocr для распознавания текста в каждой рамке. Обратите внимание, что без поиска в области текста, на выходе ocr будут присутствовать много шума.
ocrtxt = ocr(I, textBBoxes); [ocrtxt.Text]
ans =
'HANDICIXPPED
PARKING
SPECIAL PLATE
REQUIRED
UNAUTHORIZED
VEHICLES
MAY BE TOWED
AT OWNERS
EXPENSE
Таким образом, мы смогли распознать текст из картинки.