Обнаружение очков

То, что я пытаюсь сделать, это измерить толщину оправ очков. У меня была идея измерить толщину контуров рамки (может быть, лучше?). Я до сих пор обрисовал в общих чертах структуру очков, но есть промежутки, где линии не встречаются. Я думал об использовании HoughLinesP, но я не уверен, что это то, что мне нужно.

До сих пор я провел следующие шаги:

  • Конвертировать изображение в оттенки серого
  • Создайте ROI вокруг области глаз / очков
  • Размытие изображения
  • Расширьте изображение (сделали это, чтобы удалить очки в тонкой оправе)
  • Провести обнаружение краев Canny
  • Найдены контуры

Вот результаты:

Это мой код до сих пор:

//convert to grayscale
cv::Mat grayscaleImg;
cv::cvtColor( img, grayscaleImg, CV_BGR2GRAY );

//create ROI
cv::Mat eyeAreaROI(grayscaleImg, centreEyesRect);
cv::imshow("roi", eyeAreaROI);

//blur
cv::Mat blurredROI;
cv::blur(eyeAreaROI, blurredROI, Size(3,3));
cv::imshow("blurred", blurredROI);

//dilate thin lines
cv::Mat dilated_dst;
int dilate_elem = 0;
int dilate_size = 1;
int dilate_type = MORPH_RECT;

cv::Mat element = getStructuringElement(dilate_type,
cv::Size(2*dilate_size + 1, 2*dilate_size+1),
cv::Point(dilate_size, dilate_size));

cv::dilate(blurredROI, dilated_dst, element);
cv::imshow("dilate", dilated_dst);

//edge detection
int lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;

cv::Canny(dilated_dst, dilated_dst, lowThreshold, lowThreshold*ratio, kernel_size);

//create matrix of the same type and size as ROI
Mat dst;
dst.create(eyeAreaROI.size(), dilated_dst.type());
dst = Scalar::all(0);

dilated_dst.copyTo(dst, dilated_dst);
cv::imshow("edges", dst);

//join the lines and fill in
vector<Vec4i> hierarchy;
vector<vector<Point>> contours;

cv::findContours(dilated_dst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
cv::imshow("contours", dilated_dst);

Я не совсем уверен, какими будут следующие шаги или, как я уже сказал выше, следует ли мне использовать HoughLinesP и как его реализовать. Любая помощь очень ценится!

8

Решение

Я думаю, что есть 2 основные проблемы.

  1. сегментируйте оправу очков

  2. найти толщину сегментированной рамки

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

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

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

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

// this functions finds the biggest X contours. Probably there are faster ways, but it should work...
std::vector<std::vector<cv::Point>> findBiggestContours(std::vector<std::vector<cv::Point>> contours, int amount)
{
std::vector<std::vector<cv::Point>> sortedContours;

if(amount <= 0) amount = contours.size();
if(amount > contours.size()) amount = contours.size();

for(int chosen = 0; chosen < amount; )
{
double biggestContourArea = 0;
int biggestContourID = -1;
for(unsigned int i=0; i<contours.size() && contours.size(); ++i)
{
double tmpArea = cv::contourArea(contours[i]);
if(tmpArea > biggestContourArea)
{
biggestContourArea = tmpArea;
biggestContourID = i;
}
}

if(biggestContourID >= 0)
{
//std::cout << "found area: " << biggestContourArea << std::endl;
// found biggest contour
// add contour to sorted contours vector:
sortedContours.push_back(contours[biggestContourID]);
chosen++;
// remove biggest contour from original vector:
contours[biggestContourID] = contours.back();
contours.pop_back();
}
else
{
// should never happen except for broken contours with size 0?!?
return sortedContours;
}

}

return sortedContours;
}

int main()
{
cv::Mat input = cv::imread("../Data/glass2.png", CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat inputColors = cv::imread("../Data/glass2.png"); // used for displaying later
cv::imshow("input", input);

//edge detection
int lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;

cv::Mat canny;
cv::Canny(input, canny, lowThreshold, lowThreshold*ratio, kernel_size);
cv::imshow("canny", canny);

// close gaps with "close operator"cv::Mat mask = canny.clone();
cv::dilate(mask,mask,cv::Mat());
cv::dilate(mask,mask,cv::Mat());
cv::dilate(mask,mask,cv::Mat());
cv::erode(mask,mask,cv::Mat());
cv::erode(mask,mask,cv::Mat());
cv::erode(mask,mask,cv::Mat());

cv::imshow("closed mask",mask);

// extract outermost contour
std::vector<cv::Vec4i> hierarchy;
std::vector<std::vector<cv::Point>> contours;
//cv::findContours(mask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
cv::findContours(mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);// find biggest contour which should be the outer contour of the frame
std::vector<std::vector<cv::Point>> biggestContour;
biggestContour = findBiggestContours(contours,1); // find the one biggest contour
if(biggestContour.size() < 1)
{
std::cout << "Error: no outer frame of glasses found" << std::endl;
return 1;
}

// draw contour on an empty image
cv::Mat outerFrame = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1);
cv::drawContours(outerFrame,biggestContour,0,cv::Scalar(255),-1);
cv::imshow("outer frame border", outerFrame);

// now find the glasses which should be the outer contours within the frame. therefore erode the outer border ;)
cv::Mat glassesMask = outerFrame.clone();
cv::erode(glassesMask,glassesMask, cv::Mat());
cv::imshow("eroded outer",glassesMask);

// after erosion if we dilate, it's an Open-Operator which can be used to clean the image.
cv::Mat cleanedOuter;
cv::dilate(glassesMask,cleanedOuter, cv::Mat());
cv::imshow("cleaned outer",cleanedOuter);// use the outer frame mask as a mask for copying canny edges. The result should be the inner edges inside the frame only
cv::Mat glassesInner;
canny.copyTo(glassesInner, glassesMask);

// there is small gap in the contour which unfortunately cant be closed with a closing operator...
cv::dilate(glassesInner, glassesInner, cv::Mat());
//cv::erode(glassesInner, glassesInner, cv::Mat());
// this part was cheated... in fact we would like to erode directly after dilation to not modify the thickness but just close small gaps.
cv::imshow("innerCanny", glassesInner);// extract contours from within the frame
std::vector<cv::Vec4i> hierarchyInner;
std::vector<std::vector<cv::Point>> contoursInner;
//cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

// find the two biggest contours which should be the glasses within the frame
std::vector<std::vector<cv::Point>> biggestInnerContours;
biggestInnerContours = findBiggestContours(contoursInner,2); // find the one biggest contour
if(biggestInnerContours.size() < 1)
{
std::cout << "Error: no inner frames of glasses found" << std::endl;
return 1;
}

// draw the 2 biggest contours which should be the inner glasses
cv::Mat innerGlasses = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1);
for(unsigned int i=0; i<biggestInnerContours.size(); ++i)
cv::drawContours(innerGlasses,biggestInnerContours,i,cv::Scalar(255),-1);

cv::imshow("inner frame border", innerGlasses);

// since we dilated earlier and didnt erode quite afterwards, we have to erode here... this is a bit of cheating :-(
cv::erode(innerGlasses,innerGlasses,cv::Mat() );

// remove the inner glasses from the frame mask
cv::Mat fullGlassesMask = cleanedOuter - innerGlasses;
cv::imshow("complete glasses mask", fullGlassesMask);

// color code the result to get an impression of segmentation quality
cv::Mat outputColors1 = inputColors.clone();
cv::Mat outputColors2 = inputColors.clone();
for(int y=0; y<fullGlassesMask.rows; ++y)
for(int x=0; x<fullGlassesMask.cols; ++x)
{
if(!fullGlassesMask.at<unsigned char>(y,x))
outputColors1.at<cv::Vec3b>(y,x)[1] = 255;
else
outputColors2.at<cv::Vec3b>(y,x)[1] = 255;

}

cv::imshow("output", outputColors1);

/*
cv::imwrite("../Data/Output/face_colored.png", outputColors1);
cv::imwrite("../Data/Output/glasses_colored.png", outputColors2);
cv::imwrite("../Data/Output/glasses_fullMask.png", fullGlassesMask);
*/

cv::waitKey(-1);
return 0;
}

Я получаю этот результат для сегментации:

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

Наложение исходного изображения создаст впечатление качества:

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

и обратный:

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

В коде есть некоторые хитрые части, и он еще не приведен в порядок. Надеюсь это понятно.

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

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

3

Другие решения

В зависимости от освещения, цвета рамки и т. Д. Это может или не может работать, но как насчет простого определения цвета для отделения кадра? Цвет рамки, как правило, будет намного темнее кожи человека. В итоге вы получите двоичное изображение (только черно-белое) и, рассчитав количество (площадь) черных пикселей, вы получите площадь кадра.

Другой возможный способ — улучшить обнаружение краев, отрегулировав / расширив / размыв / оба, пока вы не получите лучшие контуры. Вам также нужно будет отличить контур от линз, а затем применить cvContourArea.

1