Notas de Opencv-C++ (18): contornos y cascos convexos

1. Esquema

El descubrimiento de contornos es un método para encontrar contornos de objetos basado en la extracción de bordes de imágenes. Por lo tanto, la selección del umbral para la extracción de bordes afectará el resultado final del descubrimiento del contorno.

Pasos para encontrar el contorno:

  • Convertir imagen de entrada a imagen en escala de grises cvtColor
  • Utilice Canny para la extracción de bordes o la operación de umbral para obtener una imagen binaria
  • Encuentra contornos usando findContours
  • Dibujar contornos usando drawContours

buscarContornosbuscar contornos

Encuentra contornos en imágenes binarias usando

cv::findContours(
InputOutputArray binImg,     输入图像,非0的像素被看成1,0的像素值保持不变,8-bit
OutputArrayOfArrays contours,  全部发现的轮廓对象
OutputArray, hierachy      图该的拓扑结构 std::vector<cv::Vec4i>,可选,该轮廓发现算法正是基于图像拓扑结构实现。它的元素与轮廓的数量一样多。对于每个第 i 个轮廓轮廓[i],元素hierarchy[i][0]、hierarchy[i][1]
int mode,            轮廓返回的模式
int method,            发现方法
Point offset=Point()       轮廓像素的位移,默认(0, 0)没有位移
)

drawContours dibuja contornos

Después de descubrir el contorno en la imagen binaria, cv::findContours dibuja y muestra los datos del contorno descubierto.

drawContours(
InputOutputArray binImg,      输出图像
OutputArrayOfArrays contours,    全部发现的轮廓对象
Int contourIdx            轮廓索引号
const Scalar & color,        绘制颜色
int thickness,/           绘制线宽
int lineType ,             线的类型LINE_8
InputArray hierarchy,        拓扑结构图
int maxlevel,           最大层数, 0只绘制当前的,1表示绘制绘制当前及其内嵌的轮廓
Point offset=Point()        轮廓位移,可选

código

//轮廓发现:通过cv::fingContoursAPI查找轮廓,通过cv::drawContours绘制轮廓
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int threshold_value = 100;
int threshold_max = 255;
RNG rng;
const char* output_win = "Demo_Contour";
void Demo_Contours(int, void*);
Mat src,dst;
int main(int argc, char** argv) {
    
    

	src = imread("D:/photos/45.png");
	if (src.empty()) {
    
    
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);
	imshow("input image", src);
	cvtColor(src, src, CV_BGR2GRAY);//灰度化图像,为Canny边缘检测做准备

	const char* trackbar_title = "threshold_value";
	createTrackbar(trackbar_title, output_win, &threshold_value, threshold_max, Demo_Contours);//动态调整Canny边缘检测的阈值
	Demo_Contours(0, 0);//使程序刚开始就有结果,与createTrackbar无关


	waitKey(0);
	return 0;
}

void Demo_Contours(int, void*) {
    
    
	Mat canny_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false);//Canny边缘检测,3代表算子尺寸
	imshow("canny image", canny_output);
	findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	//contours储存轮廓的点集,轮廓提取方式为RETR_TREE,轮廓表达为:CHAIN_APPROX_SIMPLE
	dst = Mat::zeros(src.size(), CV_8UC3);
	RNG rng(12345);
	for (size_t i = 0; i < contours.size(); i++) {
    
    //逐条绘制轮廓
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(dst, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
	}
	imshow(output_win, dst);

}

Insertar descripción de la imagen aquí

2. Generalización de geometría y características - Casco Convexo (Casco Convexo)

Concepto de casco convexo

¿Qué es un casco convexo (Casco Convexo)?La línea que conecta dos puntos cualesquiera en el borde o el interior de un polígono se incluye en el límite o interior del polígono.
**Definición formal:** El polígono convexo más pequeño que contiene todos los puntos en el conjunto de puntos S se llama casco convexo.

Introducción al algoritmo de escaneo de casco convexo: algoritmo de escaneo de Graham

  • Primero seleccione el punto más bajo en la dirección Y como punto inicial p0.
  • Inicie el escaneo de coordenadas polares desde p0 y agregue p1...pn en secuencia (el orden de clasificación se basa en el ángulo de las coordenadas polares, en sentido antihorario).
  • Para cada punto pi, si agregar pi al casco convexo conduce a un giro a la izquierda (en sentido antihorario), agregue el punto al casco convexo; de lo contrario, si conduce a un giro a la derecha (en sentido horario), elimine el punto del casco
    convexo .medio.
    Insertar descripción de la imagen aquí

Introducción a la API relacionada

convexHull(
InputArray points,// 输入候选点,来自findContours
OutputArray hull,// 凸包
bool clockwise,// default true, 顺时针方向
bool returnPoints)// true 表示返回点个数,如果第二个参数是			vector<Point>则自动忽略
}

Pasos de implementación de la aproximación del casco convexo:

  • Primero convierta la imagen de RGB a escala de grises.

  • Luego conviértalo a una imagen binaria.

  • Los puntos candidatos se obtienen encontrando contornos.

  • Llamadas API de casco convexo.

  • Visualización del dibujo.

Ejemplo de programa

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int threshold_value = 100;
int threshold_max = 255;
RNG rng(12345);
const char* output_win = "Demo_convex hull";
void threshold_callback(int, void*);
Mat src, dst,dst2,gray_src;
int main(int argc, char** argv) {
    
    

	src = imread("D:/photos/45.png");
	if (src.empty()) {
    
    
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);
	const char* trackbar_label = "threshold:";
	imshow("input image", src);
	cvtColor(src, gray_src, CV_BGR2GRAY);
	blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);//均值模糊进行降噪处理
	imshow("src_gray", gray_src);
	createTrackbar(trackbar_label, output_win, &threshold_value, threshold_max, threshold_callback);
	threshold_callback(0, 0);
	waitKey(0);
	return 0;
}
void threshold_callback(int, void*) {
    
    
	Mat bin_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	threshold(gray_src, bin_output, threshold_value, threshold_max, THRESH_BINARY);
	findContours(bin_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	vector<vector<Point>> convexs(contours.size());
	dst = Mat::zeros(src.size(), CV_8UC3);
	dst2 = Mat::zeros(src.size(), CV_8UC3);
	for (size_t i = 0; i < contours.size(); i++) {
    
    
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		convexHull(contours[i], convexs[i], false, true);
		//drawContours(dst, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
	}
	vector<Vec4i> empty(0);
		for (size_t k = 0; k < contours.size(); k++) {
    
    
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(dst2, contours, k, color, 2, LINE_8, hierachy,1, Point(0, 0));
		drawContours(dst, convexs, k, color, 2, LINE_8, empty, 0, Point(0, 0));//注意此时hieracgy选项填Mat()
	}
	imshow(output_win, dst);
	imshow("contours_Demo", dst2);
	return;		
}

Insertar descripción de la imagen aquí

Colección de contornos y resumen de características: dibujar cuadros rectangulares y círculos alrededor de los contornos

Introducción a las teorías relevantes.

Insertar descripción de la imagen aquí

Dibujar un rectángulo alrededor del contorno - API

approxPolyDP (curva InputArray, OutputArray approxCurve, doble épsilon, bool cerrado)
se implementa en base al algoritmo RDP y tiene como objetivo reducir el número de puntos de contorno del polígono.
Insertar descripción de la imagen aquí

cv::minEnclosingCircle(InputArray puntos, //obtiene el área mínima del círculo
Point2f& center, //
float& radio)//el radio del círculo
cv::fitEllipse(InputArray puntos) obtiene la elipse mínima

Pasos de dibujo

Primero convierta la imagen en una imagen binaria.
Descubra contornos, encuentre contornos de imágenes.
Encuentre el rectángulo y el círculo que contienen más pequeños en el punto del contorno a través de API relacionadas y gire el rectángulo y la elipse.
Dibújalos.

Ejemplo de programa

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;
Mat src, gray_src, drawImg;
int threshold_v = 170;
int threshold_max = 255;
const char* output_win = "rectangle-demo";
RNG rng(12345);
void Contours_Callback(int, void*);
int main(int argc, char** argv) {
    
    
	src = imread("D:/photos/45.png");
	if (!src.data) {
    
    
		printf("could not load image...\n");
		return -1;
	}
	cvtColor(src, gray_src, CV_BGR2GRAY);
	blur(gray_src, gray_src, Size(3, 3), Point(-1, -1));
	
	const char* source_win = "input image";
	namedWindow(source_win, CV_WINDOW_AUTOSIZE);
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);
	imshow(source_win, src);

	createTrackbar("Threshold Value:", output_win, &threshold_v, threshold_max, Contours_Callback);
	Contours_Callback(0, 0);

	waitKey(0);
	return 0;
}

void Contours_Callback(int, void*) {
    
    
	Mat binary_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	threshold(gray_src, binary_output, threshold_v, threshold_max, THRESH_BINARY);
	//imshow("binary image", binary_output);
	findContours(binary_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));

	vector<vector<Point>> contours_ploy(contours.size());
	vector<Rect> ploy_rects(contours.size());
	vector<Point2f> ccs(contours.size());
	vector<float> radius(contours.size());

	vector<RotatedRect> minRects(contours.size());
	vector<RotatedRect> myellipse(contours.size());

	for (size_t i = 0; i < contours.size(); i++) {
    
    
		approxPolyDP(Mat(contours[i]), contours_ploy[i], 3, true);
		ploy_rects[i] = boundingRect(contours_ploy[i]);
		minEnclosingCircle(contours_ploy[i], ccs[i], radius[i]);
		if (contours_ploy[i].size() > 5) {
    
    
			myellipse[i] = fitEllipse(contours_ploy[i]);
			minRects[i] = minAreaRect(contours_ploy[i]);
		}
	}

	// draw it
	drawImg = Mat::zeros(src.size(), src.type());
	Point2f pts[4];
	for (size_t t = 0; t < contours.size(); t++) {
    
    
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		//rectangle(drawImg, ploy_rects[t], color, 2, 8);
		//circle(drawImg, ccs[t], radius[t], color, 2, 8);
		if (contours_ploy[t].size() > 5) {
    
    
			ellipse(drawImg, myellipse[t], color, 1, 8);
			minRects[t].points(pts);
			for (int r = 0; r < 4; r++) {
    
    
				line(drawImg, pts[r], pts[(r + 1) % 4], color, 1, 8);
			}
		}
	}

	imshow(output_win, drawImg);
	return;
}

resultado de ejecución:
Insertar descripción de la imagen aquí

4. Momentos de imagen

1. Teorías relacionadas

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

2. Introducción a la API

1. Calcular momentos cv::momentos

moments(
InputArray  array,//输入数据
bool   binaryImage=false // 是否为二值图像
)

Introducción y uso de API: cv::moments calcula y genera datosInsertar descripción de la imagen aquí

Calcular el área del contorno cv::contourArea

contourArea(
InputArray  contour,//输入轮廓数据
bool   oriented// 默认false、返回绝对值)
}

.Calcular la longitud del contorno cv::arcLength

arcLength(
InputArray  curve,//输入曲线数据
bool   closed// 是否是封闭曲线)
}

Pasos de implementación:

Extrae los bordes de la imagen.
Descubre la silueta.
Calcula los momentos de cada objeto de contorno.
Calcula el centro, la longitud del arco y el área de cada objeto.

rutina

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;

Mat src, gray_src;
int threshold_value = 80;
int threshold_max = 255;
const char* output_win = "image moents demo";
RNG rng(12345);
void Demo_Moments(int, void*);
int main(int argc, char** argv) {
    
    
	src = imread("D:/photos/45.png");
	if (!src.data) {
    
    
		printf("could not load image...\n");
		return -1;
	}
	cvtColor(src, gray_src, CV_BGR2GRAY);
	GaussianBlur(gray_src, gray_src, Size(3, 3), 0, 0);

	char input_win[] = "input image";
	namedWindow(input_win, CV_WINDOW_AUTOSIZE);
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);
	imshow(input_win, src);

	createTrackbar("Threshold Value : ", output_win, &threshold_value, threshold_max, Demo_Moments);
	Demo_Moments(0, 0);

	waitKey(0);
	return 0;
}

void Demo_Moments(int, void*) {
    
    
	Mat canny_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;

	Canny(gray_src, canny_output, threshold_value, threshold_value * 2, 3, false);
	findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	vector<Moments> contours_moments(contours.size());
	vector<Point2f> ccs(contours.size());
	for (size_t i = 0; i < contours.size(); i++) {
    
    
		contours_moments[i] = moments(contours[i]);
		ccs[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
	}
	Mat drawImg;// = Mat::zeros(src.size(), CV_8UC3);
	src.copyTo(drawImg);
	for (size_t i = 0; i < contours.size(); i++) {
    
    
		if (contours[i].size() < 100) {
    
    
			continue;
		}
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		printf("center point x : %.2f y : %.2f\n", ccs[i].x, ccs[i].y);
		printf("contours %d area : %.2f   arc length : %.2f\n", i, contourArea(contours[i]), arcLength(contours[i], true));
		drawContours(drawImg, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
		circle(drawImg, ccs[i], 2, color,2, 8);
	}

	imshow(output_win, drawImg);
	return;
}

Insertar descripción de la imagen aquí

5. Prueba de polígono

1.Teorías relacionadas

Prueba de polígono de puntos: prueba si un punto está dentro, en un borde o fuera de un polígono determinado.
Insertar descripción de la imagen aquí

2. Introducción a las API relacionadas

cv::pointPolygonTest
pointPolygonTest(
InputArray  contour,// 输入的轮廓
Point2f  pt, // 测试点
bool  measureDist // 是否返回距离值,如果是false,1表示在内面,0表示在边界上,-1表示在外部,true返回实际距离
)
返回数据是double类型

Ejemplo de programa

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;
int main(int argc, char** argv) {
    
    
	const int r = 100;
	Mat src = Mat::zeros(r * 4, r * 4, CV_8UC1);

	vector<Point2f> vert(6);
	vert[0] = Point(3 * r / 2, static_cast<int>(1.34*r));   
	vert[1] = Point(1 * r, 2 * r);
	vert[2] = Point(3 * r / 2, static_cast<int>(2.866*r));   
	vert[3] = Point(5 * r / 2, static_cast<int>(2.866*r));
	vert[4] = Point(3 * r, 2 * r);   
	vert[5] = Point(5 * r / 2, static_cast<int>(1.34*r));

	for (int i = 0; i < 6; i++) {
    
    
		line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3, 8, 0);
	}
	
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	Mat csrc;
	src.copyTo(csrc);
	findContours(csrc, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	Mat raw_dist = Mat::zeros(csrc.size(), CV_32FC1);
	for (int row = 0; row < raw_dist.rows; row++) {
    
    
		for (int col = 0; col < raw_dist.cols; col++) {
    
    
			double dist = pointPolygonTest(contours[0], Point2f(static_cast<float>(col), static_cast<float>(row)), true);
			raw_dist.at<float>(row, col) = static_cast<float>(dist);
		}
	}

	double minValue, maxValue;
	minMaxLoc(raw_dist, &minValue, &maxValue, 0, 0, Mat());
	Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
	for (int row = 0; row < drawImg.rows; row++) {
    
    
		for (int col = 0; col < drawImg.cols; col++) {
    
    
			float dist = raw_dist.at<float>(row, col);
			if (dist > 0) {
    
    
				drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(1.0 - (dist / maxValue)) * 255);
			}
			else if (dist < 0) {
    
    
				drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(1.0 - (dist / minValue)) * 255);
			} else {
    
    
				drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(255 - dist));
				drawImg.at<Vec3b>(row, col)[1] = (uchar)(abs(255 - dist));
				drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(255 - dist));
			}
		}
	}

	const char* output_win = "point polygon test demo";
	char input_win[] = "input image";
	namedWindow(input_win, CV_WINDOW_AUTOSIZE);
	namedWindow(output_win, CV_WINDOW_AUTOSIZE);

	imshow(input_win, src);
	imshow(output_win, drawImg);

	waitKey(0);
	return 0;
}

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/jiyanghao19/article/details/132453249
Recomendado
Clasificación