Обнаружение монет (и подгонки эллипсов) на изображении
В настоящее время я работаю над проектом, в котором я пытаюсь обнаружить несколько монет, лежащих на плоской поверхности (например, стол). Монеты не перекрываются и не скрыты другими предметами. Но могут быть видны другие объекты, и условия освещения могут быть не идеальными... По сути, вы снимаете свой стол, на котором есть монеты.
Таким образом, каждая точка должна быть видна как эллипс. Поскольку я не знаю положение камеры, форма эллипсов может варьироваться от круга (вид сверху) до плоских эллипсов в зависимости от угла, из которого сняты монеты.
Моя проблема в том, что я не уверен в том, как извлечь монеты и, наконец, наложить на них эллипсы (которые я ищу для дальнейших расчетов).
На данный момент я только что сделал первую попытку, установив пороговое значение в OpenCV, используя findContours() для получения контурных линий и подгонки эллипса. К сожалению, контурные линии редко дают мне форму монет (отражения, плохое освещение,...), и этот способ также не является предпочтительным, так как я не хочу, чтобы пользователь устанавливал какой-либо порог.
Другая идея заключалась в том, чтобы использовать метод сопоставления шаблонов эллипса на этом изображении, но, поскольку я не знаю ни угла камеры, ни размера эллипсов, я не думаю, что это сработает хорошо...
Поэтому я хотел спросить, может ли кто-нибудь сказать мне метод, который будет работать в моем случае...
Есть ли быстрый способ извлечь три монеты из изображения? Расчеты должны быть сделаны в реальном времени на мобильных устройствах, и метод не должен быть слишком чувствительным для различных или меняющихся источников света или цвета фона.
Было бы здорово, если бы кто-нибудь мог дать мне какие-нибудь советы о том, какой метод мог бы работать для меня...
3 ответа
Вот некоторый источник C99, реализующий традиционный подход (основанный на OpenCV doco):
#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
//
// We need this to be high enough to get rid of things that are too small too
// have a definite shape. Otherwise, they will end up as ellipse false positives.
//
#define MIN_AREA 100.00
//
// One way to tell if an object is an ellipse is to look at the relationship
// of its area to its dimensions. If its actual occupied area can be estimated
// using the well-known area formula Area = PI*A*B, then it has a good chance of
// being an ellipse.
//
// This value is the maximum permissible error between actual and estimated area.
//
#define MAX_TOL 100.00
int main( int argc, char** argv )
{
IplImage* src;
// the first command line parameter must be file name of binary (black-n-white) image
if( argc == 2 && (src=cvLoadImage(argv[1], 0))!= 0)
{
IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3 );
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contour = 0;
cvThreshold( src, src, 1, 255, CV_THRESH_BINARY );
//
// Invert the image such that white is foreground, black is background.
// Dilate to get rid of noise.
//
cvXorS(src, cvScalar(255, 0, 0, 0), src, NULL);
cvDilate(src, src, NULL, 2);
cvFindContours( src, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
cvZero( dst );
for( ; contour != 0; contour = contour->h_next )
{
double actual_area = fabs(cvContourArea(contour, CV_WHOLE_SEQ, 0));
if (actual_area < MIN_AREA)
continue;
//
// FIXME:
// Assuming the axes of the ellipse are vertical/perpendicular.
//
CvRect rect = ((CvContour *)contour)->rect;
int A = rect.width / 2;
int B = rect.height / 2;
double estimated_area = M_PI * A * B;
double error = fabs(actual_area - estimated_area);
if (error > MAX_TOL)
continue;
printf
(
"center x: %d y: %d A: %d B: %d\n",
rect.x + A,
rect.y + B,
A,
B
);
CvScalar color = CV_RGB( rand() % 255, rand() % 255, rand() % 255 );
cvDrawContours( dst, contour, color, color, -1, CV_FILLED, 8, cvPoint(0,0));
}
cvSaveImage("coins.png", dst, 0);
}
}
Учитывая бинарное изображение, предоставленное Карньери, это вывод:
./opencv-contour.out coin-ohtsu.pbm
center x: 291 y: 328 A: 54 B: 42
center x: 286 y: 225 A: 46 B: 32
center x: 471 y: 221 A: 48 B: 33
center x: 140 y: 210 A: 42 B: 28
center x: 419 y: 116 A: 32 B: 19
И это выходное изображение:
Что вы могли бы улучшить:
- Обработка различных ориентаций эллипса (в настоящее время я предполагаю, что оси перпендикулярны / горизонтальны). Это не будет трудно сделать, используя моменты изображения.
- Проверьте выпуклость объекта (посмотрите на
cvConvexityDefects
)
Ваш лучший способ отличить монеты от других предметов, вероятно, будет по форме. Я не могу думать о каких-либо других низкоуровневых функциях изображения (цвет явно отсутствует). Итак, я могу придумать два подхода:
Традиционное обнаружение объектов
Ваша первая задача - отделить объекты (монеты и не монеты) от фона. Метод Оцу, предложенный Карньери, будет хорошо работать здесь. Вы, кажется, беспокоитесь о том, что изображения двусторонние, но я не думаю, что это будет проблемой. До тех пор, пока видимое количество рабочих мест, вы гарантированно будете иметь один пик в гистограмме. И пока на столе есть пара визуально различимых объектов, вам гарантирован ваш второй пик.
Расширьте ваше двоичное изображение пару раз, чтобы избавиться от шума, оставляемого порогом. Монеты относительно большие, поэтому они должны пережить эту морфологическую операцию.
Сгруппируйте белые пиксели в объекты, используя увеличение области - просто итеративно соедините смежные пиксели переднего плана. В конце этой операции у вас будет список непересекающихся объектов, и вы будете знать, какие пиксели занимает каждый объект.
Из этой информации вы узнаете ширину и высоту объекта (из предыдущего шага). Итак, теперь вы можете оценить размер эллипса, который будет окружать объект, а затем посмотреть, насколько хорошо этот конкретный объект соответствует эллипсу. Может быть проще просто использовать соотношение ширины и высоты.
Кроме того, вы можете использовать моменты, чтобы более точно определить форму объекта.
Если кто-то еще столкнется с этой проблемой в будущем, как я, но с использованием C++:
После того, как вы использовали findContours
чтобы найти контуры (как в ответе Миши выше), вы можете легко подогнать эллипсы, используя fitEllipse
например,
vector<vector<Point> > contours;
findContours(img, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0,0));
RotatedRect rotRecs[contours.size()];
for (int i = 0; i < contours.size(); i++) {
rotRecs[i] = fitEllipse(contours[i]);
}
Я не знаю, как лучше всего решить вашу проблему. Что касается порогового значения, в частности, однако, вы можете использовать метод Оцу, который автоматически находит оптимальное пороговое значение на основе анализа гистограммы изображения. Используйте метод порога OpenCV с параметром ThresholdType
равно THRESH_OTSU
,
Имейте в виду, однако, что метод Оцу хорошо работает только на изображениях с бимодальными гистограммами (например, изображения с яркими объектами на темном фоне).
Вы, наверное, видели это, но есть также метод для подгонки эллипса вокруг набора двухмерных точек (например, связанного компонента).
РЕДАКТИРОВАТЬ: метод Оцу применяется к образцу изображения:
Полутоновое изображение:
Результат применения метода Оцу: