Нахождение зебра-подобного рисунка на изображении (Обнаружение центральной линии структурированного света по фотографии)

12

Я работаю в проекте, где полосы проецируются на объект, и делается фотография. Задача состоит в том, чтобы найти центральные линии полос, которые математически представляют трехмерную кривую пересечения между плоскостью полосы и поверхностью объекта.

Фотография представляет собой PNG (RGB), и в предыдущих попытках использовалась градация серого, а затем пороговое значение разницы, чтобы получить черно-белую «зебра-подобную» фотографию, из которой было легко найти среднюю точку каждого столбца пикселей каждой полосы. Проблема заключается в том, что, используя пороговое значение, а также принимая среднюю высоту столбца дискретных пикселей, мы получаем некоторую потерю точности и квантование, что вовсе не желательно.

Когда я смотрю на изображения, у меня складывается впечатление, что центральные линии могли бы быть более непрерывными (больше точек) и более плавными (не квантованными), если бы они были обнаружены непосредственно из изображения без порога (RGB или в градациях серого) некоторым статистическим методом развертки (некоторое затопление / итеративная свертка, что угодно).

Ниже приведен пример изображения:

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

Любое предложение будет высоко ценится!

heltonbiker
источник
это очень интересно. Но, кстати, я провожу некоторые исследования с использованием цветных полос для обнаружения 3D-объектов. Поскольку с помощью цветной полоски легко найти соответствие каждой полосе из проектора. Таким образом, используя тригонометрию, можно вычислить трехмерную информацию. Как вы находите соответствие, если цвет такой же? Я думаю, что ваш проект также о 3d реконструкции?
@johnyoung: Пожалуйста, не добавляйте комментарии в качестве ответов. Я понимаю, что вам нужна репутация, прежде чем вы сможете комментировать, но, пожалуйста, воздержитесь от текущего курса действий. Я предлагаю задавать свои (связанные) вопросы или отвечать на вопросы других, чтобы увеличить свою репутацию.
Питер К.
Извините за еще один вопрос вместо ответа. В методе сдвига фазы мы рассчитываем фазу для каждого пикселя на проецируемом изображении, но вот почему нам нужно найти центральную линию полосы, может быть, мой вопрос слишком глуп, но я не нет, поэтому, пожалуйста, сообщите мне точную причину. Вы можете удалить мой вопрос после ответа
Это разные методы. Я моделирую серию геометрических плоскостей, проецируя серию белых полос (каждая из которых образует «плоскость» в трехмерном пространстве). Таким образом, мне нужно найти осевую линию полос, потому что плоскости не имеют толщины. Конечно, я мог бы выполнить анализ фазового сдвига, но есть одна проблема: моя проекция является двоичной (чередуются черные и белые полосы), интенсивность не изменяется синусоидально, и поэтому я не могу выполнить фазовый сдвиг (и не нужно, в настоящее время ).
Хелтонбайкер

Ответы:

13

Я предлагаю следующие шаги:

  1. Найдите порог, чтобы отделить передний план от фона.
  2. Для каждого шарика в двоичном изображении (одна полоса зебры) для каждого xнайдите взвешенный центр (по интенсивности пикселей) в yнаправлении.
  3. Возможно, сгладить yзначения, чтобы удалить шум.
  4. Соедините (x,y)точки, подгоняя какую-то кривую. Эта статья может вам помочь. Вы также можете использовать многочлен высокого уровня, хотя, на мой взгляд, он хуже.

Вот код Matlab, который показывает шаги 1,2 и 4. Я пропустил автоматический выбор порога. Вместо этого я выбрал руководство th=40:

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

Это кривые после подгонки полинома: введите описание изображения здесь

Вот код:

function Zebra()
    im = imread('http://i.stack.imgur.com/m0sy7.png');
    im = uint8(mean(im,3));

    th = 40;
    imBinary = im>th;
    imBinary = imclose(imBinary,strel('disk',2));
    % figure;imshow(imBinary);
    labels = logical(imBinary);
    props =regionprops(labels,im,'Image','Area','BoundingBox');

    figure(1);imshow(im .* uint8(imBinary));
    figure(2);imshow(im .* uint8(imBinary));

    for i=1:numel(props)
        %Ignore small ones
        if props(i).Area < 10
            continue
        end
        %Find weighted centroids
        boundingBox = props(i).BoundingBox;
        ul = boundingBox(1:2)+0.5;
        wh = boundingBox(3:4);
        clipped = im( ul(2): (ul(2)+wh(2)-1), ul(1): (ul(1)+wh(1)-1) );
        imClip = double(props(i).Image) .* double(clipped);
        rows = transpose( 1:size(imClip,1) );
        %Weighted calculation
        weightedRows  = sum(bsxfun(@times, imClip, rows),1) ./ sum(imClip,1);
        %Calculate x,y
        x = ( 1:numel(weightedRows) ) + ul(1) - 1;
        y = ( weightedRows ) + ul(2) - 1;
        figure(1);
        hold on;plot(x,y,'b','LineWidth',2);
        try %#ok<TRYNC>
            figure(2);
            [xo,yo] = FitCurveByPolynom(x,y);
            hold on;plot(xo,yo,'g','LineWidth',2);
        end
        linkaxes( cell2mat(get(get(0,'Children'),'Children')) )
    end        
end

function [xo,yo] = FitCurveByPolynom(x,y)
   p = polyfit(x,y,15); 
   yo = polyval(p,x);
   xo = x;
end
Андрей Рубштейн
источник
Я нашел это очень интересным. Я использую Python, но в любом случае мне придется изучить обоснование всего этого. В качестве независимого комментария я склонен не выполнять классическую обработку изображений (непосредственно на квантованных контейнерах изображений, таких как массивы uint8), а вместо этого загружать все в память как плавающие массивы перед применением операций. Кроме того, я удивлен результатами, полученными на нижней половине вашего изображения: синие линии не проходят вдоль ожидаемой средней полосы ... (?). Спасибо за сейчас, я собираюсь дать некоторые отзывы, как только я получу некоторый результат!
Хелтонбайкер
@heltonbiker, пожалуйста, проверьте обновленный ответ. Вы правы насчет числа с плавающей запятой, я использовал его, когда преобразовал в double. О результатах в нижней половине, мне нужно проверить, это может быть программная ошибка
Андрей Рубштейн
1
@heltonbiker, готово. Это действительно была ошибка, связанная с индексированием на основе 1.
Андрей Рубштейн
Отлично! Удивительно, действительно. С этой техникой и для моих целей сглаживание не только не понадобится, но и будет вредным. Большое спасибо за ваш интерес!
Хелтонбайкер
3

Я бы не использовал изображение RGB. Цветные изображения обычно создаются с помощью «фильтра Байера» на сенсоре камеры, который обычно снижает разрешение, которое вы можете достичь.

Если вы используете изображение в градациях серого, я думаю, что описанные вами шаги (бинаризация изображения «зебра», поиск средней линии) являются хорошим началом. В качестве последнего шага я бы

  • Возьмите каждую точку в средней линии, которую вы нашли
  • взять значения серого пикселей в строке «зебра» выше и ниже
  • подогнать параболу к этим серым значениям с помощью наименьших средних квадратов
  • вершина этой параболы - улучшенная оценка положения средней линии
Ники Эстнер
источник
Приятные мысли Я планирую использовать какую-то параболу или сплайн вдоль пиковых значений каждого столбца пикселей, но я все еще задаюсь вопросом, должен ли я изучить столбец пикселей или вместо пиксельной «области» вдоль линии ... Буду ждать еще немного больше ответов. Спасибо за сейчас!
Хелтонбайкер
@heltonbiker - в качестве быстрого теста используйте только зеленый канал. Обычно на цветном сенсоре в два раза больше зеленых пикселей, и он меньше интерполирован, чем красный и синий
Мартин Беккет
@MartinBeckett Спасибо за ваш интерес, я уже проанализировал каждый канал, и действительно, зеленый кажется гораздо более решительным, чем, скажем, красный. Тем не менее, выстраивая значения интенсивности вертикальных сечений для каждого канала, «полосчатый рисунок», по-видимому, не сильно меняется между каналами, и я в настоящее время смешиваю их одинаково при преобразовании в оттенки серого. Несмотря на то, что я все еще планирую изучить лучшую линейную комбинацию между каналами, чтобы получить лучший контрастный результат, ИЛИ получить изображения уже в оттенках серого. Еще раз спасибо!
Хелтонбайкер
3

Вот еще альтернативное решение вашей проблемы, смоделировав ваш вопрос как «проблему оптимизации пути». Хотя это сложнее, чем простое решение для бинаризации и затем кривой, на практике оно более надежно.

С очень высокого уровня, мы должны рассматривать это изображение как график, где

  1. каждый пиксель изображения является узлом на этом графике

  2. каждый узел связан с некоторыми другими узлами, известными как соседи, и это определение соединения часто называют топологией этого графа.

  3. каждый узел имеет вес (особенность, стоимость, энергия или как вы хотите его назвать), отражая вероятность того, что этот узел находится в оптимальной центральной линии, которую мы ищем.

До тех пор, пока мы можем смоделировать эту вероятность, ваша задача поиска «осевых линий границ» переходит к задаче поиска локальных оптимальных путей на графе , которые могут быть эффективно решены с помощью динамического программирования, например, алгоритма Витерби.

Вот некоторые плюсы принятия этого подхода:

  1. все ваши результаты будут непрерывными (в отличие от порогового метода, который может разбить одну центральную линию на части)

  2. много свобод для построения такого графа, вы можете выбрать различные функции и топологию графа.

  3. Ваши результаты являются оптимальными в смысле оптимизации пути

  4. Ваше решение будет более устойчивым к шуму, поскольку пока шум равномерно распределяется между всеми пикселями, эти оптимальные пути остаются стабильными.

Вот короткая демонстрация вышеуказанной идеи. Поскольку я не использую какие-либо предварительные знания для определения возможных начальных и конечных узлов, я просто декодирую по каждому возможному начальному узлу. Декодированные дорожки Витерби

Для нечетких окончаний это вызвано тем, что мы ищем оптимальные пути для всех возможных конечных узлов. В результате, хотя для некоторых узлов, расположенных в темных областях, выделенный путь все еще остается локально оптимальным.

Для нечеткого пути вы можете сгладить его после того, как найдете его, или использовать некоторые сглаженные функции вместо необработанной интенсивности.

Можно восстановить частичные пути, изменив начальный и конечный узлы.

Нетрудно обрезать эти нежелательные локальные оптимальные пути. Потому что у нас есть вероятность всех путей после декодирования Витерби, и вы можете использовать различные предшествующие знания (например, мы видим, что нам нужен только один оптимальный путь для тех, кто использует один и тот же источник).

Для более подробной информации, вы можете обратиться к статье.

 Wu, Y.; Zha, S.; Cao, H.; Liu, D., & Natarajan, P.  (2014, February). A Markov Chain Line Segmentation Method for Text Recognition. In IS&T/SPIE 26th Annual Symposium on Electronic Imaging (DRR), pp. 90210C-90210C.

Вот небольшой фрагмент кода Python, который используется для создания приведенного выше графика.


import cv2
import numpy as np
from matplotlib import pyplot
# define your image path
image_path = ;
# read in an image
img = cv2.imread( image_path, 0 );
rgb = cv2.imread( image_path, -1 );

# some feature to reflect how likely a node is in an optimal path
img = cv2.equalizeHist( img ); # equalization
img = img - img.mean(); # substract DC
img_pmax = img.max(); # get brightest intensity
img_nmin = img.min(); # get darkest intensity
# express our preknowledge
img[ img > 0 ] *= +1.0  / img_pmax; 
img[ img = 1 :
    prev_idx = vt_path[ -1 ].astype('int');
    vt_path.append( path_buffer[ prev_idx, time ] );
    time -= 1;
vt_path.reverse();    
vt_path = np.asarray( vt_path ).T;

# plot found optimal paths for every 7 of them
pyplot.imshow( rgb, 'jet' ),
for row in range( 0, h, 7 ) :
    pyplot.hold(True), pyplot.plot( vt_path[row,:], c=np.random.rand(3,1), lw = 2 );
pyplot.xlim( ( 0, w ) );
pyplot.ylim( ( h, 0 ) );
западня
источник
Это очень интересный подход. Признаюсь, тема «графиков» была для меня неясной до недавнего времени, когда (в этом же проекте) я мог решить только одну проблему, используя графики. После того как я «понял», я понял, насколько мощными могут быть эти алгоритмы кратчайших путей. Твоя идея очень интересна, и я не исключаю, что я буду реализовывать эту идею, если у меня возникнет необходимость / возможность. Большое спасибо.
Хелтонбайкер
Что касается ваших текущих результатов, из моего опыта, вероятно, было бы лучше сгладить изображение сначала с помощью гауссовского и / или медианного фильтра, прежде чем строить график. Это дало бы намного более гладкие (и более правильные) линии. Кроме того, одним из возможных приемов является расширение окрестности, чтобы обеспечить «прямой переход» через два или более пикселей (до заданного предела, скажем, 8 или 10 пикселей). Конечно, следует выбрать подходящую функцию стоимости, но я думаю, что ее легко настроить.
Хелтонбайкер
О да. Я просто взял что-то под рукой, вы определенно можете использовать другие топологические и энергетические функции. На самом деле, эта структура также обучаема. В частности, вы начинаете с необработанной интенсивности, декодируете для оптимальных путей, выбираете только те оптимальные узлы с высокой достоверностью, и таким образом вы получаете «помеченные данные». С этой небольшой частью автоматически помеченных данных вы можете узнать много разных полезных вещей.
подводный камень
3

Думаю, я должен опубликовать свой ответ, так как он немного отличается от других подходов. Я попробовал это в Matlab.

  • суммируйте все каналы и создайте изображение, чтобы все каналы были одинаково взвешены
  • выполнить морфологическое закрытие и гауссовскую фильтрацию на этом изображении
  • для каждого столбца результирующего изображения найдите локальные максимумы и создайте изображение
  • найти связанные компоненты этого изображения

Один недостаток, который я вижу здесь, состоит в том, что этот подход не будет работать хорошо для некоторых ориентаций полос. В этом случае мы должны исправить его ориентацию и применить эту процедуру.

Вот код Matlab:

im = imread('m0sy7.png');
imsum = sum(im, 3); % sum all channels
h = fspecial('gaussian', 3);
im2 = imclose(imsum, ones(3)); % close
im2 = imfilter(im2, h); % smooth
% for each column, find regional max
mx = zeros(size(im2));
for c = 1:size(im2, 2)
    mx(:, c) = imregionalmax(im2(:, c));
end
% find connected components
ccomp = bwlabel(mx);

Например, если вы возьмете средний столбец изображения, его профиль должен выглядеть следующим образом: (синим цветом обозначен профиль. Зеленым цветом обозначены локальные максимумы). средний профиль и локальные максимумы

И изображение, содержащее локальные максимумы для всех столбцов, выглядит так: введите описание изображения здесь

Вот подключенные компоненты (хотя некоторые полосы нарушены, большинство из них получают непрерывную область):

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

dhanushka
источник
Это фактически то, что мы делаем сейчас, с той лишь разницей, как найти локальные максимумы для каждого столбца пикселя: мы используем параболическую интерполяцию, чтобы найти точную вершину параболы, проходящей через пиксель с максимальным значением, а также его верхних и нижних соседей. , Это позволяет s получить результат «между» пикселями, что лучше отражает тонкую плавность линий. Спасибо за Ваш ответ!
Хелтонбайкер