Jump to content
Compvision.ru
Sign in to follow this  
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

Share this post


Link to post
Share on other sites

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

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
Мне очень приятно, что вам понравилось. Я буду рад если вы ее подправите=)

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

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

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

Share this post


Link to post
Share on other sites

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

См. camshift и floodfill.

Share this post


Link to post
Share on other sites
Извините, что разрушаю идиллию, но в OpenCV полно подобного рода алгоритмов :)

См. camshift и floodfill.

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

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

Share this post


Link to post
Share on other sites
Извините, что разрушаю идиллию, но в OpenCV полно подобного рода алгоритмов :)

См. camshift и floodfill.

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

Share this post


Link to post
Share on other sites

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

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  

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

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

Share this post


Link to post
Share on other sites
меня интересует "пробег по всем пикселям".

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, как здесь.

Share this post


Link to post
Share on other sites

добрый день)

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

при компиляции он выдает ошибку со строчкой 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);

}

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

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

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

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

Share this post


Link to post
Share on other sites

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

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

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

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

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

Убрать все связанное с 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 );

Share this post


Link to post
Share on other sites

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

Сначала думал беда в заданном классе 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);

}


Share this post


Link to post
Share on other sites

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

а тут

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

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

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

Share this post


Link to post
Share on other sites

Ну на цикл планы были, убрать его не долго, за 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 ==========

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites


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]);

                                }

                        }

                }

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites


bufOb.clear();

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

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

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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×