privetvision 4 Жалоба Опубликовано April 29, 2015 Попытаюсь структурировать всю свою кашу в голове: По совету дипломного руководителя изображение переводится в CIELAB формат, но везде почему-то используют HSV, кто прав? И в чем преимущества Lab'a перед HSV? Как я понял существуют несколько способов распознавания по цвету кожи: 2.1 Ранжирование диапазона значений каждого канала. Я использую следующий код: cvtColor(vFrame, vFrameLab, cv::COLOR_BGR2Lab); inRange(vFrameLab, Scalar(mLmin, mAmin, mBmin), Scalar(mLmax, mAmax, mBmax), vFrameLab); imshow("LAB", vFrameLab); 2.2 Выделение мышкой участков кожи и поиск таких участков на изображении. Цитата из данной статьи: Построение модели рассматривается как задача поиска группы кластеров, соответствующих оттенкам кожи. Структура и параметры модели формируются путем обработки изображений участков кожи, выделенных вручную. Распределение, отражающее соотношение параметров цвета H и S, получено в данном примере в результате обработки порядка 900 изображений фрагментов кожи размерами 3×3 пиксела. 2.3 Модификация метода (дипломный руководитель называет это калибровкой), суть: 10-20 секунд камера снимает руку при этом вычитая задний фон и запоминает яркость руки. После этого она должна по идеи распознавать только цвет кожи. Появились проблемы на пункте 2.1 ранжирование диапазонов каналов. 3.1. Сильно зависит от освещенности, при дневном свете нужны одни параметры, например: mLmin = 60; mLmax = 155; mAmin = 120; mAmax = 143; mBmin = 139; mBmax = 161; А при искусственном освещении другие. 3.2. объекты похожие на цвет кожи: дверь, мебель и т.д. цепляются и в итоге становится каша на изобажении. Конечный результат который надо получить: У всех ли так работает метод 2.1? Цепляя другие объекты типо дверей и мебели? Подскажите как быть? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано April 29, 2015 Еще больше запутаю http://www.compvision.ru/forum/index.php/topic/861-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9-%D0%BF%D0%BE%D0%B8%D1%81%D0%BA-%D1%86%D0%B2%D0%B5%D1%82%D0%B0/?p=6146 Есть еще GrubCut (есть в стандартных примерах). А так, да выделение по цвету довольно чувствительно к освещению. ЗЫ: Посмотрите еще здесь: http://white.stanford.edu/teach/index.php/DXMPsych2012Project (даже код есть внизу). Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
privetvision 4 Жалоба Опубликовано April 29, 2015 Еще больше запутаю http://www.compvision.ru/forum/index.php/topic/861-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9-%D0%BF%D0%BE%D0%B8%D1%81%D0%BA-%D1%86%D0%B2%D0%B5%D1%82%D0%B0/?p=6146 Читал, но мне все-таки мышкой нельзя выделять. Как это можно сделать "не вручную"? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано April 29, 2015 См. ссылку выше, там кроме кода можно скачать еще и датасет. ЗЫ: Посмотрите еще здесь: http://white.stanfor...sych2012Project (даже код есть внизу). Да и код с SVM, требует выделения мышью только на стадии обучения. Обучаем классификатор, сохраняем его, а затем, когда надо применить, считываем и применяем к новому изображению. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
privetvision 4 Жалоба Опубликовано April 29, 2015 См. ссылку выше, там кроме кода можно скачать еще и датасет. Да и код с SVM, требует выделения мышью только на стадии обучения. Обучаем классификатор, сохраняем его, а затем, когда надо применить, считываем и применяем к новому изображению. Что вы можете сказать по поводу вот этого 2.3 Модификация метода (дипломный руководитель называет это калибровкой), суть: 10-20 секунд камера снимает руку при этом вычитая задний фон и запоминает яркость руки. После этого она должна по идеи распознавать только цвет кожи. По идеи, это тоже самое, что и кликать мышкой, только здесь упрощенный вариант? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано April 29, 2015 Если в кадре только рука, то это также сбор образцов (положительных). Но неплохо еще и отрицательные собрать. В принципе, можно и через гистограммы и обратные проекции делать. Тогда достаточно только положительных образцов. См. opencv/samples/cpp/tutorial_code/Histograms_Matching Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
privetvision 4 Жалоба Опубликовано April 30, 2015 Если в кадре только рука, то это также сбор образцов (положительных). Но неплохо еще и отрицательные собрать. В принципе, можно и через гистограммы и обратные проекции делать. Тогда достаточно только положительных образцов. См. opencv/samples/cpp/tutorial_code/Histograms_Matching Спасибо, буду смотреть, разбираться. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано April 30, 2015 Я бы приступил к решению задачи таким способом: 1. Набрал бы большую базу изображений с кожей, с разной яркостью, освещённостью, разных наций и народов. 2. Обязательно для них сделал ground truth - для каждого изображения сделал бинарную маску по типу пиксель кожи или не кожи (как вариант найти готовую размеченную базу или разметить тем же grub cut). 3. Все картинки базы визуализировал бы разом в 3D в разных цветовых пространствах: RGB, HSV, Lab. Визуализация - облако точек, каждая из которых является пикселем из изображения. Скорее всего пикселей будет много, поэтому с каждого изображения можно брать по 100-1000 пикселей. Также можно визуализировать картинки индивидуально. 4. Попробовал бы произвести кластеризацию всех пикселей кожи с помощью EM, результаты кластеризации также визуализировать (к облакам точек добавятся эллипсоиды). "Поиграть" с числом кластеров, посмотреть насколько точно они описывают точки. По результатам пунктов 3-4 ты увидишь, что в действительности представляет собой кожа в каждом из пространств, какое подходит лучше. И почему кожу нельзя выделить простыми порогами. 5. Обучить классификатор на имеющейся базе. В качестве классификатора для начала выбрать тот же EM. А после SVM с функциями типа RBF. 6. На контрольной выборке и своём видео посмотреть на результат, порадоваться успеху. Применить к результату мат. морфологию типа Открытие и сдать задание. P.S. Если есть дополнительное желание и время, то применить к базе пикселей кожи PCA (метод главных компонент). Полученные вектора визуализировать в виде новой системы координат вместе с облаками точек. И обучать, а также распознавать пиксели уже в новом пространстве. Будет ли разница? Далее попробовать обучить и распознавать пиксели не просто переводя их в это новое пространство, а беря только первые 2 координаты в нём. Будет ли разница? Возможно, что в этом последнем случае качество может даже и улучшиться, но не обязательно. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
privetvision 4 Жалоба Опубликовано May 2, 2015 Нашел вот такую статейку http://www.morethantechnical.com/2013/03/05/skin-detection-with-probability-maps-and-elliptical-boundaries-opencv-wcode/ Что это за метод (как на русском языке называется) и где можно о нем почитать (на русском)? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано May 2, 2015 В теории вероятностей можно почитать http://www.machinelearning.ru/wiki/index.php?title=%D0%91%D0%B0%D0%B9%D0%B5%D1%81%D0%BE%D0%B2%D1%81%D0%BA%D0%B8%D0%B9_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80 Собираем две гистограммы: "кожа" и "не кожа", нормализуем, так чтобы получить плотность распределения вероятности. Подсовываем новый пример, смотрим чему равна плотность вероятности в этом месте для "кожи" и для "не кожи". Чья вероятность больше тот и победил. Посмотрите еще гауссовы смеси (Gaussian Mixture Model или GMM ). http://www.ijmlc.org/papers/386-H0021.pdf Opencv, кстати, умеет смеси считать. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
privetvision 4 Жалоба Опубликовано May 2, 2015 В теории вероятностей можно почитать http://www.machinelearning.ru/wiki/index.php?title=%D0%91%D0%B0%D0%B9%D0%B5%D1%81%D0%BE%D0%B2%D1%81%D0%BA%D0%B8%D0%B9_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80 Собираем две гистограммы: "кожа" и "не кожа", нормализуем, так чтобы получить плотность распределения вероятности. Подсовываем новый пример, смотрим чему равна плотность вероятности в этом месте для "кожи" и для "не кожи". Чья вероятность больше тот и победил. Посмотрите еще гауссовы смеси (Gaussian Mixture Model или GMM ). http://www.ijmlc.org/papers/386-H0021.pdf Opencv, кстати, умеет смеси считать. Хотел попробовать по примерам посмотреть работу gmm + em, но на двух примерах стопорится программа после em->train(), оно во-первых выполняется минут 5 и потом стопорится, в чем может быть проблема? Вот код: #include <iostream> #include <set> #include <opencv2/core/core.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/ml.hpp> #include <opencv/cv.h> using std::string; using std::cout; using std::cerr; using std::endl; using std::flush; using std::vector; using namespace std; using namespace cv; using namespace cv::ml; bool is_black(cv::Vec3b c1) { return c1[0] == 0 && c1[1] == 0 && c1[2] == 0; } std::vector<cv::Vec3b> get_colors(const int n) { vector<cv::Vec3b> vec; vec.push_back(cv::Vec3b(255,0,0)); vec.push_back(cv::Vec3b(0,255,0)); vec.push_back(cv::Vec3b(0,0,255)); vec.push_back(cv::Vec3b(0,255,255)); vec.push_back(cv::Vec3b(255,255,0)); vec.push_back(cv::Vec3b(255,0,255)); for(int i=6; i < n; i++) { cv::Vec3b color(cv::saturate_cast<uchar>(rand()*255), cv::saturate_cast<uchar>(rand()*255), cv::saturate_cast<uchar>(rand()*255)); vec.push_back(color); } return vec; } int main(int argc, char** argv) { string filename = "5.png"; int num_clusters = 15; if(argc > 1 && string(argv[1]) == "help") { cout << "Usage: gmm [data file] [number clusters]" << endl; return EXIT_SUCCESS; } if(argc > 1) filename = string(argv[1]); if(argc > 2) num_clusters = atoi(argv[2]); cv::Mat data = cv::imread(filename); cv::Mat patterns(0,0, CV_32F); for(int r=0; r < data.rows; r++) for(int c=0; c < data.cols; c++) { cv::Vec3b p = data.at<cv::Vec3b>(r,c); if(!is_black(p)) { cv::Mat pat = (cv::Mat_<float>(1,2) << c,r); patterns.push_back(pat); } } const int cov_mat_type = EM::COV_MAT_GENERIC; EM::Params params; params.nclusters = num_clusters; params.covMatType = cov_mat_type; params.termCrit = cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 15000, 1e-6); Ptr<EM> gmm = EM::create(params); cout << "num samples: " << patterns.rows << endl; cv::Mat labels, posterior, logLikelihood; cout << "Training GMM... " << flush; gmm->train(patterns, logLikelihood, labels, posterior, params); cout << "Done!" << endl; cv::Mat display_img(data.size(), CV_8UC3); cv::Mat posterior_img(data.size(), CV_8U); vector<cv::Vec3b> colors = get_colors(num_clusters); // Draw points with labels for(int p=0; p < patterns.rows; p++) { cv::Mat pat = patterns.row(p); int r = pat.at<float>(0,1); int c = pat.at<float>(0,0); display_img.at<cv::Vec3b>(r,c) = colors[labels.at<int>(p,0)]; posterior_img.at<uchar>(r,c) = cv::saturate_cast<uchar>(posterior.at<float>(r,c)*255); } // draw components cv::Mat means = gmm->get<cv::Mat>("means"); vector<cv::Mat> covs = gmm->get<vector<cv::Mat> >("covs"); cv::Mat weights = gmm->get<cv::Mat>("weights"); for(int g=0; g < num_clusters; g++) { double cx = means.at<double>(g, 0); double cy = means.at<double>(g, 1); double w = weights.at<double>(0, g); cv::Mat cov = covs[g]; // draw centroid cv::circle(display_img, cv::Point(cx,cy), 2, cv::Scalar(255,255,255), CV_FILLED); // draw eigenvectors cv::Mat eigVal, eigVec; cv::eigen(cov, eigVal, eigVec); double eigVec1_len = sqrt(eigVal.at<double>(0,0)) * 3; double eigVec1_x = eigVec.at<double>(0,0) * eigVec1_len; double eigVec1_y = eigVec.at<double>(0,1) * eigVec1_len; double eigVec2_len = sqrt(eigVal.at<double>(1,0)) * 3; double eigVec2_x = eigVec.at<double>(1,0) * eigVec2_len; double eigVec2_y = eigVec.at<double>(1,1) * eigVec2_len; cv::line(display_img, cv::Point(cx,cy), cv::Point(cx+eigVec1_x, cy+eigVec1_y), cv::Scalar(255,255,255) ); cv::line(display_img, cv::Point(cx,cy), cv::Point(cx+eigVec2_x, cy+eigVec2_y), cv::Scalar(255,255,255) ); // draw ellipse along eigenvector 1 double angle = atan(eigVec1_y / eigVec1_x) * (180 / M_PI); cv::RotatedRect rect(cv::Point(cx, cy), cv::Size(eigVec1_len, eigVec2_len), angle); double min, max; cv::minMaxLoc(weights, &min, &max); uchar intensity = cv::saturate_cast<uchar>(w * 255.0 / max); cv::ellipse(display_img, rect, cv::Scalar(intensity,intensity,intensity), 1); } cv::namedWindow("display"); cv::namedWindow("posterior"); cv::imshow("display", display_img); cv::imshow("posterior", posterior_img); cv::waitKey(); return EXIT_SUCCESS; } Скрин отладки: А вот второй код, который тоже пробовал, но тоже самое стопорится после train #include <iostream> #include <opencv2/highgui/highgui.hpp> #include "opencv/cv.h" #include "opencv2/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/opencv.hpp> #include "opencv2/imgproc.hpp" #include "opencv2/ml.hpp" using namespace std; using namespace cv; using namespace cv::ml; int main() { cv::Mat source = cv::imread("4.png"); //ouput images cv::Mat meanImg(source.rows, source.cols, CV_32FC3); cv::Mat fgImg(source.rows, source.cols, CV_8UC3); cv::Mat bgImg(source.rows, source.cols, CV_8UC3); //convert the input image to float cv::Mat floatSource; source.convertTo(floatSource, CV_32F); //now convert the float image to column vector cv::Mat samples(source.rows * source.cols, 3, CV_32FC1); cv::Mat samples1(source.rows * source.cols, 3, CV_32FC1); int idx = 0; for (int y = 0; y < source.rows; y++) { cv::Vec3f* row = floatSource.ptr<cv::Vec3f > (y); for (int x = 0; x < source.cols; x++) { samples.at<cv::Vec3f > (idx++, 0) = row[x]; } } //we need just 2 clusters EM::Params params(2); params.covMatType = EM::COV_MAT_SPHERICAL; params.termCrit.maxCount = 300; params.termCrit.epsilon = 0.1; params.termCrit.type = CV_TERMCRIT_ITER|CV_TERMCRIT_EPS; //cv::ExpectationMaximization em(samples, cv::Mat(), params); Ptr<EM> em = EM::create(params); em->train(samples); //the two dominating colors cv::Mat means = em->getMeans(); //the weights of the two dominant colors cv::Mat weights = em->getWeights(); //we define the foreground as the dominant color with the largest weight const int fgId = weights.at<float>(0) > weights.at<float>(1) ? 0 : 1; //now classify each of the source pixels idx = 0; for (int y = 0; y < source.rows; y++) { for (int x = 0; x < source.cols; x++) { //classify const int result = cvRound(em->predict(samples.row(idx++))); //get the according mean (dominant color) const double* ps = means.ptr<double>(result, 0); //set the according mean value to the mean image float* pd = meanImg.ptr<float>(y, x); //float images need to be in [0..1] range pd[0] = ps[0] / 255.0; pd[1] = ps[1] / 255.0; pd[2] = ps[2] / 255.0; //set either foreground or background if (result == fgId) { fgImg.at<cv::Point3_<uchar> >(y, x, 0) = source.at<cv::Point3_<uchar> >(y, x, 0); } else { bgImg.at<cv::Point3_<uchar> >(y, x, 0) = source.at<cv::Point3_<uchar> >(y, x, 0); } } } cv::imshow("Means", meanImg); cv::imshow("Foreground", fgImg); cv::imshow("Background", bgImg); cv::waitKey(0); return 0; } Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано August 3, 2015 https://archive.ics.uci.edu/ml/datasets/Skin+Segmentationдатасет для попиксельной классификации кожа\не кожа.из минусов пожалуй то, что это не наглядно(нельзя посмотреть изначальную разметку) Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано August 3, 2015 А как там скачать? Открывается директория, в которой лежит один единственный txt. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано August 3, 2015 Тут видео с масками кожи: http://www.feeval.org/Data-sets/Skin_Colors.html 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано August 3, 2015 в которой лежит один единственный txt.так это и есть датасет в формате B G R Label 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Nuzhny 243 Жалоба Опубликовано August 3, 2015 так это и есть датасет в формате B G R LabelАаааааа.... "Семён, Семёныч!" (С) Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано October 8, 2015 (изменено) Сделал простой детектор кожи на питоне, который использует дерево http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html по сути можно использовать любой другой более сложный классификатор, но плюс дерева в том, что его легко интерпретировать.из минусов, непонятно какой конкретно алгоритм для построения дерева(id3, CART, C4.5) используется в scikit-learn, и я пока вывожу только feature_importances_ , неплохо было бы выводить еще и само дерево, чтобы понять какие решения оно приняло( по сути последовательность threshold'ов).https://github.com/mrgloom/Simple-skin-detection TODO:Неплохо было бы еще сделать визуализацию в 3D двух кластеров кожа/не кожа для разных цветовых пространств.Неплохо бы найти какую то базу для тестов побольше и посложнее. Еще вопрос по histogram backprojection по сути мы там моделируем распределение только для объекта(и кажется что в некоторых случаях выгодней моделировать фон?)? есть ли методы которые моделируют отдельно объект и фон(или это и есть, то чем занимается Gaussian Mixture Model) и будет ли результат лучше? Изменено October 8, 2015 пользователем mrgloom Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах