Lien entre un programme C++ avec Open CV et Arduino

Bonjour à tous,

Je réalise un projet (scolaire) où une partie consiste à détecter un mouvement à partir d'une webcam (branchée sur mon PC) et à allumer une LED branchée sur une carte Arduino. Donc j'aimerais que lorsqu'un mouvement est détecté (je prends 500 à titre d'exemple dans le main.cpp) la LED de la carte Arduino s'allume et s’éteint lorsqu'il n'y pas ou plus de mouvement.
J’utilise pour cela 2 programmes séparément, c'est-à-dire que je lance le programme de détection de mouvement (main.cpp) qui fonctionne avec Open CV. Le programme modifie une variable externe (var) que je réutilise dans le programme Arduino (arduino_command.ino) par l'intermédiaire d'un fichier header (Arduino_command.h).

Le programme main.cpp fonctionne correctement, mais lorsque je lance le programme Arduino, j'obtient l'erreur suivante :

Arduino_command.ino: In function void loop()
 
Arduino_command.ino: 12: error: expected primary-expression before 'extern
 
Arduino_command.ino: 12: error: expected `;' before 'extern
Error compiling project sources
Debug build failed for project 'Arduino_command'

Je comprends bien qu'il ne reconnaît pas le mot clé 'extern' pour ce code Arduino :

Arduino_command.ino

#include "Arduino_command.h"

int a = 0;

void setup()
{
	pinMode(11, OUTPUT);
}

void loop()
{
	a = extern var;
		if (a == 1) {
			digitalWrite(11, HIGH);
		}
		else {
			digitalWrite(11, LOW);
		}
}

Mais lorsque j'enlève le mot clé 'extern', j'ai le message suivant, me disant que la variable 'var' n'est pas définit, alors que je lance avant le programme (main.cpp) qui définit justement cette variable :

Arduino_command.cpp.o*: In function loop
Arduino_command.ino:12: undefined reference to var
Arduino_command.ino:12: undefined reference to var

Error linking for board Arduino Uno
Debug build failed for project 'Arduino_command'

D'ailleurs que je lance le programme 'main.cpp' ou pas cela ne change rien (l'erreur reste la même).
J’utilise une variable externe, car je pense que c'est la façon la plus simple de résoudre ce problème, mais visiblement Arduino n'a pas l'air d'aimer.

Ça fait une semaine que je suis dessus et je n'y arrive pas, j'ai essayer de créer des classes, des fonctions, en les important dans un fichier .ino par l'intermédiaire d'un fichier header (.h), mais je rencontre le même problème.
J'ai aussi regardé les codes proposé sur ce site :
http://aleksandarkrstikj.com/Tracking-a-ball-and-Rotating-Camera-with-OpenCV-and-Arduino/
Mais je ne comprends toujours pas comment je pourrais faire.

Je tiens à préciser que je suis débutant en programmation et surtout en C++, donc il est probablement possible de résoudre ce problème en utilisant des classes, mais même avec les recherches fait sur internet et les tests effectués, je ne vois pas comment faire.

Les 2 fichiers ne sont pas dans le même dossier, car j'utilise Visual Studio 2015 (avec Visual micro pour le programme Arduino) pour les 2 programmes et je dois les placer dans un dossier différents (je crée 2 projets différents). J'ai placé le programme Arduino avec le header dans un dossier 'Arduino_command' (même nom que le projet Arduino) qui se trouve dans le même répertoire que 'main.cpp'.

Voici les codes du 'main.cpp' et du 'Arduino_command.h' pour y voir plus clair :

Arduino_command.h

#ifndef _ARDUINO_COMMAND_h
#define _ARDUINO_COMMAND_h

extern int var;

#endif

main.cpp

#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>

#include "Arduino-command\test.h"

using namespace cv;
using namespace std;


void addImageToComposition(Mat& composedImage, Mat& image, const bool isGrayscale, const int quarter) {
	const auto width = composedImage.size().width;
	const auto height = composedImage.size().height;
	const auto halfWidth = width / 2;
	const auto halfHeight = height / 2;

	resize(image, image, Size(halfWidth, halfHeight));
	if (isGrayscale) {
		cvtColor(image, image, COLOR_GRAY2RGB);
	}

	Range rowsRange, colsRange;
	if (quarter == 1) {
		rowsRange = Range(0, halfHeight);
		colsRange = Range(halfWidth, width);
	}
	else if (quarter == 2) {
		rowsRange = Range(0, halfHeight);
		colsRange = Range(0, halfWidth);
	}
	else if (quarter == 3) {
		rowsRange = Range(halfHeight, height);
		colsRange = Range(0, halfWidth);
	}
	else if (quarter == 4) {
		rowsRange = Range(halfHeight, height);
		colsRange = Range(halfWidth, width);
	}

	image.copyTo(Mat(composedImage, rowsRange, colsRange));
}

void uniteRectangles(vector<Rect>& rectangles) {
	vector<char> useRectangles(rectangles.size(), 1);
	while (true) {
		auto wasChange = false;
		for (int i = 0; i < rectangles.size(); ++i) {
			if (!useRectangles[i]) {
				continue;
			}
			for (int j = i + 1; j < rectangles.size(); ++j) {
				if (useRectangles[j] && ((rectangles[i] & rectangles[j]).area() > 0)) {
					rectangles[i] |= rectangles[j];
					useRectangles[j] = false;
					wasChange = true;
				}
			}
		}
		if (!wasChange) {
			break;
		}
	}
	vector<Rect> resultRectangles;
	for (int i = 0; i < rectangles.size(); ++i) {
		if (useRectangles[i]) {
			resultRectangles.push_back(rectangles[i]);
		}
	}
	rectangles = resultRectangles;
}

int var = 0;

void processVideo() {
	VideoCapture capture(0);
	Mat cameraImage, blurredImage, blocksImage, contoursImage;
	Ptr<BackgroundSubtractorKNN> backgroundSubstructor = createBackgroundSubtractorKNN(10, 400, true);
	Mat dilateElement = getStructuringElement(MORPH_RECT, Size(32, 32));
	while (true) {
		capture.read(cameraImage);

		cvtColor(cameraImage, blurredImage, CV_BGR2GRAY);
		GaussianBlur(blurredImage, blurredImage, Size(21, 21), 0, 0);

		backgroundSubstructor->apply(blurredImage, blurredImage);
		threshold(blurredImage, blocksImage, backgroundSubstructor->getShadowValue() + 1, 255, THRESH_BINARY);
		dilate(blocksImage, blocksImage, dilateElement, Point(-1, -1), 2);

		contoursImage = blocksImage.clone();

		vector<vector<Point>> contours;
		vector<Vec4i> hierarchy;
		findContours(contoursImage, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

		vector<Rect> rectangles;
		contoursImage = Mat::zeros(blocksImage.size(), CV_8UC3);
		for (int i = 0; i < contours.size(); ++i) {
			drawContours(contoursImage, contours, i, Scalar(255, 0, 0), 2, 8, hierarchy, 0);
			const auto rect = boundingRect(contours[i]);
			if (rect.area() >= 50000) {
				rectangles.push_back(rect);
			}
			if (rect.area() >= 500) {
				var = 1;
			}
			else {
				var = 0;
			}
		}

		uniteRectangles(rectangles);

		for (const auto& rect : rectangles) {
			rectangle(cameraImage, rect, Scalar(0, 255, 0), 8);
		}

		Mat composedImage(cameraImage.size(), cameraImage.type());

		addImageToComposition(composedImage, blurredImage, true, 2);
		addImageToComposition(composedImage, blocksImage, true, 1);
		addImageToComposition(composedImage, contoursImage, false, 3);
		addImageToComposition(composedImage, cameraImage, false, 4);

		imshow("Scene", composedImage);

		int c = waitKey(30);

	}
	capture.release();
}

int main(int argc, char* argv[]) {
	namedWindow("Scene");

	processVideo();
	destroyAllWindows();

	return EXIT_SUCCESS;
}

Ma question est donc : Comment pourrais je allumer ou éteindre une LED en détectant un mouvement avec Open CV et Arduino ?

Sachant que je suis sous Windows 8.1, j'utilise Visual micro (Arduino 1.0) pour une carte Arduino Uno.

PS : Je précise également que les 2 programmes fonctionnent correctement séparément (quand je mets une autre variable dans le fichier .ino qui est interne à celle-ci), c'est donc un problème de lien entre le main.cpp et le Arduino_command.ino avec le header.

Merci d'avance pour vos réponses.

Bonjour,

J'imagine que le programme qui fait tourner openCV est exécuté par ton ordi et que le programme Arduino est exécuté par l'Arduino. Tu ne peux donc pas les faire communiquer comme tu le souhaites: chacun est compilé par son propre compilateur et est exécuté par des processeurs différents.

Tu dois faire communiquer les deux machines (le PC et l'Arduino) via la liaison série, exactement comme ce qui est fait dans le lien que tu donnes.

D'accord, je comprends bien ce que tu me dis, mais alors je dois tout d'abord inclure <Windows.h> ?

Et faut-il que j'ajoute ce code (qui se trouve sur le site) ?

	HANDLE hSerial = CreateFile(L"COM3", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

	if (hSerial !=INVALID_HANDLE_VALUE)
    {
        printf("Port opened! \n");

        DCB dcbSerialParams;
        GetCommState(hSerial,&dcbSerialParams);

        dcbSerialParams.BaudRate = CBR_9600;
        dcbSerialParams.ByteSize = 8;
        dcbSerialParams.Parity = NOPARITY;
        dcbSerialParams.StopBits = ONESTOPBIT;

        SetCommState(hSerial, &dcbSerialParams);
	}
	else
	{
		if (GetLastError() == ERROR_FILE_NOT_FOUND)
		{
			printf("Serial port doesn't exist! \n");
		}

		printf("Error while setting up serial port! \n");
	}

Et ensuite je ne vois pas comment faire, car dans l'exemple du site, il n'y a pas de fichier header intermédiaire, il utilise des librairies. Pourrais tu me donner des exemples ?

Je ne sais pas, je te donne juste des pistes pour continuer.

D'accord, je comprends bien ce que tu me dis, mais alors je dois tout d'abord inclure <Windows.h> ?

// Need to include this for serial port communication
#include <Windows.h>

après, rien n'empêche de remplacer servo par une led en modifiant un peu le script arduino

Bonsoir, et merci pour vos réponses.

J'ai réfléchi à ce que vous m'avez dit et j'ai regardé attentivement le programme du site.
Puis j'ai eu un "flash", je me suis aperçu que mon raisonnement était mauvais depuis le début :
En fait, il ne faut pas de fichier header intermédiaire. C'est le "main.cpp" (dans mon cas) qui doit envoyer directement la valeur de la variable à la carte Arduino.

De là, j'ai cherché ce qu'il fallait rajouté pour envoyé cette valeur, et je me suis fortement inspiré du site en y ajoutant ce qui m'était utile, voici le code finale (et il marche) :

main.cpp

#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>

#include <Windows.h>				//////////////

using namespace cv;
using namespace std;

void addImageToComposition(Mat& composedImage, Mat& image, const bool isGrayscale, const int quarter) {
	const auto width = composedImage.size().width;
	const auto height = composedImage.size().height;
	const auto halfWidth = width / 2;
	const auto halfHeight = height / 2;

	resize(image, image, Size(halfWidth, halfHeight));
	if (isGrayscale) {
		cvtColor(image, image, COLOR_GRAY2RGB);
	}

	Range rowsRange, colsRange;
	if (quarter == 1) {
		rowsRange = Range(0, halfHeight);
		colsRange = Range(halfWidth, width);
	}
	else if (quarter == 2) {
		rowsRange = Range(0, halfHeight);
		colsRange = Range(0, halfWidth);
	}
	else if (quarter == 3) {
		rowsRange = Range(halfHeight, height);
		colsRange = Range(0, halfWidth);
	}
	else if (quarter == 4) {
		rowsRange = Range(halfHeight, height);
		colsRange = Range(halfWidth, width);
	}

	image.copyTo(Mat(composedImage, rowsRange, colsRange));
}

void uniteRectangles(vector<Rect>& rectangles) {
	vector<char> useRectangles(rectangles.size(), 1);
	while (true) {
		auto wasChange = false;
		for (int i = 0; i < rectangles.size(); ++i) {
			if (!useRectangles[i]) {
				continue;
			}
			for (int j = i + 1; j < rectangles.size(); ++j) {
				if (useRectangles[j] && ((rectangles[i] & rectangles[j]).area() > 0)) {
					rectangles[i] |= rectangles[j];
					useRectangles[j] = false;
					wasChange = true;
				}
			}
		}
		if (!wasChange) {
			break;
		}
	}
	vector<Rect> resultRectangles;
	for (int i = 0; i < rectangles.size(); ++i) {
		if (useRectangles[i]) {
			resultRectangles.push_back(rectangles[i]);
		}
	}
	rectangles = resultRectangles;
}

char var[] = "0";			//////////////

void processVideo() {
	HANDLE hSerial = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

	if (hSerial != INVALID_HANDLE_VALUE)
	{
		printf("Port opened! \n");

		DCB dcbSerialParams;
		GetCommState(hSerial, &dcbSerialParams);

		dcbSerialParams.BaudRate = CBR_115200;
		dcbSerialParams.ByteSize = 8;
		dcbSerialParams.Parity = NOPARITY;
		dcbSerialParams.StopBits = ONESTOPBIT;

		SetCommState(hSerial, &dcbSerialParams);
	}
	else
	{
		if (GetLastError() == ERROR_FILE_NOT_FOUND)
		{
			printf("Serial port doesn't exist! \n");
		}

		printf("Error while setting up serial port! \n");			
	}
	DWORD btsIO;		//////////////

	VideoCapture capture(0);
	Mat cameraImage, blurredImage, blocksImage, contoursImage;
	Ptr<BackgroundSubtractorKNN> backgroundSubstructor = createBackgroundSubtractorKNN(10, 400, true);
	Mat dilateElement = getStructuringElement(MORPH_RECT, Size(32, 32));
	while (true) {
		capture.read(cameraImage);

		cvtColor(cameraImage, blurredImage, CV_BGR2GRAY);
		GaussianBlur(blurredImage, blurredImage, Size(21, 21), 0, 0);

		backgroundSubstructor->apply(blurredImage, blurredImage);
		threshold(blurredImage, blocksImage, backgroundSubstructor->getShadowValue() + 1, 255, THRESH_BINARY);
		dilate(blocksImage, blocksImage, dilateElement, Point(-1, -1), 2);

		contoursImage = blocksImage.clone();

		vector<vector<Point>> contours;
		vector<Vec4i> hierarchy;
		findContours(contoursImage, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

		vector<Rect> rectangles;
		contoursImage = Mat::zeros(blocksImage.size(), CV_8UC3);
		for (int i = 0; i < contours.size(); ++i) {
			drawContours(contoursImage, contours, i, Scalar(255, 0, 0), 2, 8, hierarchy, 0);
			const auto rect = boundingRect(contours[i]);
			if (rect.area() >= 50000) {
				rectangles.push_back(rect);
			}
			if (rect.area() >= 500) {
				var[0] = '1';							//////////////
				WriteFile(hSerial, var, strlen(var), &btsIO, NULL);	//////////////
			}
			else {
				var[0] = '0';							//////////////
				WriteFile(hSerial, var, strlen(var), &btsIO, NULL);	//////////////
			}
		}

		uniteRectangles(rectangles);

		for (const auto& rect : rectangles) {
			rectangle(cameraImage, rect, Scalar(0, 255, 0), 8);
		}

		Mat composedImage(cameraImage.size(), cameraImage.type());

		addImageToComposition(composedImage, blurredImage, true, 2);
		addImageToComposition(composedImage, blocksImage, true, 1);
		addImageToComposition(composedImage, contoursImage, false, 3);
		addImageToComposition(composedImage, cameraImage, false, 4);

		imshow("Scene", composedImage);

		int c = waitKey(30);

	}
	CloseHandle(hSerial);         /////////
	capture.release();
}

int main(int argc, char* argv[]) {
	namedWindow("Scene");

	processVideo();
	destroyAllWindows();

	return EXIT_SUCCESS;
}

(J'ai mis des slash pour tout ce que j'ai rajouté pour communiquer avec la carte Arduino + le code que j'ai mis dans la dernière réponse.)

Vous pourrez remarquer que j'inclue le "Windows.h" qui permet d'ouvrir le port en question (ici COM3) et d'y envoyer la valeur de "var" qui est d'ailleurs un caractère (je ne sais pas trop pourquoi).

Ensuite, j'ai fait la même chose pour le programme Arduino :

Arduino_command.ino

int var = 0;						//Initialisation

void setup()
{
	Serial.begin(115200);			 // ouvre le port série et fixe le debit de communication à 9600 bauds
	pinMode(11, OUTPUT);
}

void loop()
{
	if (Serial.available() > 0) {		// si données disponibles sur le port série
		var = Serial.read();		// lit l'octet entrant
		if (var == '1') {		//compare la valeur entrante
			digitalWrite(11, HIGH);
			delay(500);		/// Allume la LED pendant 500ms
		}				///////////SINON ON NE VOIT PAS LA LED S'ALLUMER (trés faiblement)
	}
	else {
		digitalWrite(11, LOW);
	}
}

Et maintenant ça marche !
Merci beaucoup !

Petite précision : il faut compiler le programme Arduino et ensuite arrêter le débogage pour que cela fonctionne, sinon pour une raison qui m'est obscure, le programme "main.cpp" ne reconnait pas le COM3 et ne peut pas envoyer les données.

reconnait pas le COM3

il le reconnait, mais le COM est déjà occupé par la console :wink:

Ah oui d'accord, merci