Comparación entre una imagen y un valor constante: void inRange (Mat src, Mat lowerb, Mat upperb, Mat dst); ? dest(x,y):= (lowerb(x,y) <= src(x,y) <= upperb(x,y)) Comprueba, para cada píxel de la imagen, si su valor está entre el límite inferior (lowerb) y el superior (upperb), que pueden ser imágenes o constates (esclares). El resultado es una imagen binaria, 8U, con 0 ó 255 (todos los bits a 1). Si src es multicanal, comprueba todos los canales están en el rango. Umbralización/binarización de una imagen: double threshold (Mat src, Mat dst, double thresh, double maxval, int tipo); Umbraliza la imagen según el método dado en tipo y el umbral es thresh. P.ej., THRESH_BINARY para binarizar con umbral 128:threshold(A, C, 128, 255, THRESH_BINARY); ? C(x,y):= si A(x,y) > 128 entonces 255 si no 0 La umbralización se hace con un valor constante. Para un método más avanzado ver adaptiveThreshold. El umbral se calcula para cada píxel, usando una vecindad local (adaptativo).
Máximo entre dos imágenes o entre imagen y un valor constante: void max (Mat src1, Mat src2, Mat dest); dest(x,y):= max {src1(x,y), src(x,y)} En imágenes multicanal, calcula el máximo para cada uno de los canales. Mínimo entre dos imágenes o entre imagen y un valor constante: void min (Mat src1, Mat src2, Mat dest); dest(x,y):= min {src1(x,y), src(x,y)} Potencia, exponencial y logaritmo de los píxeles de una imagen: void pow (Mat A, double p, Mat C); ? C(x,y):= A(x,y)p void exp (Mat A, Mat C); ? C(x,y):= eA(x,y) void log (Mat A, Mat C); ? C(x,y):= loge |A(x,y)| Para evitar saturación y pérdida de información, es conveniente transformar las profundidad a reales de 32 o 64 bits. Ejemplo. Transformación de gamma de una imagen img: Mat im32F; img.convertTo(im32F, CV_32F, 1.0/255.0); pow(im32F, ui->doubleSpinBox->value(), im32F); im32F.convertTo(img, CV_8U, 255);
Multiplicar dos imágenes: void multiply (Mat src1, Mat src2, Mat dest, [double scale=1, int tipo=-1] ); dest(x,y):= src1(x,y)*src2(x,y)*scale El valor scale permite evitar problemas de saturación. El valor tipo permite cambiar el tipo de datos de salida. src1 y src2 pueden ser escalares. Y se puede usar modo in-place. Ejemplo: multiplicar dos imágenes de 8 bits:multiply(im1, im2, resultado, 1./255); Dividir una imagen por otra: void divide (Mat src1, Mat src2, Mat dest, [double scale=1, int tipo=-1] ); dest(x,y):= scale*src1(x,y)/src2(x,y) Las mismas opciones y parámetros que en la anterior función. Tener cuidado con los problemas de saturación. Puede ser adecuado usar enteros con signo. Ejemplo: divide(im1, im2, resultado, 255.0);
Observar el estilo de programación usando estas funciones. Por ejemplo, queremos implementar la operación: R(x,y):= A(x,y)·(255-N(x,y))/255 + B(x,y)·N(x,y)/255 void Combina (Mat A, Mat B, Mat N, Mat &R); Implementación 1. multiply(B, N, B, 1./255); bitwise_not(N, N); multiply(A, N, A, 1./255); R= A+B; Implementación 2. Vec3b pA, pB, pN, pR; R.create(A.size(), A.type()); for (int y=0; y(y, x); pB= B.at(y, x); pN= N.at(y, x); for (int c= 0; c<3; c++) pR[c]= (pA[c]*(255-pN[c])+pB[c]*pN[c])/255.0; R.at(y, x)= pR; } Esto es más sencillo y eficiente, porque las opera-ciones están optimizadas Esto es menos eficiente (~2 veces más lento) y menos recomendable Aunque no del todo correcto, porque modifica las imágenes de entrada A, B y N…
Operaciones con histogramas En OpenCV los histogramas son de tipo Mat siendo la profundidad float y de 1 canal. Por lo tanto, podemos usar las operaciones lineales (suma, resta, producto, etc.) y el acceso y modificación con at. El número de dimensiones del histograma depende de los que queramos calcular (hist. de 1 canal, de 2 canales, etc.). El tamaño del histograma (bins) también puede cambiar, según la resolución que queramos (desde 2 hasta 256). Tenemos también una operación de ecualización del histograma: equalizeHist. Otra cuestión relacionada son las tablas de transformación (look-up table, o LUT), para realizar una transformación de curva tonal arbitraria.
Propiedades de un histograma: Número de dimensiones (dims). Normalmente tendremos 1 dimensión (escala de grises), 2 dimensiones (histogramas conjuntos de dos canales) o como mucho 3 (de triplas R,B,G). Para cada dimensión, número de celdas (histSize). Normalmente será una potencia de 2, como 256, 64, 32… Rango de valores correspondientes a cada celda (ranges), (normalmente el hist. es uniforme, y los valores son informes). Ejemplos. Histograma de 1 dimensión y 4 celdas Histograma de 2 dimensiones, con tamaños 3 y 2
Calcular el histograma de una imagen: void calcHist (Mat* images, int nimages, const int* canales, Mat mask,Mat hist, int dims, int* histSize, float** ranges,[ bool uniform= true, bool acumular= false] ); images: array de imágenes sobre las que se calcula el histograma. Normalmente será una sola imagen de 1 o 3 canales. Si hay varias, todas ellas deben tener el mismo tamaño. nimages: número de imágenes que hay en el anterior array. canales: array con los números de los canales sobre los que se quiere calcular el histograma. Los canales están numerados 0, 1, … mask: máscara opcional. Si vale noArray(), no se usa. En otro caso, debe ser una imagen de 1 canal y profundidad 8U. hist: histograma resultante, de profundidad float. dims: número de dimensiones del histograma. Debe coincidir con el tamaño del array canales (por ejemplo, si dims=3, deben indicarse 3 canales). histSize: tamaño (número de bins) en cada una de las dimensiones. ranges: rango para cada dimensión. Normalmente (con histograma uniforme) será un array de arrays con valores {0, 256}. uniform: indica si las celdas se distribuyen el rango uniformemt. (es lo normal). acumular: permite acumular los valores entre distintas llamadas a la función.
Ejemplo 1. Calcular el histograma unidimensional del nivel de gris de una imagen “a.jpg” en color. El resultado se escribe en salida debug: Mat img= imread("a.jpg", 1); Mat gris; Mat hist; cvtColor(img, gris, CV_BGR2GRAY); // Conversión a gris int canales[1]= {0}; int bins[1]= {256}; float rango[2]= {0, 256}; const float *rangos[]= {rango}; calcHist(&gris, 1, canales, noArray(), hist, 1, bins, rangos); for (int i= 0; i<256; i++) qDebug("Celda %d: %g", i, hist.at(i));
Observar el acceso a las celdas del histograma con: hist.at(posición)
Ejemplo 2. Calcular el histograma bidimensional de los canales (R,G) de una imagen “a.jpg” en color, con 64×64 celdas. El resultado se pinta en una imagen: Mat img= imread("a.jpg", 1); Mat hist; int canales[2]= {2, 1}; // El 2 es el canal R, y el 1 es el canal G int bins[2]= {64, 64}; float rango[2]= {0, 256}; const float *rangos[]= {rango, rango}; calcHist(&img, 1, canales, noArray(), hist, 2, bins, rangos);
// Operaciones para pintar el histograma Mat pinta(64, 64, CV_8UC1); double minval, maxval; minMaxLoc(hist, &minval, &maxval); // Para escalar el color entre blanco y negro for (int r= 0; r<64; r++) for (int g= 0; g<64; g++) pinta.at(r, g)= 255-255*hist.at(r, g)/maxval; namedWindow("Histograma R-G", 0); imshow("Histograma R-G", pinta);
Normalizar un histograma: conseguir que la suma de todas las celdas sea un valor dado (por ejemplo, que sumen 100). Si se conoce el número de píxeles de la imagen, se puede hacer con una simple ponderación: hist*= 100.0/img.size().area(); Si no se conoce, se puede calcular la suma total de las celdas:hist*= 100.0/norm(hist, NORM_L1); Obtener máximo y mínimo de un histograma: void minMaxLoc (Mat h, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, Mat mask=noArray()); Dado el histograma, calcula el mínimo (minVal), el máximo (maxVal), el índice de la celda mínima (minLoc) y máxima (maxLoc). Ver un uso en el ejemplo anterior. También puede aplicarse esta función sobre imágenes, para buscar el píxel más claro y/o más oscuro. Ecualizar el histograma de una imagen: void equalizeHist (Mat src, Mat dst); La imagen debe ser de 1 solo canal y 8U. Otras funciones interesantes: compareHist (comparar histogramas), normalize (normalizar los niveles de gris de una imagen, usando diferentes criterios).
Las tablas de transformación (look-up table, LUT) son tablas que definen funciones discretas de la forma: f: [0…255] ? R Esto nos permite construir cualquier curva tonal arbitraria. En OpenCV, una LUT es una matriz de tipo Mat: Mat lut(1, 256, CV_8UC1); 1 fila y 256 columnas. El número de canales puede ser 1 o 3. La profundidad puede cambiar (8U, 8S, 16U, 32F, …) Aplicar una transformación de tabla LUT: void LUT (Mat src, Mat lut, Mat dest); La profundidad de entrada, src, debe ser 8 bits (con o sin signo), y la de salida (en dest y en lut) puede ser cualquiera. Si la imagen src tiene 1 canal, lut debe ser de 1 canal. Si src tiene 3 canales, lut puede ser de 1 canal (aplicar la misma transformación a todos los canales) o de 3 canales (como si fueran 3 tablas LUT, una para cada canal).
Ejemplo 1. Aplicar una ecualización conjunta del histograma a una imagen “a.jpg” en color, usando calcHist y LUT:
Mat img= imread(“a.jpg", 1); imshow("Entrada", img); Mat gris, hist; cvtColor(img, gris, CV_BGR2GRAY); int canales[1]= {0}, bins[1]= {256}; float rango[2]= {0, 256}; const float *rangos[]= {rango}; calcHist(&gris, 1, canales, noArray(), hist, 1, bins, rangos); hist*= 255.0/norm(hist, NORM_L1); Mat lut(1, 256, CV_8UC1); float acum= 0.0; for (int i= 0; i<256; i++) { lut.at(0, i)= acum; acum+= hist.at(i); } Mat res; LUT(img, lut, res); imshow("Ecualizada", res);
Página anterior | Volver al principio del trabajo | Página siguiente |