Clasificación de género y detección de rostros

Seguimos probando lo que podemos hacer con el módulo DNN de OpenCV versión 4.0, esta vez vamos a crear una simple aplicación la cual captura video de la webcam, detecta un rostro e intenta predecir si el mismo pertenece a una cara masculina o femenina.

enter image description here

Lo primero, iniciar la captura proveniente de la webcam, esto ya lo hemos hecho antes por lo que le dejo el enlace al respectivo tutorial: Acceso a webcam con OpenCV.

Ahora necesitamos saber si existe un rostro presente en el video capturado, para ello también usaremos un módelo preentrenado para este proposito, anteriormente lo habiamos hecho utilizando clasificadores en cascada pero este método usando redes neuronales es mas robusto.

Detección de rostros

Al siguiente método le pasamos una instancia de la red neuronal a utilizar, la imagen que deseamos procesar, un vector en donde se guardarán las regiones rectángulares de los rostros encontrados y por último un valor de tolerancia.

void getFaceRect(dnn::Net net, cv::Mat& frame, vector<cv::Rect>& rc, double conf_threshold)
{
	int frameHeight = frame.rows;
	int frameWidth = frame.cols;
	double inScaleFactor = 1.0;
	Size size = Size(300, 300);
	Scalar meanVal = Scalar(104, 117, 123);

	cv::Mat inputBlob;
	cv::dnn::blobFromImage(frame, inputBlob, inScaleFactor, size, meanVal, true, false);

	net.setInput(inputBlob, "data");

	cv::Mat detection = net.forward("detection_out");
	cv::Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());

	for (int i = 0; i < detectionMat.rows; i++)
	{
		float confidence = detectionMat.at<float>(i, 2);

		if (confidence > conf_threshold)
		{
			int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * frameWidth);
			int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * frameHeight);
			int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * frameWidth);
			int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * frameHeight);

			rc.push_back(cv::Rect(cv::Point(x1, y1), cv::Point(x2, y2)));		
		}
	}
}

Detalles importante del código:

Convertir la imagen a procesar en un formato que la red neuronal pueda manejar, de esto se encarga dnn::blobFromImage, luego este será nuestra entrada para la DNN, lo hacemos con setInput(...)

	cv::Mat inputBlob;
	cv::dnn::blobFromImage(frame, inputBlob, inScaleFactor, size, meanVal, true, false);
	net.setInput(inputBlob, "data");    

Correr la red y obtener la prediccion, para esto tenemos: net.forward()

	cv::Mat detection = net.forward("detection_out");
	cv::Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());

Guardar todos los rectángulos que corresponden a los restros detectados cuya probalidad de ser un rostro esté por encima de la tolerancia que se indico.

	for (int i = 0; i < detectionMat.rows; i++)
	{
		float confidence = detectionMat.at<float>(i, 2);

		if (confidence > conf_threshold)
		{
			int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * frameWidth);
			int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * frameHeight);
			int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * frameWidth);
			int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * frameHeight);

			rc.push_back(cv::Rect(cv::Point(x1, y1), cv::Point(x2, y2)));		
		}
	}

Ya tenemos el rostro ahora podemos intentar predecir si es masculino o femenino.

Clasificación de género

Para obtener el género usaremos un módelo entrenado con el framework Caffe, este nos devuelve la probablilidad de que la imagen de entrada pertenesca a la clase uno o dos, siendo estas mujer y hombre, este es el código:

void main() {

	auto net_file = "caffe-models/caffe/res10_300x300_ssd_iter_140000_fp16.caffemodel";
	auto net_desc = "caffe-models/caffe/res10_300x300_ssd_iter_140000_fp16.prototxt";
	
	auto net_file1 = "caffe-models/caffe/gender_net.caffemodel";
	auto net_desc1 = "caffe-models/caffe/gender_deploy.prototxt";

	auto net = dnn::readNetFromCaffe(net_desc, net_file);
	auto net_gender = dnn::readNetFromCaffe(net_desc1, net_file1);

	VideoCapture capture;
	capture.open(0);
//...
	vector<string> genderList = { "Hombre", "Mujer" };
	Scalar meanVal = Scalar(104, 117, 123);

	while (capture.isOpened()) {

		Mat frame;
		capture >> frame;

		if (!frame.empty()) {

			vector<cv::Rect> rcFace;
			getFaceRect(net, frame, rcFace, 0.6);
						
			for (auto roi_face : rcFace) {

				auto face = frame(roi_face);

				auto blob = dnn::blobFromImage(face, 1, Size(227, 227), meanVal, false);
				net_gender.setInput(blob);

				vector<float> genderPreds = net_gender.forward();

				int max_index_gender = std::distance(genderPreds.begin(), max_element(genderPreds.begin(), genderPreds.end()));
				string gender = genderList[max_index_gender];

				cv::rectangle(frame, roi_face, cv::Scalar(0, 255, 255), 1, LINE_AA);
				cv::putText(frame, gender, roi_face.br() - cv::Point(roi_face.width - 8, 8), FONT_HERSHEY_SIMPLEX, 0.60, cv::Scalar(0, 0, 255), 1, LINE_AA);
			}
		}
// ...
		cv::imshow("face", frame);
		if (waitKey(1) == 27) break;
	}

	capture.release();
}

Detalles importantes:

Cargar los modelos para la deteccion de rostros y la clasificación, el primero lo puedes encontrar entre los ejemplos porporcionados en el repositorio de OpenCV y el segundo en el siguiente enlace: gender-model

	auto net_file = "caffe-models/caffe/res10_300x300_ssd_iter_140000_fp16.caffemodel";
	auto net_desc = "caffe-models/caffe/res10_300x300_ssd_iter_140000_fp16.prototxt";
	
	auto net_file1 = "caffe-models/caffe/gender_net.caffemodel";
	auto net_desc1 = "caffe-models/caffe/gender_deploy.prototxt";

	auto net = dnn::readNetFromCaffe(net_desc, net_file);
	auto net_gender = dnn::readNetFromCaffe(net_desc1, net_file1);

Obtener la región que contiene el rostro con getFaceRect(...) función que explicamos previamente, luego corremos la red usando como entrada la región encontrada, net_gender.forward() esto nos devolverá un conjunto de datos que representa la probabilidad de que el rostro pertenesca a la clase uno o dos, nos quedaremos con el indice de la clase que tenga un valor más alto de probabilidad.

El restro del código es solo para mostrar la salida, marcar la cara e indicar con un texto la clase a la que pertenece.

vector<cv::Rect> rcFace;
getFaceRect(net, frame, rcFace, 0.6);
			
for (auto roi_face : rcFace) {

	auto face = frame(roi_face);

	auto blob = dnn::blobFromImage(face, 1, Size(227, 227), meanVal, false);
	net_gender.setInput(blob);

	vector<float> genderPreds = net_gender.forward();

	int max_index_gender = std::distance(genderPreds.begin(), max_element(genderPreds.begin(), genderPreds.end()));
	string gender = genderList[max_index_gender];

	cv::rectangle(frame, roi_face, cv::Scalar(0, 255, 255), 1, LINE_AA);
	cv::putText(frame, gender, roi_face.br() - cv::Point(roi_face.width - 8, 8), FONT_HERSHEY_SIMPLEX, 0.60, cv::Scalar(0, 0, 255), 1, LINE_AA);
}

El código completo y los modelos los puedes encontrar en el repositorio indicado abajo, recordar que este ejemplo está preparado para trabajar con la webcam y además se incluye el script CMake para generar el proyecto fácilmente.

Repositorio de código

Comentarios

  1. Excelente info.
    Una precisión el termino "Genero" no es aplicable a humanos. Eso solo cabe, en la aberrante ideología de género. En Humanos hay sexos: Femenino y Masculino.
    Segun la lengua castellana, el termino "genero" solo se aplica a cosas, no a humanos.

    Saludos y gracias.

    ResponderEliminar
  2. Clasificación de género y detección de rostros son áreas fascinantes en el campo de la inteligencia artificial y visión por computadora. La capacidad de reconocer y clasificar géneros, así como identificar rostros, abre un amplio abanico de aplicaciones, desde la seguridad hasta la personalización de experiencias. La inclusión de servicios de ciberseguridad en estas aplicaciones demuestra un enfoque proactivo y responsable. La protección de la privacidad y la integridad de los datos es fundamental en cualquier sistema que maneje información sensible, como imágenes faciales. La incorporación de medidas de ciberseguridad fortalece la confianza del usuario y asegura que la tecnología se utilice de manera ética y segura. En conjunto, estas tecnologías ofrecen un gran potencial para mejorar la eficiencia y la seguridad en diversos sectores, desde la identificación de personas en situaciones críticas hasta la personalización de servicios basada en el reconocimiento de género. La combinación de estas capacidades con servicios de servicios de ciberseguridad la utilidad y confiabilidad de estas aplicaciones, generando un impacto positivo en la sociedad.

    ResponderEliminar

Publicar un comentario

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Controles y Contenedores JavaFX 8 - I

Histogramas OpenCV Python