Обнаружение края/границы OpenCV на основе цвета

Я новичок в OpenCV и очень рад узнать больше. Я занимался идеей очертания краев, форм.

Я пришел на этот код (работает на устройстве iOS), который использует Canny. Я хотел бы иметь возможность отображать это в цвете и окружать каждую фигуру. Может ли кто-нибудь указать мне в правильном направлении?

Спасибо!

IplImage *grayImage = cvCreateImage(cvGetSize(iplImage), IPL_DEPTH_8U, 1);
cvCvtColor(iplImage, grayImage, CV_BGRA2GRAY);
cvReleaseImage(&iplImage);

IplImage* img_blur = cvCreateImage( cvGetSize( grayImage ), grayImage->depth, 1);
cvSmooth(grayImage, img_blur, CV_BLUR, 3, 0, 0, 0);
cvReleaseImage(&grayImage);

IplImage* img_canny = cvCreateImage( cvGetSize( img_blur ), img_blur->depth, 1);
cvCanny( img_blur, img_canny, 10, 100, 3 );
cvReleaseImage(&img_blur);

cvNot(img_canny, img_canny);

И примером могут быть эти пирожки с гамбургерами. OpenCV обнаружил бы патти и обрисовал бы его. enter image description here

Исходное изображение:

enter image description here

Ответ 1

Информация о цвете часто обрабатывается путем преобразования в цветовое пространство HSV, которое обрабатывает "цвет" непосредственно вместо разделения цвета на компоненты R/G/B, что упрощает обработку одинаковых цветов с разной яркостью и т.д.

если вы преобразуете свое изображение в HSV, вы получите следующее:

cv::Mat hsv;
cv::cvtColor(input,hsv,CV_BGR2HSV);

std::vector<cv::Mat> channels;
cv::split(hsv, channels);

cv::Mat H = channels[0];
cv::Mat S = channels[1];
cv::Mat V = channels[2];

Цветовой тон:

enter image description here

Канал насыщения:

enter image description here

Канал значений:

enter image description here

обычно, канал оттенка является первым, на который нужно обратить внимание, если вы заинтересованы в сегментировании "цвета" (например, всех красных объектов). Одна из проблем заключается в том, что этот оттенок является круговым / angular значением, что означает, что самые высокие значения очень похожи на самые низкие значения, что приводит к ярким артефактам на границе патчей. Чтобы преодолеть это для определенной ценности, вы можете переместить весь оттенок. Если сдвинуть на 50 °, вы получите что-то вроде этого:

cv::Mat shiftedH = H.clone();
int shift = 25; // in openCV hue values go from 0 to 180 (so have to be doubled to get to 0 .. 360) because of byte range from 0 to 255
for(int j=0; j<shiftedH.rows; ++j)
    for(int i=0; i<shiftedH.cols; ++i)
    {
        shiftedH.at<unsigned char>(j,i) = (shiftedH.at<unsigned char>(j,i) + shift)%180;
    }

enter image description here

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

cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);

enter image description here

Вы можете видеть, что регионы немного больше, чем настоящие пирожки, возможно, из-за крошечных отражений на земле вокруг пирожков, но я не уверен в этом. Может быть, это просто из-за артефактов сжатия jpeg;)

Если вы используете канал насыщения для извлечения краев, вы получите что-то вроде этого:

cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);

enter image description here

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

На этом этапе у вас есть края. Обратите внимание, что ребра еще не контуры. Если вы непосредственно извлекаете контуры из краев, они могут не закрываться/разделяться и т.д.:

// extract contours of the canny image:
std::vector<std::vector<cv::Point> > contoursH;
std::vector<cv::Vec4i> hierarchyH;
cv::findContours(cannyH,contoursH, hierarchyH, CV_RETR_TREE , CV_CHAIN_APPROX_SIMPLE);

// draw the contours to a copy of the input image:
cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
 {
   cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
 }

enter image description here

вы можете удалить эти небольшие контуры, проверив cv::contourArea(contoursH[i]) > someThreshold перед рисованием. Но вы видите, что два пирожки слева связаны? Здесь самая сложная часть... используйте некоторые эвристики, чтобы "улучшить" ваш результат.

cv::dilate(cannyH, cannyH, cv::Mat());
cv::dilate(cannyH, cannyH, cv::Mat());
cv::dilate(cannyH, cannyH, cv::Mat());

Dilation before contour extraction will "close" the gaps between different objects but increase the object size too.

enter image description here

если вы извлекаете контуры из этого, он будет выглядеть так:

enter image description here

Если вы выбираете только "внутренние" контуры, это именно то, что вам нравится:

cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
 {
    if(cv::contourArea(contoursH[i]) < 20) continue; // ignore contours that are too small to be a patty
    if(hierarchyH[i][3] < 0) continue;  // ignore "outer" contours

    cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
 }

enter image description here

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

EDIT: Некоторая важная информация о HSV: Канал оттенков даст каждому пикселю цвет спектра, даже если насыщенность очень низкая (= серый/белый) или если цвет очень низкий (значение) так часто желательно установить порог каналов насыщения и значений, чтобы найти определенный цвет! Это может быть намного проще и гораздо более стабильным, чем расширение, которое я использовал в своем коде.