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

Поиск зелёных объектов на изображении

Recommended Posts

Не судите строго, я недавно начал заниматься OpenCV и Компьютерным зрением в целом, да и статьи не умею толком писать. Данная статья, хотя это скорее тезис, явилась промежуточным итогом моей работы. Мне бы очень было бы интересно услышать ваши мнения без разнице какие. Код программы приведенной в статье написан для Microsoft Visual C++.

Поиск объектов на изображении является одной из важнейших составляющих компьютерного зрения. На сегодняшний момент существует множество способов выделения объектов на изображении, таких как выделение по цвету, по яркости, по структуре. В этой статье описывается попытка создания компьютерной программы которая могла бы выделять зелёные объекты на изображении в реальном времени.

Рассмотрим в начале задачу, в которой программа ищет только один объект. Изображение состоит из множества отдельных частей, пикселей и наша задача сводится к нахождению массива пикселей одного цвета, в нашем случае зелёного. Будем считать, что зелёный пиксель это такой пиксель у которого зелёная составляющая его цвета (в пространстве RGB) превышает остальные составляющие в k раз. Коэффициент может варьироваться в зависимости от освещённости и от яркости зелёного цвета более устойчивого распознавания объекта. Проверив все пиксели по указанному выше критерию, можно получить массив G всех зелёных пикселей на изображении. Предположим, что на изображении всего один объект, тогда массив G содержит только пиксели принадлежащие этому объекту. Координаты центра масс, который ввиду равномерной плотности (веса точек одинаковы) совпадает с геометрическим центром, можно определить сложив между собой координаты всех его составляющих и разделив полученную сумму на их количество. Такой способ определения зелёного объекта является простым и как показали опыты работоспособным, но его можно усложнить, введя вероятность принадлежности пикселя к объекту.

Алгоритм нахождения нескольких объектов ни чем не отличается от алгоритма нахождения одного объекта. Просто в этом случае массив G будет содержать элементы всех объектов, а не только одного. По этому задача сводится к разделению массива G на отдельные массивы I1, I2, … , In которые содержали бы в себе элементы отдельных объектов. Если в качестве массива G использовать само изображение, то отделить зелёные пиксели от остальных можно просто погасив все пиксели, а зелёные сделать белыми, что приведёт к получению двухцветной картинки (бинаризация). Следующий шаг в этом процессе, найти отдельные объекты для этого можно организовать цикл по всем пикселям дабы найти все белые пиксели, и при нахождении такого пикселя производить заливку всей белой области другим цветом(цветом объекта), и продолжать поиск далее с этого же места.

Для более детального понимания алгоритма ниже приведен исходный код программы. Программа написана на языке C++ и использует библиотеку OpenCV 1.1 для работы с изображениями и камерой.

#include <cv.h>

#include <highgui.h>

#include <ctype.h>

#include <stdio.h>

#include <stdlib.h>


#include <iostream>

#include <vector>


using namespace std;

// Размер картинки

	int width;

	int height;

//Класс объекта

class ob{

public:

	char id; //Цвет

	int M00,M10,M01;

	int Xc,Yc;

	ob(){

		M00=M10=M01=0;

	}

	void addPoint(int x, int y){

		M00++;

		M10+=x;

		M01+=y;

	}

	CvPoint getCenter(){

		Xc=int(M10/M00);

		Yc=int(M01/M00);

		return cvPoint(Xc,Yc);

	}

};

//---Три функции алгоритма заливки

void NearPix(uchar** ptr,int x, int y, int *numStack, CvPoint *Stack, ob &Obj){

	if(ptr[y][3*x]==255){

		ptr[y][3*x] = 0;

		ptr[y][3*x+1] = 255;

		ptr[y][3*x+2] = 0;

		Stack[*numStack].x=x;

		Stack[*numStack].y=y;

		(*numStack)++;

		Obj.addPoint(x,y);

	}

}


void OneStep(uchar** ptr, int *numSrc, int *numDest, CvPoint *Src, CvPoint *Dest, ob &Obj){

	int x,y,i;

	*numDest=0;

	for(i=0; i<*numSrc;i++){

		x=Src[i].x;

		y=Src[i].y;

		NearPix(ptr,x+1,y,numDest,Dest,Obj);

		NearPix(ptr,x-1,y,numDest,Dest,Obj);

		NearPix(ptr,x,y+1,numDest,Dest,Obj);

		NearPix(ptr,x,y-1,numDest,Dest,Obj);

	}

}


void WaveFill(uchar** ptr,int xst, int yst, ob &Obj){


	Obj.addPoint(xst,yst);

	ptr[yst][3*xst] = 0;

	ptr[yst][3*xst+1] = Obj.id;

	ptr[yst][3*xst+2] = 0;


	int numA, numB;

	CvPoint *stackA, *stackB;


	stackA=new CvPoint[10000];

	stackB=new CvPoint[10000];

	numA=1;

	stackA[0].x=xst;

	stackA[0].y=yst;

	numB=0;

	while(1){

		if(numA>0)OneStep(ptr,&numA,&numB,stackA,stackB,Obj);

		else break;

		if(numB>0)OneStep(ptr,&numB,&numA,stackB,stackA,Obj);

		else break;

	}

	delete [] stackB;

	delete [] stackA;

}

//---Начало программы

int main(int argc, char* argv[])

{

	vector<ob> bufOb;	//Буфер объектов


	cvNamedWindow( "Example2", CV_WINDOW_AUTOSIZE );

	CvCapture* capture = cvCreateCameraCapture(0);

	IplImage* frame;

	width = (int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH);

	height = (int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT);

	IplImage* buf = cvCreateImage( cvSize(width,height), 8, 3 );


	uchar** ptra=new uchar*[height];


	while(1){

		frame = cvQueryFrame( capture );

		if( !frame ) break;


		cvCopy(frame,buf);


		cvSmooth( frame, frame, CV_GAUSSIAN, 5, 5 );

		uchar* ptr;

		//Первый прход по всем пикселям

		for( int y=0; y<height; y++ ){

			ptr = (uchar*)(frame->imageData + y * frame->widthStep);

			for( int x=0; x<width; x++ ){

				//Гасим всё кроме зелёного

				bool bGreen = ptr[3*x]+15<ptr[3*x+1]&&ptr[3*x+2]+15<ptr[3*x+1];

				ptr[3*x] = ptr[3*x+1] = ptr[3*x+2] = bGreen*255;

			}

			ptra[y]=ptr;

		}

		//Рисуем рамку чёрного цвета по границе изображения

		//для ограничения заливки

		cvRectangle(frame,

			cvPoint(0,0),

			cvPoint(width-1,height-1),

			cvScalar(0,0,0)

		);

		//Второй проход по всем пикселям в котором применяется заливка

		//для разделения объектов, причём шаг цикла это ограничение

		//на размер объекта

		for( int y=0; y<height; y+=1 ){

			for( int x=0; x<width; x+=1 ){

				if(ptra[y][3*x]==255){

					ob newOb();

					bufOb.push_back(newOb);

					WaveFill(ptra,x,y,bufOb[bufOb.size()-1]);

				}

			}

		}


		for(int i=0; i<bufOb.size(); i++){

			cvCircle (frame,bufOb[i].getCenter(),5,cvScalar(0,0,255),3,2);

		}

		//Очистка буфера объектов

		bufOb.clear();

		cvShowImage( "Example2", frame );

		char c = cvWaitKey(33);

		if( c == 27 ) break;

	}

 cvReleaseCapture( &capture );

 cvDestroyWindow( "Example2" );

}

  • Like 1

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


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

Мне нравится, но если можно подправлю, чуток (по стилю) с Вашего согласия и в pdf запишу.

С сохранением авторства, разумеется.

Спасибо за отличный материал.

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


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

Мне очень приятно, что вам понравилось. Я буду рад если вы ее подправите=)

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


Ссылка на сообщение
Поделиться на других сайтах
Мне очень приятно, что вам понравилось. Я буду рад если вы ее подправите=)

В pdf передумал, и так вроде неплохо. Немного поправил.

PS: Для выделения объектов по цвету обычно работают в HSV пространстве, а не RGB, просто это легче.

Ссылка с картинками по HSV.

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


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

Извините, что разрушаю идиллию, но в OpenCV полно подобного рода алгоритмов :)

См. camshift и floodfill.

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


Ссылка на сообщение
Поделиться на других сайтах
Извините, что разрушаю идиллию, но в OpenCV полно подобного рода алгоритмов :)

См. camshift и floodfill.

Никто не спорит :)

Просто чтобы лучше что то понять, иногда надо соорудить велосипед :)

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


Ссылка на сообщение
Поделиться на других сайтах
Извините, что разрушаю идиллию, но в OpenCV полно подобного рода алгоритмов :)

См. camshift и floodfill.

Спасибо за информацию, я недавно в этой области и со многим ещё не ознакомился. Я знал, что я изобретаю велосипед, но это больше для тренировки как лабораторная работа.

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


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

меня интересует "пробег по всем пикселям".

uchar* ptr;

        //Первый прход по всем пикселям

        for( int y=0; y<height; y++ ){

            ptr = (uchar*)(frame->imageData + y * frame->widthStep);

            for( int x=0; x<width; x++ ){

                //Гасим всё кроме зелёного

                bool bGreen = ptr[3*x]+15<ptr[3*x+1]&&ptr[3*x+2]+15<ptr[3*x+1];

                ptr[3*x] = ptr[3*x+1] = ptr[3*x+2] = bGreen*255;

            }

            ptra[y]=ptr;

        }
следующей строчкой мы получаем столбец, я правильно понимаю? ptr = (uchar*)(frame->imageData + y * frame->widthStep); почему именно такие числа
ptr[3*x]+15  

объясните пожалуйста принцип.

совсем не понимаю

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


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

uchar* ptr;

//Первый прход по всем пикселям

for( int y=0; y<height; y++ ){

ptr = (uchar*)(frame->imageData + y * frame->widthStep);

for( int x=0; x<width; x++ ){

//Гасим всё кроме зелёного

bool bGreen = ptr[3*x]+15<ptr[3*x+1]&&ptr[3*x+2]+15<ptr[3*x+1];

ptr[3*x] = ptr[3*x+1] = ptr[3*x+2] = bGreen*255;

}

ptra[y]=ptr;

}

следующей строчкой мы получаем столбец, я правильно понимаю?

ptr = (uchar*)(frame->imageData + y * frame->widthStep);

почему именно такие числа

ptr[3*x]+15

объясните пожалуйста принцип.

совсем не понимаю

Это фильтр по компонентам цвета (ptr[3*x],ptr[3*x+1],ptr[3*x+2] - это синяя, зеленая и красная составляющие цвета) , т.к. зеленый, это не обязательно чисто зеленый цвет, он может содержать примеси красного и синего. Этим условием автор ограничивает количество примесных цветов. Но как уже говорилось в этой теме, работать лучше в пространстве HSV, а не RGB, как здесь.

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


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

добрый день)

меня интересует вопрос. а кто нибудь пытался данный пример запускать?

при компиляции он выдает ошибку со строчкой bufOb.push_back(newOb);

суть ошибки в несоответствии типов.

но если даже убрать эту часть

//Второй проход по всем пикселям в котором применяется заливка

//для разделения объектов, причём шаг цикла это ограничение

//на размер объекта

for( int y=0; y<height; y+=1 ){

for( int x=0; x<width; x+=1 ){

if(ptra[y][3*x]==255){

ob newOb();

bufOb.push_back(newOb);

WaveFill(ptra,x,y,bufOb[bufOb.size()-1]);

}

}

}

for(int i=0; i<bufOb.size(); i++){

cvCircle (frame,bufOb.getCenter(),5,cvScalar(0,0,255),3,2);

}

то все равно по ходу выполнения программы будут появляться новые ошибки уже в ходе выполнения программы.

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


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

Несоответствия типов там нет. Приведи лучше полный текст ошибки.

Если же ты эту строку уберёшь, то, разумеется, появятся новые ошибки из-за обращения к пустому массиву по индексу - в него же ничего не добавляется.

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


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

это скорей всего из за того что у меня стоит OpenCV 1.0. как раз собирался обновлять..потом отпишусь как и что))

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


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

Добрый день!)

Я, конечно, блонда и всё такое, поэтому, не обращайте внимание, если туплю)

У меня такая проблема - хочу понять работу алгоритма camshift. И вот, нахожу его упоминание в этой теме. Может, кто-то поможет?)

Заранее благодарна!

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


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

Добрый день!)

Я, конечно, блонда и всё такое, поэтому, не обращайте внимание, если туплю)

У меня такая проблема - хочу понять работу алгоритма camshift. И вот, нахожу его упоминание в этой теме. Может, кто-то поможет?)

Заранее благодарна!

Описание камшифта есть здесь.

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


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

полезная статья, спасибо!

а как можно переделать под загружаемый файл, допустим jpg?

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


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

Убрать все связанное с Capture, убрать цикл, и вместо

frame = cvQueryFrame( capture );
загрузить изображение:
frame = cvLoadImage("имаге.жпг");
только buf надо попозже создавать, когда известны параметры изображения: вместо этого:
  width = (int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH);
height = (int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT);
IplImage* buf = cvCreateImage( cvSize(width,height), 8, 3 );[/code] должно быть как то так:
[code]
width = frame.width;
height = frame.height;
IplImage* buf = cvCreateImage( cvSize(width,height), 8, 3 );

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


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

Собственно я так и сделал, разве только цикл не убивал, но это не решило мою проблему, он всё также ругается на буффер во втором прогоне....

Сначала думал беда в заданном классе ob ибо пишет: невозможно преобразовать "overloaded-function" в "ob"

но нет вроде.... в чём беда.... почему ругается?

Так же не решается вопрос с перегрузкой функции...


#include "stdafx.h"

#include <cv.h>

#include <highgui.h>

#include <ctype.h>

#include <stdio.h>

#include <stdlib.h>


#include <iostream>

#include <vector>


using namespace std;

// Размер картинки

        int width;

        int height;

//Класс объекта



......

//Начало такое же как и в оригинале...

......


IplImage* image = 0;


//---Начало программы

int main(int argc, char* argv[])

{

	 while(1){

        // имя картинки задаётся первым параметром

        char* filename = argc == 2 ? argv[1] : "E:\\1.jpg";


        vector<ob> bufOb;       //Буфер объектов


		cvNamedWindow( "Example2", CV_WINDOW_AUTOSIZE );

        image = cvLoadImage(filename,1);

        IplImage* frame;

        width = image->width;

        height = image->height;

        IplImage* buf = cvCreateImage( cvSize(width,height), 8, 3 );


        uchar** ptra=new uchar*[height];


         while(1){


                frame = cvLoadImage("E:\\1.jpg");

                if( !frame ) break;


                cvCopy(frame,buf);


                cvSmooth( frame, frame, CV_GAUSSIAN, 5, 5 );

                uchar* ptr;

                //Первый прход по всем пикселям

                for( int y=0; y<height; y++ ){

                        ptr = (uchar*)(frame->imageData + y * frame->widthStep);

                        for( int x=0; x<width; x++ ){

                                //Гасим всё кроме зелёного

                                bool bGreen = ptr[3*x]+15<ptr[3*x+1]&&ptr[3*x+2]+15<ptr[3*x+1];

                                ptr[3*x] = ptr[3*x+1] = ptr[3*x+2] = bGreen*255;

                        }

                        ptra[y]=ptr;

                }

                //Рисуем рамку чёрного цвета по границе изображения

                //для ограничения заливки

                cvRectangle(frame,

                        cvPoint(0,0),

                        cvPoint(width-1,height-1),

                        cvScalar(0,0,0)

                );

                //Второй проход по всем пикселям в котором применяется заливка

                //для разделения объектов, причём шаг цикла это ограничение

                //на размер объекта

                for( int y=0; y<height; y+=1 ){

                        for( int x=0; x<width; x+=1 ){

                                if(ptra[y][3*x]==255){

                                        ob newOb();

                                        bufOb.push_back(newOb);   // ТУТ ОШИБКА! - Пишет: Экземпляр перегружен функциями

                                        WaveFill(ptra,x,y,bufOb[bufOb.size()-1]);

                                }

                        }

                }


                for(int i=0; i<bufOb.size(); i++){  // ОШИБКА! А тут не соответствие типов со знаком и без..... 

                        cvCircle (frame,bufOb[i].getCenter(),5,cvScalar(0,0,255),3,2);

                }

                //Очистка буфера объектов

                bufOb.clear();

                cvShowImage( "Example2", frame );

                char c = cvWaitKey(33);

                if( c == 27 ) break;

	 }


 cvDestroyWindow( "Example2" );

 cvReleaseImage(& image);

}


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


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

bufOb.clear(); не уничтожает объекты вектора. Нужно в цикле разрушить каждый объект, а потом вызвать bufOb.clear();

а тут

for(int i=0; i<bufOb.size(); i++)
нужно так:
for(unsigned int i=0; i<bufOb.size(); i++)
Еще нужно убрать бесконечный цикл
while(1){

, зачем бесконечно считывать файл?

В общем, тут нужна еще большая лопата :)

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


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

Ну на цикл планы были, убрать его не долго, за unsigned спасибо, ошибка там пропала, но вот что делать с


bufOb.push_back(newOb);   // ТУТ ОШИБКА! - Пишет: Экземпляр перегружен функциями

на против пишет 6891d3136abba3e1c8a40358f71acda7.jpg в Выводе пишет

1>------ Построение начато: проект: opencv1, Конфигурация: Debug Win32 ------

1>Построение начато 28.12.2011 23:44:45.

1>InitializeBuildStatus:

1>  Обращение к "Debug\opencv1.unsuccessfulbuild".

1>ClCompile:

1>  Для всех выходных данных обновления не требуется.

1>  opencv1.cpp

1>e:\opencv\include\opencv2\flann\logger.h(66): warning C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

1>          e:\program files (x86)\microsoft visual studio 10.0\vc\include\stdio.h(234): см. объявление "fopen"

1>e:\opencv\include\opencv2\flann\flann.hpp(233): warning C4996: 'cv::flann::Index_<T>': объявлен deprecate

1>          e:\opencv\include\opencv2\flann\flann.hpp(278): см. ссылку на создание экземпляров класса шаблон при компиляции "cv::flann::Index_<T>"

1>e:\cprojects\opencv1\opencv1\opencv1.cpp(141): error C2664: void std::vector<_Ty>::push_back(_Ty &&): невозможно преобразовать параметр 1 из "ob (__cdecl *)(void)" в "ob &&"

1>          with

1>          [

1>              _Ty=ob

1>          ]

1>          Причина: невозможно преобразовать "overloaded-function" в "ob"

1>          Ни один конструктор не смог принять исходный тип, либо разрешение перегрузки конструктора неоднозначно

1>

1>СБОЙ построения.

1>

1>Затраченное время: 00:00:01.07

========== Построение: успешно: 0, с ошибками: 1, без изменений: 0, пропущено: 0 ==========

Возможно мой косяк, опять, но где?

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


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

В конструкторе класса ob, скорее всего. Дальше моя телепатия не идёт.

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


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


class ob{

public:

        char id; //Цвет

        int M00,M10,M01;

        int Xc,Yc;

        ob(){

                M00=M10=M01=0;

        }

        void addPoint(int x, int y){

                M00++;

                M10+=x;

                M01+=y;

        }

        CvPoint getCenter(){

                Xc=int(M10/M00);

                Yc=int(M01/M00);

                return cvPoint(Xc,Yc);

        }

};

возможно перегруз начинается тут

 vector<ob> bufOb;       //Буфер объектов

тут ошибка

for( int y=0; y<height; y+=1 ){

                        for( int x=0; x<width; x+=1 ){

                                if(ptra[y][3*x]==255){

                                        ob newOb();

                                        bufOb.push_back(newOb);

                                        WaveFill(ptra,x,y,bufOb[bufOb.size()-1]);

                                }

                        }

                }

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


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

Так надо же его через new создавать :)

ob* newOb;
newOb = new ob;[/code] или
[code]bufOb.push_back(new ob);
а в векторе указатели хранить:
vector<ob*> bufOb;

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


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


bufOb.clear();

класс завершается вместе с буфером, так как буфер его вызвал

не если через new создавать, то появляется ошибка о том, что класс не является именем типа....

честно говоря в именах типа я пока плохо разбираюсь...

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


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

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

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

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

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

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

Войти

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

Войти сейчас


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

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

×