Concept de contour
Un contour est une courbe constituée d'une séquence de points connectés qui représente la forme globale d'un objet. Bien que les contours et les bords soient similaires, leur distinction fondamentale réside dans leur continuité : les contours forment des trajectoires fermées et continues, tandis que les bords détectés peuvent être fragmentés.
Les contours sont principalement utilisés pour l'analyse morphologique d'objets, permettant par exemple de calculer leur périmètre ou leur aire. La détection s'effectue généralement sur des images binaires, obtenues par seuillage ou détection de bords. Il est crucial que l'objet d'intérêt soit représenté en blanc sur un fond noir.
Détecsion basique avec Canny
Voici un exemple de détection de contours utilisant l'algorithme Canny pour l'extraction préalable des bords.
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;
Mat imageGris;
int seuilCanny = 100;
RNG generateurAleatoire(9999);
void traiterChangementSeuil(int, void*) {
Mat resultatBords;
Canny(imageGris, resultatBords, seuilCanny, seuilCanny * 1.5);
vector<vector<Point>> ensemblesContours;
vector<Vec4i> relationsHierarchiques;
findContours(resultatBords, ensemblesContours, relationsHierarchiques, RETR_TREE, CHAIN_APPROX_NONE);
Mat canevasDessin = Mat::zeros(resultatBords.size(), CV_8UC3);
for (size_t idx = 0; idx < ensemblesContours.size(); ++idx) {
Scalar couleur = Scalar(generateurAleatoire.uniform(30, 255),
generateurAleatoire.uniform(30, 255),
generateurAleatoire.uniform(30, 255));
drawContours(canevasDessin, ensemblesContours, idx, couleur, 2, LINE_8, relationsHierarchiques, 1);
}
imshow("Contours détectés", canevasDessin);
}
int main(int argc, char** argv) {
Mat imageSource = imread(argv[1]);
if (imageSource.empty()) {
cerr << "Échec du chargement de l'image" << endl;
return -1;
}
cvtColor(imageSource, imageGris, COLOR_BGR2GRAY);
GaussianBlur(imageGris, imageGris, Size(5,5), 1.5);
namedWindow("Image originale");
imshow("Image originale", imageSource);
createTrackbar("Seuillage Canny:", "Image originale", &seuilCanny, 255, traiterChangementSeuil);
traiterChangementSeuil(0, nullptr);
waitKey(0);
return 0;
}
Application : détection de quadrilatères
L'algorithme suivant identifie spécifiquement les carrés et rectangles dans une image par analyse géométrique des contours.
#include <opencv2/opencv.hpp>
#include <cmath>
#include <vector>
using namespace cv;
using namespace std;
double calculerAngleCosinus(Point a, Point b, Point sommet) {
Point vecteurA = a - sommet;
Point vecteurB = b - sommet;
double normeA = norm(vecteurA);
double normeB = norm(vecteurB);
if (normeA * normeB == 0) return 1.0;
return vecteurA.dot(vecteurB) / (normeA * normeB);
}
vector<vector<Point>> extraireQuadrilateres(const Mat& imageEntree) {
vector<vector<Point>> resultats;
Mat imageReduite, imageRehaussee;
pyrDown(imageEntree, imageReduite, Size(imageEntree.cols/2, imageEntree.rows/2));
pyrUp(imageReduite, imageRehaussee, imageEntree.size());
for (int canal = 0; canal < 3; ++canal) {
Mat planCouleur, imageBinaire;
extractChannel(imageRehaussee, planCouleur, canal);
for (int niveauSeuil = 1; niveauSeuil <= 10; ++niveauSeuil) {
threshold(planCouleur, imageBinaire, niveauSeuil * 25.5, 255, THRESH_BINARY);
if (niveauSeuil == 1) {
dilate(imageBinaire, imageBinaire, Mat(), Point(-1,-1), 2);
}
vector<vector<Point>> contoursDetectes;
findContours(imageBinaire, contoursDetectes, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (auto& contour : contoursDetectes) {
vector<Point> formeApprochee;
approxPolyDP(contour, formeApprochee, arcLength(contour, true) * 0.03, true);
if (formeApprochee.size() == 4 && fabs(contourArea(formeApprochee)) > 500 && isContourConvex(formeApprochee)) {
double maximumCosinus = 0;
for (int i = 0; i < 4; ++i) {
double cosAngle = fabs(calculerAngleCosinus(
formeApprochee[(i+2)%4],
formeApprochee[i],
formeApprochee[(i+1)%4]));
maximumCosinus = max(maximumCosinus, cosAngle);
}
if (maximumCosinus < 0.25) {
resultats.push_back(formeApprochee);
}
}
}
}
}
return resultats;
}
void afficherResultats(Mat& image, const vector<vector<Point>>& quadrilateres) {
for (const auto& quad : quadrilateres) {
const Point* points = quad.data();
int nombrePoints = quad.size();
polylines(image, &points, &nombrePoints, 1, true, Scalar(0, 200, 0), 2, LINE_AA);
}
imshow("Détection de quadrilatères", image);
}
int main() {
vector<String> fichiers = {"geometrie1.jpg", "geometrie2.jpg", "scene_bureau.png"};
for (const auto& nom : fichiers) {
Mat image = imread(nom);
if (image.empty()) continue;
auto formesDetectees = extraireQuadrilateres(image);
Mat copieImage = image.clone();
afficherResultats(copieImage, formesDetectees);
if (waitKey(0) == 27) break;
}
return 0;
}
Cette implémentation utilise une approche multi-niveaux de seuillage et vérifie la géométrie des formes détectées pour identifier spécifiquement les quadrilatères avec des angles proches de 90 degrés.