Ответ 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];
Цветовой тон:
Канал насыщения:
Канал значений:
обычно, канал оттенка является первым, на который нужно обратить внимание, если вы заинтересованы в сегментировании "цвета" (например, всех красных объектов). Одна из проблем заключается в том, что этот оттенок является круговым / 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;
}
теперь вы можете использовать простое определение canny edge для поиска краев в канале оттенка:
cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);
Вы можете видеть, что регионы немного больше, чем настоящие пирожки, возможно, из-за крошечных отражений на земле вокруг пирожков, но я не уверен в этом. Может быть, это просто из-за артефактов сжатия jpeg;)
Если вы используете канал насыщения для извлечения краев, вы получите что-то вроде этого:
cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);
где контуры не полностью закрыты. Возможно, вы можете комбинировать оттенок и насыщенность в рамках предварительной обработки, чтобы извлекать края в канале оттенка, но только там, где насыщенность достаточно высока.
На этом этапе у вас есть края. Обратите внимание, что ребра еще не контуры. Если вы непосредственно извлекаете контуры из краев, они могут не закрываться/разделяться и т.д.:
// 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);
}
вы можете удалить эти небольшие контуры, проверив 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.
если вы извлекаете контуры из этого, он будет выглядеть так:
Если вы выбираете только "внутренние" контуры, это именно то, что вам нравится:
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);
}
помните, что материал дилатации и внутреннего контура немного нечеткий, поэтому он может не работать для разных изображений, и если исходные края располагаются лучше вокруг границы объекта, это может быть 1. не нужно делать расширенный и внутренний контур вещь и 2. если это еще необходимо, расширение приведет к уменьшению объекта в этом сценарии (что, к счастью, отлично подходит для данного образца изображения.)
EDIT: Некоторая важная информация о HSV: Канал оттенков даст каждому пикселю цвет спектра, даже если насыщенность очень низкая (= серый/белый) или если цвет очень низкий (значение) так часто желательно установить порог каналов насыщения и значений, чтобы найти определенный цвет! Это может быть намного проще и гораздо более стабильным, чем расширение, которое я использовал в своем коде.