Перейти к содержимому
Compvision.ru
privetvision

Выделение по цвету кожи

Recommended Posts

Попытаюсь структурировать всю свою кашу в голове:
  1. По совету дипломного руководителя изображение переводится в CIELAB формат, но везде почему-то используют HSV, кто прав? И в чем преимущества Lab'a перед HSV?
  2. Как я понял существуют несколько способов распознавания по цвету кожи:
    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 секунд камера снимает руку при этом вычитая задний фон и запоминает яркость руки. После этого она должна по идеи распознавать только цвет кожи.

  3. Появились проблемы на пункте 2.1 ранжирование диапазонов каналов.
    3.1. Сильно зависит от освещенности, при дневном свете нужны одни параметры, например: 

    mLmin = 60;  mLmax = 155;  mAmin = 120;  mAmax = 143;  mBmin = 139;  mBmax = 161;

    А при искусственном освещении другие.

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

  4. Конечный результат который надо получить:
    7c0agjY.png

 

    

У всех ли так работает метод 2.1? Цепляя другие объекты типо дверей и мебели?

Подскажите как быть? 
 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Еще больше запутаю smile.png

 

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  (даже код есть внизу).

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Читал, но мне все-таки мышкой нельзя выделять. Как это можно сделать "не вручную"?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

См. ссылку выше, там кроме кода можно скачать еще и датасет.

 

ЗЫ: Посмотрите еще здесь: http://white.stanfor...sych2012Project  (даже код есть внизу).

 

Да и код с SVM, требует выделения мышью только на стадии обучения.

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

См. ссылку выше, там кроме кода можно скачать еще и датасет.

 

 

Да и код с SVM, требует выделения мышью только на стадии обучения.

 

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

Что вы можете сказать по поводу вот этого

2.3 Модификация метода (дипломный руководитель называет это калибровкой), суть: 10-20 секунд камера снимает руку при этом вычитая задний фон и запоминает яркость руки. После этого она должна по идеи распознавать только цвет кожи.

По идеи, это тоже самое, что и кликать мышкой, только здесь упрощенный вариант?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Если в кадре только рука, то это также сбор образцов (положительных).

Но неплохо еще и отрицательные собрать.

 

В принципе, можно и через гистограммы и обратные проекции делать.

Тогда достаточно только положительных образцов.

См. opencv/samples/cpp/tutorial_code/Histograms_Matching

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Если в кадре только рука, то это также сбор образцов (положительных).

Но неплохо еще и отрицательные собрать.

 

В принципе, можно и через гистограммы и обратные проекции делать.

Тогда достаточно только положительных образцов.

См. opencv/samples/cpp/tutorial_code/Histograms_Matching

 

Спасибо, буду смотреть, разбираться. 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я бы приступил к решению задачи таким способом:

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 координаты в нём. Будет ли разница? Возможно, что в этом последнем случае качество может даже и улучшиться, но не обязательно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Нашел вот такую статейку http://www.morethantechnical.com/2013/03/05/skin-detection-with-probability-maps-and-elliptical-boundaries-opencv-wcode/

Что это за метод (как на русском языке называется) и где можно о нем почитать (на русском)?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

В теории вероятностей можно почитать smile.png

 

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, кстати, умеет смеси считать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

В теории вероятностей можно почитать smile.png

 

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;
}

Скрин отладки:post-7361-0-42231800-1430603223_thumb.pn

 

А вот второй код, который тоже пробовал, но тоже самое стопорится после 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;
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

https://archive.ics.uci.edu/ml/datasets/Skin+Segmentation

датасет для попиксельной классификации кожа\не кожа.

из минусов пожалуй то, что это не наглядно(нельзя посмотреть изначальную разметку)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

А как там скачать? Открывается директория, в которой лежит один единственный txt.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Тут видео с масками кожи: http://www.feeval.org/Data-sets/Skin_Colors.html

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

в которой лежит один единственный txt.

так это и есть датасет в формате  B G R Label

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

так это и есть датасет в формате  B G R Label

Ааааааа.... "Семён, Семёныч!" (С)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Сделал простой детектор кожи на питоне, который использует дерево 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) и будет ли результат лучше?

Изменено пользователем mrgloom

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×