4 de enero de 2014

Programando un lector de crotales



Algoritmo de Matlab para la lectura a través del OCR Tesseract de códigos en imágenes de crotales tomadas en una cinta transportadora.






                                          ALGORITMO                                              

Preparar los crotales
La función prepararcrotales tiene como objetivo mejorar las imágenes de los crotales, a fin de que estos puedan ser fácilmente decodificados posteriormente a través del OCR Tesseract.

Entrada: imagen original.
Salida: imagen con únicamente el código del crotal a analizar.

La primera mejora se realiza sobre la orientación de la imagen, de modo que si el crotal apareciese rotado, se llevase a su correcta posición, es decir, con los números en paralelo al borde inferioro, para una correcta lectura por parte de Tesseract. Este proceso se realiza de la siguiente manera:

  1. Binarización mediante Otsu y detección de bordes de la imagen.
  2. Cálculo sobre dichos bordes, mediante transformada de Hough, de la línea más larga, que corresponderá al borde inferior del crotal.
  3. Cálculo del ángulo de dicha línea, y rotación de la imagen para corregirlo.


Figura 1
Arriba: imagen original.
Centro: imagen binarizada y detección de bordes. En azul, línea más larga obtenida con Hough.
Abajo: imagen original rotada para corregir la orientación.

Tras el proceso de binarización, puede darse el caso de que los dígitos se hayan mezclado con el fondo. Para solucionarlo, se vuelve a obtener mediante Hough el borde inferior del crotal, con unos parámetros tales que se busque una línea más o menos horizontal, y recortamos lo que esté debajo.

Figura 3
Arriba: imagen binarizada. Nótese que los dígitos se han mezclado con el fondo de la imagen en el borde inferior.
Abajo: imagen recortada desde el borde inferior del crotal. Se soluciona el problema, de una manera simple

La siguiente mejora consiste en seleccionar el área correspondiente al código a analizar y desechar el resto de la imagen. Para ello:


  1. Se invierte la imagen y se etiquetan los elementos de la imagen.
  2. Se elimina el elemento mayor, que corresponde al fondo, y se obtienen los siguientes cuatro mayores, que corresponderán a los dígitos aunque haya ruido o sean parciales.
  3. Se obtienen las bounding box de los dígitos y, a partir del de más a la izquierda y del de más a la derecha, se calcula un recuadro que los contenga a todos. Se desecha el resto de la imagen.


Figura 4
Arriba: imagen binarizada y recortada inferiormente.
Abajo: sólo los dígitos, desechando el resto, e invertida para que Tesseract pueda trabajar con ella.


Por último, se añade un borde blanco a la imagen de los dígitos, para que Tesseract pueda trabajar con ella.

Figura 5
Arriba: imagen sólo dígitos.
Abajo: borde blanco añadido para que Tesseract pueda trabajar con la imagen.


Leer los crotales
La función leercrotales efectua las llamadas necesarias para leer, a través de Tesseract, el código de los crotales.

Entrada: imagen de crotal a analizar.
Salida: genera una imagen en formato .tif con los dígitos analizados y un archivo .txt con la lectura obtenida por tesseract.

El proceso que sigue es el siguiente:


  1. Llama a la función preparacrotales, pasándole la imagen del crotal (en escala de grises, un único canal) y obteniendo la imagen con sólo los dígitos a analizar.
  2. Se guarda dicha imagen en formato .tif en el directorio utilizado.
  3. Se llama al OCR Tesseract, pasándole el archivo .tif. Este lee la imagen y devuelve la lectura en un archivo .txt en el directorio utilizado.
I = imread('crotal.tif');
function leercrotales(I)

imprep=prepararcrotales(I);
imwrite(imprep,'Imagen.tif','tiff');
!tesseract Imagen.tif Codigo;
end



                                         RESULTADOS                                            

Se han realizado pruebas sobre una base de datos con imágenes de crotales. Los resultados son correctos en un 90% de los casos, sin embargo se han detectado dos tipos de errores:

En el primero, la umbralización elimina los detalles buscados. Es posible que mediante otro método diferente de Otsu, o controlando los parámetros de la umbralización, pudiera obtenerse una binarización correcta en todas las imágenes, y ese sería el primer punto sobre el que trabajar. Sin embargo, hay que tener en cuenta que este suele ser un objetivo complicado y muchas veces inalcanzable.

En el segundo, la imagen del código se procesa correctamente, pero el OCR Tesseract falla (por ejemplo, reconociendo un '1' como '|'). Este es un problema ajeno al algoritmo creado.




                                             CÓDIGO                                                   


function leercrotales(I)
% Esta función devuelve, a partir de la imagen de un crotal y a través del
% OCR Tesseract, el cógido del crotal.
%
% Entrada:
%   -I: imagen de un crotal.
%
% Salidas:
%   genera una imagen en formato .tif con los dígitos analizados y un
%   archivo .txt con la lectura obtenida por tesseract.
%% Preprocesado de la imagen

imprep=prepararcrotales(I);
%% Lectura del código
imwrite(imprep,'Imagen.tif','tiff');
!tesseract Imagen.tif Codigo;
end


function imprep=prepararcrotales(I)
% Esta función preprocesa imágenes de crotales, quedándose únicamente con
% la región de la imagen que contiene el código numérico de los mismos.
%
% Entrada:
%   -I: imagen de un crotal.
%
% Salida:
%   -imprep: subimagen conteniendo únicamente la región del código, con
%            dígitos en negro sobre fondo blanco.
%% Eliminación borde inferior
% Se elimina el borde inferior de la imagen (un 2%) que no cincierne al crotal:

borde=round(size(I,1)-0.02*size(I,1));
I = I(1:borde,:);
%% Corrección de ángulo
% Se binariza y se detectan los bordes:

level = graythresh(I);
BW = im2bw(I,level);
se = strel('disk', 6, 0);
BW=imclose(BW,se);
BWed = edge(BW,'canny');
% Se calcula la transformada de Hough y se obtienen los picos:
[H,T,R] = hough(BWed);
P  = houghpeaks(H,5,'threshold',ceil(0.3*max(H(:))));
x = T(P(:,2)); y = R(P(:,1));
% Se extraen las líneas de la imagen
lines = houghlines(BWed,T,R,P,'FillGap',40,'MinLength',7);
% Se calculan los puntos de los extremos de la línea más larga
max_len = 0;
for k = 1:length(lines)
   xy = [lines(k).point1; lines(k).point2];
   len = norm(lines(k).point1 - lines(k).point2);
   if ( len > max_len)
      max_len = len;
      xy_long = xy;
   end
end
% Se calcula en ángulo de la línea y se gira la imagen para corregir
angle_in_deg=atan2(xy_long(4)-xy_long(3),xy_long(2)-xy_long(1))*180/pi;
rotBW = imrotate(BW,angle_in_deg,'crop');
rotBWed = imrotate(BWed,angle_in_deg,'crop');

%% Obtención área de interés
% Calculamos la nueva línea más larga tras el giro

[H,T,R] = hough(rotBWed,'Theta', -90:-80);
P  = houghpeaks(H,5,'threshold',ceil(0.3*max(H(:))));
x = T(P(:,2)); y = R(P(:,1));
lines = houghlines(rotBWed,T,R,P,'FillGap',20,'MinLength',2);
max_len = 0;
for k = 1:length(lines)
   xy = [lines(k).point1; lines(k).point2];
   len = norm(lines(k).point1 - lines(k).point2);
   if ( len > max_len)
      max_len = len;
      xy_long = xy;
   end
end
% Recorte de la parte inferior de la imagen para evitar posibles mezclas de
% dígitos con fondo.

BW2=rotBW(1:xy_long(4)-5,:);
BW2=1-BW2;
% Se etiquetan los elementos de la imagen.
L = bwlabel(BW2,8);
[L, num] = bwlabel(BW2,8);
% Se extrae el área de los elementos y se elimina el elemento de área mayor
% (el fondo).

CC = bwconncomp(BW2);
S = regionprops(CC,'Area');
A=[S(:).Area];
% Se seleccionan los siguientes 4 elementos de área mayor (los dígitos)
indi=[];
H=0;
for i=1:length(S)
     H=H+1;
     if H <= 4
        [maxi,ind]=max([S.Area]);
        if maxi>8000
            S(ind).Area=NaN;
            H=H-1;
        else
            indi=[indi,ind];
            S(ind).Area=NaN;
        end
     end
end
 
% Se extraen la BoundingBox de cada dígito y a partir de ellas los límites
% que contienen a los cuatro.

T = regionprops(CC, 'BoundingBox');
min_x=min([
    T(indi(1)).BoundingBox(1),
    T(indi(2)).BoundingBox(1),
    T(indi(3)).BoundingBox(1),
    T(indi(4)).BoundingBox(1)]);
max_x=max([
    T(indi(1)).BoundingBox(1)+T(indi(1)).BoundingBox(3),
    T(indi(2)).BoundingBox(1)+T(indi(2)).BoundingBox(3),
    T(indi(3)).BoundingBox(1)+T(indi(3)).BoundingBox(3),
    T(indi(4)).BoundingBox(1)+T(indi(4)).BoundingBox(3)]);
min_y=min([
    T(indi(1)).BoundingBox(2),
    T(indi(2)).BoundingBox(2),
    T(indi(3)).BoundingBox(2),
    T(indi(4)).BoundingBox(2)]);
% Se recorta la imagen para quedarse únicamente con la región de los
% dígitos.

[alto, ancho]=size(BW2);
imaux=rotBW(:,min_x:max_x);
imaux=imaux(min_y:alto,:);
[alto2, ancho2]=size(imaux);
imprep=ones(alto2+round(alto2/4),ancho2+round(ancho2/4));
% Se añade un borde blanco alrededor de la imagen para un posterior correcto
% funcionamiento de Tesseract.
filainicio = round(alto2/8);
coluinicio = round(ancho2/8);
imprep(filainicio:filainicio+size(imaux,1)-1,coluinicio:coluinicio+size(imaux,2)-1) = imaux;
imshow(imprep)
end