Universidad Nacional de San Juan Facultad de Ingeniería Detección y Medición de Arandelas mediante una cámara Trabajo Integrador • Complementos de Informática • Temas Específicos de Control I (Visión) Más, Germán Emilio – 23860 1 Año 2015 Objetivo El objetivo de este trabajo integrador es detectar y medir arandelas de forma automática, utilizando una cámara web y un sistema de iluminación posterior o por contraste (backlight). Los datos obtenidos son recopilados para su posterior uso. Sistema de Iluminación La técnica de iluminación por contraste se utiliza situando el objeto entre la fuente de luz y la cámara. De esta forma se puede reconocer la silueta del objeto por contraste y realizar mediciones muy precisas, aunque no permite reconocer los detalles superficiales de las piezas a inspeccionar. La fuente de luz utilizada consiste en 9 LED blancos dispuestos en una matriz de 3x3, alimentados con 12V. Los LED proveen una iluminación direccional y tienen la ventaja de utilizar corriente continua, pues la corriente alterna genera variaciones imperceptibles de intensidad, que puede llegar a ser capturada por la cámara. A continuación se puede observar el arreglo construido. Imagen 1 – Sistema de Iluminación construido Imagen 2 – Matriz de LEDs 2 Software El programa carga una imagen desde un archivo o cámara y la procesa para detectar una arandela. La información de cada arandela es recopilada utilizando un vector. Esto permite manejar la información de forma ordenada. El software fue programado utilizando C++ y OpenCV 2.4.9 en el IDE Eclipse Luna. A continuación se explican y fundamentan partes del código, que puede verse completo en el Apéndice 1. Clases Se implementan dos clases: Arandela y Procesador, explicadas a continuación. Arandela La clase proviene del objeto real a medir. Las medidas importantes de una arandela son el diámetro interior, el diámetro exterior y la altura. La altura se asume constante, ya que no puede medirse con el sistema utilizado. Imagen 3 – Medidas útiles de una arandela Como parámetros privados de la clase encontramos aquellas medidas físicas que describen a una arandela: private: //Parametros float mDiametroInterior; float mDiametroExterior; float mAltura; float mArea; float mVolumen; time_t mTiempo; El área y volumen son medidas secundarias que derivan de los diámetros y la altura. Se calculan porque pueden servir para alguna aplicación más específica (como calcular el peso, por ejemplo). El tiempo hace referencia al tiempo en el cual la arandela fue detectada. Los métodos de esta clase incluyen distintos constructores, destructor, acceso a los parámetros y sobrecarga de operadores. Se agrega también una variable estática para contar la cantidad de arandelas detectadas. 3 public: //Variables estáticas static unsigned int s_CantidadArandelas; //Constructores Arandela(void); Arandela(float, float); //Diametros Interior y Exterior //Destructores ~Arandela(void); //Gets const float GetDiametroInterior(void) const; const float GetDiametroExterior(void) const; const float GetAltura(void) const; const float GetArea(void) const; const float GetVolumen(void) const; const double GetTiempo(void) const; //Sets void SetDiametroInterior(float); void SetDiametroExterior(float); void SetAltura(float); void SetTiempo(void); //Sobrecarga de Operadores Arandela& operator= (const Arandela&); friend std::ostream& operator<<(std::ostream&, const Arandela&); Procesador Esta clase es más abstracta que la anterior, ya que no proviene de un objeto físico. Tiene como objetivo realizar el procesamiento de imágenes y detección de arandelas de forma encapsulada. Posee dos parámetros matriciales sobre los que trabaja y tres parámetros numéricos que pueden ser modificados para la calibración del procesamiento. private: //Parametros Mat mImagen; Mat mMascara; int mValorThreshold; int mAreaMin; int mAreaMax; La imagen es enmascarada y procesada para detectar la arandela. Los valores numéricos intervienen en el procesamiento. Los métodos de esta clase incluyen constructores, destructor, acceso a los parámetros, muestra y procesamiento. public: //Constructores Procesador(void); Procesador(Mat, Mat); //Destructores ~Procesador(); //Get const Mat GetImagen(void) const; const Mat GetMascara(void) const; 4 const int GetValorThreshold(void) const; const int GetAreaMin(void) const; const int GetAreaMax(void) const; //Set void SetImagen(string); //Via archivo void SetImagen(int); //Via camara void SetMascara(string); //Via archivo void SetMascara(int); //Via camara void SetValorThreshold(int); void SetAreaMin(int); void SetAreaMax(int); //Otros void MuestraImagen(string); void MuestraMascara(string); const Arandela DetectarArandela(void) const; Se puede observar una sobrecarga en los métodos SetImagen y SetMascara. Cuando el argumento es una cadena de caracteres, la imagen se lee desde un archivo y cuando es un entero, es tomada desde la cámara (indicada por su número). El corazón de la clase Procesador es sin duda el método DetectarArandela, que se explicará a continuación. Detección La clase Procesador trabaja con la imagen y la máscara. La imagen, es la foto tomada desde la cámara y la máscara es una imagen binaria utilizada para determinar la región de interés. Analicemos primero la máscara. Para calcularla, se toma una foto del iluminador sin ninguna arandela y se procesa. void Procesador::SetMascara(int cam) { Mat img; VideoCapture Camara(cam); if(!Camara.isOpened()) throw(3); //ERROR_CAMARA_NO_ENCONTRADA Camara.read(img); Hasta aquí se toma la imagen, que será contenida en la matriz img. Ahora comienza su procesamiento. El primer paso es convertir la imagen a escala de grises, para eliminar la información que pueda existir de color. cvtColor(img, mMascara, CV_BGR2GRAY); El paso siguiente consiste en binarizar la imagen. Para ello se utiliza la función de OpenCV threshold. Los valores de intensidad menores a mValorThreshold son puestos en 0 y los mayores son llevados a 255. threshold(mMascara, mMascara, mValorThreshold, 255, THRESH_BINARY); En este caso particular, debido a las características constructivas del iluminador, se realiza 5 una doble erosión para reducir un poco el tamaño del círculo. erode(mMascara, mMascara, Mat(), Point(-1, -1), 1, 1, 1); erode(mMascara, mMascara, Mat(), Point(-1, -1), 1, 1, 1); } Una vez calculada la máscara, ya podemos realizar el procesamiento de la imagen para detectar una arandela. const Arandela Procesador::DetectarArandela() const { Mat imgMsk; float diametroExterior = 0; float diametroInterior = 0; Declaramos e inicializamos algunas variables auxiliares. //Enmascara (imagen con mascara) if(mImagen.empty() || mMascara.empty()) throw(2); //ERROR_MATRIZ_VACIA Para enmascarar correctamente, ni la imagen ni la máscara deben estar vacía. mImagen.copyTo(imgMsk, mMascara); Esta línea realiza el enmascaramiento. Copia cada elemento de mImagen hacia imgMsk, pero sólo aquellos donde la máscara mMascara tenga valor igual a 1. cvtColor(imgMsk,imgMsk,CV_BGR2GRAY); //Convierte imgMsk a grayscale threshold(imgMsk, imgMsk, mValorThreshold, 255, THRESH_BINARY); //Erosiona y dilata 2 veces para disminuir el ruido erode(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); erode(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); dilate(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); dilate(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); Luego el proceso es similar al visto anteriormente con una salvedad: Se realiza una doble dilatación luego de una doble erosión. Esto actúa como un filtro al ruido binario. //Contornos vector<vector<Point> > contours; //Vector de Contornos vector<Vec4i> hierarchy; //Vector de Jerarquía de Contornos findContours(imgMsk, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0,0)); Esta última línea encuentra los contornos de imgMsk que son guardados en contours. Los contornos son un conjunto de puntos continuos desarrollados a lo largo de un borde. Se utilizan para la detección, reconocimiento y análisis de formas. Para una detección mas precisa, es mejor utilizar una imagen binaria. La jerarquía de los contornos, hierarchy, indica la relación de los contornos: En qué nivel están, si están dentro de un contorno o si tienen otros contornos dentro. if(contours.size() == 0) throw(5); //ERROR_CONTORNOS_NO_DETECTADOS Si no se encuentra ningún contorno, no hay arandela a detectar. 6 vector<Moments> mu(contours.size()); //Vector de Momentos Se define un vector de momentos de la imagen. El momento más utilizado es el área, mu[i].m00. Con él analizaremos los contornos obtenidos. Esta función es análoga a contourArea(contours[i]). //Verifica todos los contornos for(unsigned i = 0; i<contours.size(); i++) { //Si el contorno es significativo, calcula los valores if(contourArea(contours[i])>mAreaMin && contourArea(contours[i])<mAreaMax) { A veces se detectan contornos de más, debido al ruido u otros factores no deseados. Con el área del contorno, podemos determinar el rango de aquellos que nos interesan. La circunferencia interna de una arandela tiene un área no menor al parámetro mAreaMin y la circunferencia de la iluminación tiene un área algo mayor a mAreaMax. Los contornos cuyo área esté dentro de éstos límites, es una arandela. //Obtiene los Momentos mu[i] = moments(contours[i], false); //Calcula los Diámetros con el área (mu[i].m00) if(2*sqrt(mu[i].m00/CV_PI) > diametroExterior) { diametroInterior = diametroExterior; diametroExterior = round(2*sqrt(mu[i].m00/CV_PI)); } else { diametroInterior = round(2*sqrt(mu[i].m00/CV_PI)); } Como leemos los contornos progresivamente con un lazo for, determinamos cuál es el contorno de la circunferencia exterior o interior. } } //Fin for if((diametroInterior == 0) && (diametroExterior == 0)) throw(6); //ERROR_ARANDELA_NO_DETECTADA Si las variables auxiliares no cambian de valor, significa que no se detectó ninguna arandela. } Arandela::s_CantidadArandelas += 1; return (Arandela(diametroInterior, diametroExterior)); Al terminar la detección, aumenta el número de arandelas y devuelve el objeto de tipo Arandela detectado. Las imágenes siguientes fueron obtenidas para ilustrar el proceso de detección 7 Paso 1 – Imagen tomada Paso 2 – Máscara Paso 4 – Threshold Paso 5 – Contornos 8 En el programa se implementa un pequeño menú para que el usuario pueda utilizar las distintas funciones. Se realiza primero una carga de las imágenes tomadas en el directorio. Luego, con las opciones, se cargan desde la cámara. german@HPG42:~/Documentos/cpp/Arandelas/Debug$ ./Arandelas +--------------------------------------------------+ | DETECCION Y MEDICION DE ARANDELAS POR IMAGEN | | Por German Emilio Mas | +--------------------------------------------------+ - Cargando Imagenes del directorio /home/german/IMG/A/ - Carga finalizada Que desea hacer? 1) Generar una nueva mascara 2) Tomar una nueva imagen 3) Ver los ultimos datos adquiridos 4) Ver la lista de arandelas 5) Ordenar lista según Diametro Interior 6) Ordenar lista según Diametro Exterior 7) Ordenar lista según Tiempo de Lectura 8) Salir Ingrese un numero: Al ingresar el comando del menú, se realiza la acción correspondiente. 9 Apendice I – Código main.cpp #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "arandela.h" #include "procesador.h" #include <iostream> #include <vector> #include <algorithm> using namespace cv; using namespace std; //Comparacion para ordenar por Diametro Interior bool comparaDiametroInterior(const Arandela& a1, const Arandela& a2) { return (a1.GetDiametroInterior()<a2.GetDiametroInterior()); } //Comparacion para ordenar por Diametro Exterior bool comparaDiametroExterior(const Arandela& a1, const Arandela& a2) { return (a1.GetDiametroExterior()<a2.GetDiametroExterior()); } //Comparacion para ordenar por Tiempo de Lectura bool comparaTiempo(const Arandela& a1, const Arandela& a2) { return (a1.GetTiempo()<a2.GetTiempo()); } int main(int argc, char** argv) { enum { ERROR_PARAMETRO_NEGATIVO, ERROR_DIAMETRO_INTERIOR_MAYOR, ERROR_MATRIZ_VACIA, ERROR_CAMARA_NO_ENCONTRADA, ERROR_AREA_LIMITE_INFERIOR_MAYOR, ERROR_CONTORNOS_NO_DETECTADOS, ERROR_ARANDELA_NO_DETECTADA, ERROR_VECTOR_VACIO }; Procesador proc; //Objeto que procesa las imagenes stringstream conv; //Para conversion de int a string string filename; //Imagen a precargar int comandoMenu; //Indica la accion a realizar vector<Arandela> vecArandelas; //Vector de Arandelas Leidas 10 vector<Arandela>::const_iterator cii; //Iterador del vector cout cout cout cout cout << << << << << "+--------------------------------------------------+" "| DETECCION Y MEDICION DE ARANDELAS POR IMAGEN |" "| Por German Emilio Mas |" "+--------------------------------------------------+" endl; << << << << endl; endl; endl; endl; //PRECARGA DE IMAGENES POR ARCHIVO try { proc.SetMascara("/home/german/IMG/BKG/1.jpg"); //Carga la Mascara cout << "- Cargando Imagenes del directorio /home/german/IMG/A/" << endl; for(unsigned i=1; i<56; i++) { conv << i; //Para convertir i en string filename = "/home/german/IMG/A/"+conv.str()+".jpg"; //Nombre del Archivo a Cargar conv.str(""); //Limpia el stringstream proc.SetImagen(filename); //Lee el archivo vecArandelas.push_back(proc.DetectarArandela()); //Cargo Lista } cout << "- Carga finalizada" << endl; //Menu while(true) { cout << "\nQue desea hacer?" << endl; cout << "1)\tGenerar una nueva mascara" << endl; cout << "2)\tTomar una nueva imagen" << endl; cout << "3)\tVer los ultimos datos adquiridos" << endl; cout << "4)\tVer la lista de arandelas" << endl; cout << "5)\tOrdenar lista según Diametro Interior" << endl; cout << "6)\tOrdenar lista según Diametro Exterior" << endl; cout << "7)\tOrdenar lista según Tiempo de Lectura" << endl; cout << "8)\tSalir" << endl << endl; cout << "Ingrese un numero: "; cin >> comandoMenu; switch(comandoMenu) { case 1: proc.SetMascara(1); break; case 2: proc.SetImagen(1); vecArandelas.push_back(proc.DetectarArandela()); //Cargo Vector break; case 3: if(vecArandelas.empty()) throw(7); //ERROR_VECTOR_VACIO sort(vecArandelas.begin(), vecArandelas.end(), comparaTiempo); cout << vecArandelas.back() << endl; break; 11 case 4: if(vecArandelas.empty()) throw(7); //ERROR_VECTOR_VACIO cout << "D.Int\tD.Ext\th\tTiempo" << endl; for(cii=vecArandelas.begin(); cii!=vecArandelas.end(); cii++) cout << *cii; break; case 5: sort(vecArandelas.begin(), vecArandelas.end(), comparaDiametroInterior); break; case 6: sort(vecArandelas.begin(), vecArandelas.end(), comparaDiametroExterior); break; case 7: sort(vecArandelas.begin(), vecArandelas.end(), comparaTiempo); break; case 8: cout << "\n--- Fin del programa ---" << endl << endl; return 0; break; default: cout << "\n--- Comando no reconocido. Por favor, ingrese el numero indicado. ---" << endl; break; } //Fin switch } //Fin while } //Fin try catch(int e) { switch(e) { case ERROR_PARAMETRO_NEGATIVO: cerr << "ERROR. Los parámetros de las arandelas no pueden ser negativos." << endl; break; case ERROR_DIAMETRO_INTERIOR_MAYOR: cerr << "ERROR. El diametro interior no puede ser mayor al exterior." << endl; break; case ERROR_MATRIZ_VACIA: cerr << "ERROR. La matriz no puede estar vacía." << endl; break; case ERROR_CAMARA_NO_ENCONTRADA: cerr << "ERROR. No se encontró la cámara." << endl; break; case ERROR_AREA_LIMITE_INFERIOR_MAYOR: 12 cerr << "ERROR. El límite inferior del área no puede ser mayor al límite superior." << endl; break; case ERROR_CONTORNOS_NO_DETECTADOS: cerr << "ERROR. No se detectaron contornos." << endl; break; case ERROR_ARANDELA_NO_DETECTADA: cerr << "ERROR. No se detectó ninguna arandela." << endl; break; case ERROR_VECTOR_VACIO: cerr << "ERROR. El vector de Arandelas está vacío." << endl; break; } //Fin Switch } //Fin Catch return (0); } arandelas.h #ifndef ARANDELA_H_ #define ARANDELA_H_ #include <iostream> #include <time.h> using namespace std; class Arandela { private: //Parametros float mDiametroInterior; float mDiametroExterior; float mAltura; float mArea; float mVolumen; time_t mTiempo; public: //Variables estáticas static unsigned int s_CantidadArandelas; //Constructores Arandela(void); Arandela(float, float); //Diametros Interior y Exterior //Destructores ~Arandela(void); //Gets const float GetDiametroInterior(void) const; const float GetDiametroExterior(void) const; const float GetAltura(void) const; const float GetArea(void) const; const float GetVolumen(void) const; const double GetTiempo(void) const; //Sets void SetDiametroInterior(float); void SetDiametroExterior(float); 13 void SetAltura(float); void SetTiempo(void); //Sobrecarga de Operadores Arandela& operator= (const Arandela&); friend std::ostream& operator<<(std::ostream&, const Arandela&); }; #endif // ARANDELA_H_ arandela.cpp #include "arandela.h" #include <iostream> #include <time.h> using namespace std; //Variables estáticas unsigned int Arandela::s_CantidadArandelas = 0; //Constructores Arandela::Arandela() { mDiametroInterior = 0; mDiametroExterior = 0; mAltura = 1; mArea = 0; mVolumen = 0; time(&mTiempo); } Arandela::Arandela(float d1, float d2) { if((d1 < 0) || (d2 < 0)) throw(0); //ERROR_PARAMETRO_NEGATIVO if(d1 >= d2) throw(1); //ERROR_DIAMETRO_INTERIOR_MAYOR mDiametroInterior = d1; mDiametroExterior = d2; mAltura = 1; mArea = (d2*d2-d1*d1)*3.1415*0.25; mVolumen = mArea*mAltura; time(&mTiempo); } //Destructores Arandela::~Arandela() { } //Gets const float Arandela::GetDiametroInterior() const { return(mDiametroInterior); } const float Arandela::GetDiametroExterior() const { return(mDiametroExterior); } const float Arandela::GetAltura() const { return(mAltura); } const float Arandela::GetArea() const { return(mArea); } const float Arandela::GetVolumen() const { return(mVolumen); } const double Arandela::GetTiempo() const { return(mTiempo); } 14 //Sets void Arandela::SetDiametroInterior(float d1) { if(d1 < 0) throw(0); //ERROR_PARAMETRO_NEGATIVO if(d1 >= mDiametroExterior) throw(1); //ERROR_DIAMETRO_INTERIOR_MAYOR mDiametroInterior = d1; mArea = (mDiametroExterior*mDiametroExterior-d1*d1)*3.1415*0.25; mVolumen = mArea*mAltura; } void Arandela::SetDiametroExterior(float d2) { if(d2 < 0) throw(0); //ERROR_PARAMETRO_NEGATIVO if(d2 <= mDiametroInterior) throw(1); //ERROR_DIAMETRO_INTERIOR_MAYOR mDiametroExterior = d2; mArea = (d2*d2-mDiametroInterior*mDiametroInterior)*3.1415*0.25; mVolumen = mArea*mAltura; } void Arandela::SetAltura(float h) { if(h < 0) throw(0); //ERROR_PARAMETRO_NEGATIVO mAltura = h; mVolumen = mArea*h; } void Arandela::SetTiempo() { time(&mTiempo); } //Sobrecarga de Operadores Arandela& Arandela::operator= (const Arandela& ara) { mDiametroInterior = ara.mDiametroInterior; mDiametroExterior = ara.mDiametroExterior; mAltura = ara.mAltura; mArea = ara.mArea; mVolumen = ara.mVolumen; mTiempo = ara.mTiempo; return (*this); } std::ostream& operator<<(std::ostream& salida, const Arandela& ara) { salida << ara.mDiametroInterior << "\t" << ara.mDiametroExterior << "\t" << ara.mAltura << "\t" << ctime(&ara.mTiempo); return(salida); } procesador.h #ifndef PROCESADOR_H_ #define PROCESADOR_H_ 15 #include <iostream> #include "arandela.h" using namespace std; using namespace cv; class Procesador { private: //Parametros Mat mImagen; Mat mMascara; int mValorThreshold; int mAreaMin; //Limite Inferior para verificar Contornos int mAreaMax; //Limite Superior para verificar Contornos public: //Constructores Procesador(void); Procesador(Mat, Mat); //Destructores ~Procesador(); //Get const Mat GetImagen(void) const; const Mat GetMascara(void) const; const int GetValorThreshold(void) const; const int GetAreaMin(void) const; const int GetAreaMax(void) const; //Set void SetImagen(string); //Via archivo void SetImagen(int); //Via camara void SetMascara(string); //Via archivo void SetMascara(int); //Via camara void SetValorThreshold(int); void SetAreaMin(int); void SetAreaMax(int); //Otros void MuestraImagen(string); void MuestraMascara(string); const Arandela DetectarArandela(void) const; }; #endif // PROCESADOR_H_ procesador.cpp #include #include #include #include #include #include "opencv2/highgui/highgui.hpp" "opencv2/imgproc/imgproc.hpp" "procesador.h" "arandela.h" <iostream> <string> using namespace std; using namespace cv; //Constructores Procesador::Procesador() 16 { } mImagen = Mat::zeros(1,1,CV_8UC3); mMascara = Mat::zeros(1,1,CV_8UC3); mValorThreshold = 30; mAreaMin = 120; mAreaMax = 69000; Procesador::Procesador(Mat img, Mat msk) { if(img.empty() || msk.empty()) throw(2); //ERROR_MATRIZ_VACIA mImagen = img.clone(); mMascara = msk.clone(); mValorThreshold = 30; mAreaMin = 120; mAreaMax = 69000; } //Destructores Procesador::~Procesador() { mImagen.release(); mMascara.release(); } //Gets const Mat const Mat const int const int const int Procesador::GetImagen() const { return mImagen.clone(); } Procesador::GetMascara() const { return mMascara.clone(); } Procesador::GetValorThreshold() const { return mValorThreshold; } Procesador::GetAreaMin() const { return mAreaMin; } Procesador::GetAreaMax() const { return mAreaMax; } //Sets void Procesador::SetImagen(string filename) { mImagen = imread(filename); if(mImagen.empty()) throw(2); //ERROR_MATRIZ_VACIA } void Procesador::SetImagen(int cam) { VideoCapture Camara(cam); if(!Camara.isOpened()) throw(3); //ERROR_CAMARA_NO_ENCONTRADA Camara.read(mImagen); } void Procesador::SetMascara(string filename) { Mat img; img = imread(filename); if(img.empty()) throw(2); //ERROR_MATRIZ_VACIA cvtColor(img, mMascara, CV_BGR2GRAY); threshold(mMascara, mMascara, mValorThreshold, 255, THRESH_BINARY); erode(mMascara, mMascara, Mat(), Point(-1, -1), 1, 1, 1); //Erosiona la máscara 17 erode(mMascara, mMascara, Mat(), Point(-1, -1), 1, 1, 1); máscara } //Erosiona la void Procesador::SetMascara(int cam) { Mat img; VideoCapture Camara(cam); if(!Camara.isOpened()) throw(3); //ERROR_CAMARA_NO_ENCONTRADA Camara.read(img); cvtColor(img, mMascara, CV_BGR2GRAY); threshold(mMascara, mMascara, mValorThreshold, 255, THRESH_BINARY); erode(mMascara, mMascara, Mat(), Point(-1, -1), 1, 1, 1); //Erosiona la máscara erode(mMascara, mMascara, Mat(), Point(-1, -1), 1, 1, 1); //Erosiona la máscara } void Procesador::SetValorThreshold(int valor) { if(valor < 0) throw(0); //ERROR_PARAMETRO_NEGATIVO mValorThreshold = valor; } void Procesador::SetAreaMin(int valor) { if(valor < 0) throw(0); //ERROR_PARAMETRO_NEGATIVO if(valor >= mAreaMax) throw(4); //ERROR_AREA_LIMITE_INFERIOR_MAYOR mAreaMin = valor; } void Procesador::SetAreaMax(int valor) { if(valor < 0) throw(0); //ERROR_PARAMETRO_NEGATIVO if(valor <= mAreaMin) throw(4); //ERROR_AREA_LIMITE_INFERIOR_MAYOR mAreaMax = valor; } //Otros void Procesador::MuestraImagen(string ventana) { if(mImagen.empty()) throw(2); //ERROR_MATRIZ_VACIA imshow(ventana, mImagen); cout << "\n--- Pulse una tecla para continuar ---" << endl; waitKey(); } void Procesador::MuestraMascara(string ventana) { if(mMascara.empty()) throw(2); //ERROR_MATRIZ_VACIA imshow(ventana, mMascara); 18 } cout << "\n--- Pulse una tecla para continuar ---" << endl; waitKey(); const Arandela Procesador::DetectarArandela() const { Mat imgMsk; float diametroExterior = 0; float diametroInterior = 0; //Enmascara (imagen con mascara) if(mImagen.empty() || mMascara.empty()) throw(2); //ERROR_MATRIZ_VACIA mImagen.copyTo(imgMsk, mMascara); cvtColor(imgMsk,imgMsk,CV_BGR2GRAY); //Convierte imgMsk a grayscale threshold(imgMsk, imgMsk, mValorThreshold, 255, THRESH_BINARY); //Threshold //Erosiona y dilata 2 veces para disminuir el ruido erode(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); erode(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); dilate(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); dilate(imgMsk, imgMsk, Mat(), Point(-1, -1), 1, 1, 1); //Contornos vector<vector<Point> > contours; //Vector de Contornos vector<Vec4i> hierarchy; //Vector de Jerarquía de Contornos findContours(imgMsk, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0,0)); if(contours.size() == 0) throw(5); //ERROR_CONTORNOS_NO_DETECTADOS vector<Moments> mu(contours.size()); //Vector de Momentos //Verifica todos los contornos for(unsigned i = 0; i<contours.size(); i++) { //Si el contorno es significativo, calcula los valores if(contourArea(contours[i])>mAreaMin && contourArea(contours[i])<mAreaMax) { //Obtiene los Momentos mu[i] = moments(contours[i], false); //Calcula los Diámetros con el área (mu[i].m00) if(2*sqrt(mu[i].m00/CV_PI) > diametroExterior) { diametroInterior = diametroExterior; diametroExterior = round(2*sqrt(mu[i].m00/CV_PI)); } else { diametroInterior = round(2*sqrt(mu[i].m00/CV_PI)); } } } //Fin for if((diametroInterior == 0) && (diametroExterior == 0)) throw(6); //ERROR_ARANDELA_NO_DETECTADA Arandela::s_CantidadArandelas += 1; return (Arandela(diametroInterior, diametroExterior)); } 19 20