Подтвердить что ты не робот

Оптический поток игнорирует разреженные движения

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

ДО: введите описание изображения здесь ПОСЛЕ: введите описание изображения здесь

Сначала мы просто вычислили разницу между двумя изображениями, и вот результат (обратите внимание, что я добавил 128 к результату Mat, чтобы иметь более приятное изображение):

(ПОСЛЕ - ПЕРЕД) + 128 введите описание изображения здесь

Цель состоит в том, чтобы обнаружить, что чаша (красная стрелка) исчезла со сцены, и шприц (черная стрелка) вошел в сцену, другими словами мы должны обнаружить ТОЛЬКО области, которые соответствуют объектам, оставленным/введенным в сцена. Кроме того, очевидно, что объекты в левом верхнем углу сцены немного смещены от их исходного положения. Я думал о Optical flow, поэтому я использовал OpenCV C++ для вычисления Farneback, чтобы увидеть, достаточно ли этого для нашего случая, и вот результат, который мы получили, а затем код, который мы написали:

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

void drawOptFlowMap(const Mat& flow, Mat& cflowmap, int step, double, const Scalar& color)
{
    cout << flow.channels() << " / " << flow.rows << " / " << flow.cols << endl;
    for(int y = 0; y < cflowmap.rows; y += step)
        for(int x = 0; x < cflowmap.cols; x += step)
        {
            const Point2f& fxy = flow.at<Point2f>(y, x);
            line(cflowmap, Point(x,y), Point(cvRound(x+fxy.x), cvRound(y+fxy.y)), color);
            circle(cflowmap, Point(x,y), 1, color, -1);
        }
}

void MainProcessorTrackingObjects::diffBetweenImagesToTestTrackObject(string pathOfImageCaptured, string pathOfImagesAfterOneAction, string pathOfResultsFolder)
{
    //Preprocessing step...

    string pathOfImageBefore = StringUtils::concat(pathOfImageCaptured, imageCapturedFileName);
    string pathOfImageAfter = StringUtils::concat(pathOfImagesAfterOneAction, *it);

    Mat imageBefore = imread(pathOfImageBefore);
    Mat imageAfter = imread(pathOfImageAfter);

    Mat imageResult = (imageAfter - imageBefore) + 128;
    //            absdiff(imageAfter, imageBefore, imageResult);
    string imageResultPath = StringUtils::stringFormat("%s%s-color.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str());
    imwrite(imageResultPath, imageResult);

    Mat imageBeforeGray, imageAfterGray;
    cvtColor( imageBefore, imageBeforeGray, CV_RGB2GRAY );
    cvtColor( imageAfter, imageAfterGray, CV_RGB2GRAY );

    Mat imageResultGray = (imageAfterGray - imageBeforeGray) + 128;
    //            absdiff(imageAfterGray, imageBeforeGray, imageResultGray);
    string imageResultGrayPath = StringUtils::stringFormat("%s%s-gray.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str());
    imwrite(imageResultGrayPath, imageResultGray);


    //*** Compute FarneBack optical flow
    Mat opticalFlow;
    calcOpticalFlowFarneback(imageBeforeGray, imageAfterGray, opticalFlow, 0.5, 3, 15, 3, 5, 1.2, 0);

    drawOptFlowMap(opticalFlow, imageBefore, 5, 1.5, Scalar(0, 255, 255));
    string flowPath = StringUtils::stringFormat("%s%s-flow.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str());
    imwrite(flowPath, imageBefore);

    break;
}

И чтобы узнать, насколько точным этот оптический поток, я написал эту небольшую часть кода, которая вычисляет (IMAGEAFTER + FLOW) - IMAGEBEFORE:

//Reference method just to see the accuracy of the optical flow calculation
Mat accuracy = Mat::zeros(imageBeforeGray.rows, imageBeforeGray.cols, imageBeforeGray.type());

strinfor(int y = 0; y < imageAfter.rows; y ++)
for(int x = 0; x < imageAfter.cols; x ++)
{
     Point2f& fxy = opticalFlow.at<Point2f>(y, x);
     uchar intensityPointCalculated = imageAfterGray.at<uchar>(cvRound(y+fxy.y), cvRound(x+fxy.x));
     uchar intensityPointBefore = imageBeforeGray.at<uchar>(y,x);
     uchar intensityResult = ((intensityPointCalculated - intensityPointBefore) / 2) + 128;
     accuracy.at<uchar>(y, x) = intensityResult;
}
validationPixelBased = StringUtils::stringFormat("%s%s-validationPixelBased.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str());
 imwrite(validationPixelBased, accuracy);

Цель этого ((intensityPointCalculated - intensityPointBefore) / 2) + 128; состоит только в том, чтобы иметь понятное изображение.

РЕЗУЛЬТАТ ИЗОБРАЖЕНИЯ:

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

Поскольку он обнаруживает все области, которые были сдвинуты/введены/оставлены на сцене, мы считаем, что OpticalFlow недостаточно, чтобы обнаружить только те области, которые отображают объекты, которые исчезли/появились в сцене. Есть ли способ игнорировать разреженные движения, обнаруженные OpticalFlow? Или есть альтернативный способ определения того, что нам нужно?

4b9b3361

Ответ 1

Скажем, цель здесь состоит в том, чтобы идентифицировать области с появляющимися/исчезнувшими объектами, но не те, которые присутствуют на обеих картинках, а просто перемещены.

Оптический поток должен быть хорошим способом, как вы уже сделали. Однако проблема заключается в том, как оценивается результат. В отличие от различий между пикселями и пикселями, которые не показывают толерантности к отклонениям вращения/масштабирования, вы можете выполнить сопоставление функций (SIFT и т.д. Отъезд здесь для что вы можете использовать с opencv)

Здесь я получаю хорошие функции для отслеживания с вашего изображения раньше.

GoodFeaturesToTrackDetector detector;
vector<KeyPoint> keyPoints;
vector<Point2f> kpBefore, kpAfter;
detector.detect(imageBefore, keyPoints);

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

Вместо плотного оптического потока вы можете использовать разреженный поток и отслеживать только функции,

vector<uchar> featuresFound;
vector<float> err;
calcOpticalFlowPyrLK(imageBeforeGray, imageAfterGray, keyPointsBefore, keyPointsAfter, featuresFound, err, Size(PATCH_SIZE , PATCH_SIZE ));

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

vector<KeyPoint> kpNotMatched;
for (int i = 0; i < kpBefore.size(); i++) {
    if (!featuresFound[i] || err[i] > ERROR_THRESHOLD) {
        kpNotMatched.push_back(KeyPoint(kpBefore[i], 1));
    }
}
Mat output;
drawKeypoints(imageBefore, kpNotMatched, output, Scalar(0, 0, 255));  

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

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

Mat mask = Mat::zeros(imageBefore.rows, imageBefore.cols, CV_8UC1);
for (int i = 0; i < kpNotMatched.size(); i++) {
    mask.at<uchar>(kpNotMatched[i].pt) = 255;
}
blur(mask, mask, Size(BLUR_SIZE, BLUR_SIZE));
threshold(mask, mask, MASK_THRESHOLD, 255, THRESH_BINARY);

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

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

vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( mask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

vector<vector<Point> >hull( contours.size() );
for( int i = 0; i < contours.size(); i++ ) {
    convexHull(Mat(contours[i]), hull[i], false);
}
for( int i = 0; i < contours.size(); i++ ) {
    drawContours( output, hull, i, Scalar(0, 255, 255), 3, 8, vector<Vec4i>(), 0, Point() );
}

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

И просто сделайте это в обратном порядке (совпадение с imageAfter to imageBefore), чтобы получить регионы.:)

Ответ 2

Вот что я пробовал;

  • Обнаружение регионов, претерпевших изменения. Для этого я использую простое различие кадров, пороговое значение, морфологические операции и выпуклость.
  • Найдите в этих изображениях точки присутствия этих регионов и посмотрите, совпадают ли они. Хорошее совпадение в регионе указывает на то, что оно не претерпело существенных изменений. Плохой матч означает, что две области теперь разные. Для этого я использую расстояние BOW и Bhattacharyya.

Параметры могут нуждаться в настройке. Я использовал значения, которые только что работали для двух образцовых изображений. В качестве детектора признаков/дескриптора я использовал SIFT (несвободный). Вы можете попробовать другие детекторы и дескрипторы.

Изображение: diff

Регионы: regions

Изменения (красный: вставка/удаление, желтый: разреженное движение): changes

// for non-free modules SIFT/SURF
cv::initModule_nonfree();

Mat im1 = imread("1.png");
Mat im2 = imread("2.png");

// downsample
/*pyrDown(im1, im1);
pyrDown(im2, im2);*/

Mat disp = im1.clone() * .5 + im2.clone() * .5;
Mat regions = Mat::zeros(im1.rows, im1.cols, CV_8U);

// gray scale
Mat gr1, gr2;
cvtColor(im1, gr1, CV_BGR2GRAY);
cvtColor(im2, gr2, CV_BGR2GRAY);
// simple frame differencing
Mat diff;
absdiff(gr1, gr2, diff);
// threshold the difference to obtain the regions having a change
Mat bw;
adaptiveThreshold(diff, bw, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, 15, 5);
// some post processing
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
morphologyEx(bw, bw, MORPH_CLOSE, kernel, Point(-1, -1), 4);
// find contours in the change image
Mat cont = bw.clone();
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(cont, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point(0, 0));
// feature detector, descriptor and matcher
Ptr<FeatureDetector> featureDetector = FeatureDetector::create("SIFT");
Ptr<DescriptorExtractor> descExtractor = DescriptorExtractor::create("SIFT");
Ptr<DescriptorMatcher> descMatcher = DescriptorMatcher::create("FlannBased");

if( featureDetector.empty() || descExtractor.empty() || descMatcher.empty() )
{
    cout << "featureDetector or descExtractor or descMatcher was not created" << endl;
    exit(0);
}
// BOW
Ptr<BOWImgDescriptorExtractor> bowExtractor = new BOWImgDescriptorExtractor(descExtractor, descMatcher);

int vocabSize = 10;
TermCriteria terminate_criterion;
terminate_criterion.epsilon = FLT_EPSILON;
BOWKMeansTrainer bowTrainer( vocabSize, terminate_criterion, 3, KMEANS_PP_CENTERS );

Mat mask(bw.rows, bw.cols, CV_8U);
for(size_t j = 0; j < contours.size(); j++)
{
    // discard regions that a below a specific threshold
    Rect rect = boundingRect(contours[j]);
    if ((double)(rect.width * rect.height) / (bw.rows * bw.cols) < .01)
    {
        continue; // skip this region as it too small
    }
    // prepare a mask for each region
    mask.setTo(0);
    vector<Point> hull;
    convexHull(contours[j], hull);
    fillConvexPoly(mask, hull, Scalar::all(255), 8, 0);

    fillConvexPoly(regions, hull, Scalar::all(255), 8, 0);

    // extract keypoints from the region
    vector<KeyPoint> im1Keypoints, im2Keypoints;
    featureDetector->detect(im1, im1Keypoints, mask);
    featureDetector->detect(im2, im2Keypoints, mask);
    // get their descriptors
    Mat im1Descriptors, im2Descriptors;
    descExtractor->compute(im1, im1Keypoints, im1Descriptors);
    descExtractor->compute(im2, im2Keypoints, im2Descriptors);

    if ((0 == im1Keypoints.size()) || (0 == im2Keypoints.size()))
    {
        // mark this contour as object arrival/removal region
        drawContours(disp, contours, j, Scalar(0, 0, 255), 2);
        continue;
    }

    // bag-of-visual-words
    Mat vocabulary = bowTrainer.cluster(im1Descriptors);
    bowExtractor->setVocabulary( vocabulary );
    // get the distribution of visual words in the region for both images
    vector<vector<int>> idx1, idx2;
    bowExtractor->compute(im1, im1Keypoints, im1Descriptors, &idx1);
    bowExtractor->compute(im2, im2Keypoints, im2Descriptors, &idx2);
    // compare the distributions
    Mat hist1 = Mat::zeros(vocabSize, 1, CV_32F);
    Mat hist2 = Mat::zeros(vocabSize, 1, CV_32F);

    for (int i = 0; i < vocabSize; i++)
    {
        hist1.at<float>(i) = (float)idx1[i].size();
        hist2.at<float>(i) = (float)idx2[i].size();
    }
    normalize(hist1, hist1);
    normalize(hist2, hist2);
    double comp = compareHist(hist1, hist2, CV_COMP_BHATTACHARYYA);

    cout << comp << endl;
    // low BHATTACHARYYA distance means a good match of features in the two regions
    if ( comp < .2 )
    {
        // mark this contour as a region having sparse motion
        drawContours(disp, contours, j, Scalar(0, 255, 255), 2);
    }
    else
    {
        // mark this contour as object arrival/removal region
        drawContours(disp, contours, j, Scalar(0, 0, 255), 2);
    }
}

Ответ 3

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

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

1 Сравните изображения с кодом OpenCV и выделите движущиеся объекты в обоих изображениях

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

3 Найдите разницу изображений, которая должна теперь иметь большие крупные объекты и меньшие артефакты, оставшиеся от движущихся изображений

4 Порог для определенного размера объекта, обнаруженного в разнице изображений

5 Составьте список вероятных кандидатов

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