На самом деле мы работаем над проектом анализа изображений, в котором нам нужно идентифицировать объекты, исчезнувшие / появившиеся на сцене. Вот 2 изображения, одно из которых было сделано до того, как хирург совершил какое-то действие, а другое — после.
Во-первых, мы только что рассчитали разницу между двумя изображениями, и вот результат (обратите внимание, что я добавил 128 к результату Mat
просто чтобы получше был имидж)
Цель состоит в том, чтобы обнаружить, что чашка (красная стрелка) исчезла со сцены и шприц (черная стрелка) вошел в сцену, другими словами, мы должны обнаруживать ТОЛЬКО области, которые соответствуют объектам, оставленным / введенным в сцену. Кроме того, очевидно, что объекты в верхнем левом углу сцены немного сместились от своего исходного положения. Я думал о Optical flow
поэтому я использовал OpenCV C++
вычислить единицу Farneback, чтобы увидеть, достаточно ли это для нашего случая, и вот результат, который мы получили, а затем код, который мы написали:
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
? Или есть какой-нибудь альтернативный способ определить, что нам нужно?
Допустим, цель здесь состоит в том, чтобы идентифицировать регионы с появившимися / исчезнувшими объектами, но не те, которые присутствуют на обоих рисунках, а просто перемещенные позиции.
Оптический поток должен быть хорошим способом, как вы уже сделали. Однако вопрос заключается в том, как оценивается результат. В отличие от разницы между пикселями, которая показывает, что она не допускает отклонения от поворота / масштабирования, вы можете выполнить сопоставление объектов (SIFT и т. Д.). Посмотрите здесь, что вы можете использовать с opencv)
Вот что я получил с помощью Good Features To Track из вашего изображения раньше.
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 для imageBefore), чтобы получить появившиеся регионы. 🙂
Вот что я попробовал;
Параметры могут нуждаться в настройке. Я использовал значения, которые только что работали для двух образцов изображений. В качестве детектора / дескриптора функции я использовал SIFT (не бесплатный). Вы можете попробовать другие детекторы и дескрипторы.
Изменения (красный: вставка / удаление, желтый: редкое движение):
// 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's 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);
}
}
Вы можете попробовать двухсторонний подход. Использование метода различий в изображениях отлично подходит для обнаружения объектов, которые входят и выходят из сцены, если цвет объекта отличается от цвета фона. Что меня поразило, так это то, что было бы значительно улучшено, если бы вы могли удалять объекты, которые были перемещены, перед использованием метода.
Существует отличный метод OpenCV для обнаружения объектов Вот который находит точки интереса в изображении для обнаружения перевода объекта. Я думаю, что вы могли бы достичь того, что вы хотите с помощью следующего метода —
1 Сравните изображения с кодом OpenCV и выделите движущиеся объекты на обоих изображениях.
2 Цвет в обнаруженных объектах с фоном другого изображения в том же наборе пикселей (или что-то похожее), чтобы уменьшить разницу в изображениях, вызванную движущимися изображениями
3 Найдите разницу в изображении, которая теперь должна иметь большие крупные объекты и меньшие артефакты, оставшиеся от движущихся изображений.
4 Порог для определенного размера объекта, обнаруженного в разнице изображения
5 Составьте список вероятных кандидатов
Существуют и другие альтернативы для отслеживания объектов, так что может быть, код вам больше нравится, но я думаю, что процесс должен быть в порядке для того, что вы делаете.