2 de enero de 2014

Programando un lector de códigos de barras



Programando en Matlab un lector de códigos de barras que, trabajando con imágenes de códigos, las repara y decodifica.





                                          ALGORITMO                                              

Preparar los códigos de barras
La función prepararcodigos tiene como objetivo mejorar las imágenes de los códigos de barras, a fin de que estos puedan ser fácilmente decodificados posteriormente.

Entrada: imagen original.
Salida: imagen con únicamente el código de barras, mejorada.

La primera mejora se realiza sobre la orientación de la imagen, de modo que si el código apareciese rotado, se llevase a su correcta posición, es decir, con las barras completamente verticales, para una lectura del mismo mucho más cómoda y sencilla. Este proceso se realiza de la siguiente manera:

  1. Binarización y detección de bordes de la imagen.
  2. Cálculo sobre dichos bordes, mediante transformada de Hough, de una línea correspondiente a una barra.
  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 obtenida con Houg.
                           Abajo: imagen original rotada para corregir. La línea verde se ha añadido a posteriori 
                                                           como referencia visual.

La segunda mejora consiste en seleccionar el área correspondiente al código y desechar el resto de la imagen. Para ello:
  1. Se aplica una apertura sobre la imagen con una estructura tipo fila para agrupar separadamente los distintos elementos (código, dígitos, letras, etc).
  2. Se etiquetan los nuevos elementos y se elige el mayor, que corresponderá a la zona del propio código de barras.
  3. Se obtiene su bounding box y, sobre la imagen sin la apertura, se desecha el resto.

Figura 2
                        Arriba: imagen original.
                        Centro: apertura sobre la imagen para unir elementos. Nótese que el 
                                      código y los dígitos se han agrupado de manera separada.
                        Abajo: imagen original desechando lo que no pertenezca al código.


Por último, se intenta recuperar las barras que pudieran aparecer degradadas, mediante una apertura con un elemento tipo columna.

Figura 3
Arriba: imagen original.
Abajo: imagen reconstruida obtenida tras la apertura aplicada.




Leer una línea
La función leerLinea tiene como objetivo extraer un vector con las anchuras de las barras de una línea del código de barras.

Entrada: imagen con únicamente el código de barras.
Salida: vector con las anchuras de las barras.

  1. Sobre la línea, se busca la posición de las transiciones de blanco a negro y viceversa.
  2. Se crea un vector con la diferencia entre dichas transiciones, lo que equivale a la anchura de las barras.

Figura 4
En la tabla de arriba, la variable T es un vector con la posición de las transiciones de blanco a negro o de negro a blanco.
En la tabla de abajo, la variable línea es un vector con la diferencia entre transiciones, es decir, la anchura de las barras.




Obtener el código
La función devolverCodigo tiene como objetivo decodificar el código de barras y devolverlo.
Entrada: vector con las anchuras de las barras.
Salida: vector con el código leído.
  1. Se calcula una media entre la barra más fina y la más ancha, y se utiliza como frontera para determinar si una anchura es fina (se le asigna el valor 0) o gruesa (se le asigna el valor 1).
  2. Se crea una diccionario con los diferentes caracteres del código Interleaved 2de5. Así, por ejemplo, el vector [0,0,1,1,0] corresponderá al dígito ‘0’.
  3. Se recorre la línea, con un bucle que:

    • Comienza a tomar barras una vez pasado el marcador de inicio de código.
    • Toma las barras de cinco en cinco, alternadas de tal manera que toma cinco negras, cinco blancas, cinco negras, etc.
    • Las analiza dos a dos (por sencillez de código), es decir, cinco barras negras y cinco blancas, y las compara con el código, y va almacenando en un vector la decodificación. Si no corresponde a una palabra del diccionario, almacena un NaN.
    • Se detiene al llegar al marcador de fin de código.


Llamada a las funciones
La función leerCodigo llama sucesivamente a las funciones anteriormente descritas, de modo que la salida de una es la entrada de la siguiente. También es la encargada de que todas las líneas de la imagen sean leídas y se calcule un promediado de las decodificaciones resultantes.
Entrada: imagen original.
Salida: el código leído, por pantalla.
  1. Se llama a prepararcodigos para preprocesar la imagen.
  2. Se llama a leerlinea sucesivamente, y se almacena el código leído en cada línea en una estructura tipo cell.
  3. Se recoge en un vector auxiliar la muestra n-ésima de todas y cada una de las líneas leídas, y se calcula el valor que más se repite. Así, de haber discrepancias entre las líneas leídas, elegiremos la decodificación más votada.

A esta función se le introduce la imagen en formato matriz de matlab, por lo que debe ser introducida previamente con el comando imread. Nótese que las imágenes pueden tener más de dos dimensiones, por lo que la llamada general al algoritmo es de la forma:

    I = imread('Buenos1.tif');
    I = I(:,:,1);
    codigo = leerCodigo(I)


                                         RESULTADOS                                           

Se probó el algoritmo sobre una base de datos con imágenes de códigos de barras.

Todos los códigos marcados como ‘buenos’ (poca o ninguna degradación) son leídos correctamente. Los resultados han sido corroborados manualmente y con un lector de códigos de barras para Android.

Los códigos marcados como ‘malos’ (muy degradados) han sido reconstruidos y decodificados, algunos sólo parcialmente, pero su degradación es tal que no puede comprobarse el código original, ni de forma manual ni con el lector Android.



                                             CÓDIGO                                                   


function codigo=leerCodigo(I)
% Esta función decodifica un código de barras interleaved 2de5 y devuelve
% el código.
%
% Entrada:
%   -I: imagen de un código de barras. Este debe ser predominante en la
%   imagen.
%
% Salida:
%   -Código: vector con el código decodificado.
 
%% Preprocesado de la imagen
imprep=prepararcodigos(I);
%% Decodificación de todas las líneas y obtención del código más votado
% Leemos y almacenamos todas las líneas

alto=size(imprep,1);
cod=[];
for i=1:alto
    linea=leerLinea(imprep,i);
    cod{i}=devolverCodigo(linea);
end
% Recogemos en un vector auxiliar la muestra k-ésima de cada una de las
% j líneas leídas, y calculamos el valor decodificado que más se repite
% para dicha posición.

codigo=[];
ncol = max(cellfun(@length, cod)); % Cáclulo dimensión (el vector más largo)
for k=1:ncol
vec=[];
    for j=1:length(cod)
        if k > length(cod{j})
            aux=NaN; % Si la posición excede la longitud del vector,                         % rellenamos con NaN
        else
            aux=cod{j}(k);
        end
        vec =[vec, aux];
    end
    codigo(k)=mode(vec); % Valor más repetido
end
codigo
end

function imprep=prepararcodigos(I)
% Esta función prepara una imagen que contenga un código de barras, para  
% facilitar su posterior decodificación. De hallarse rotado, recupera la  
% verticalidad del código de barras, elimina el resto de elementos de la  
% imagen, y caso de estar degradado, intenta reconstruirlo.               
%                                                                         
% Entrada:                                                                
%  I: imagen del código de barras. Este debe ser predominante en la       
%  escena.                                                                 
%                                                                         
% Salida:                                                                 
%  imprep: imagen sólo del código de barras, corregida.                    

%% Corrección de ángulo


% Se binariza y se detectan los bordes:

BW=im2bw(I);
BWed = edge(BW,'canny');

% Se calcula la transformada de Hough y se obtienen los picos:
[H,T,R] = hough(BWed,'Theta', -30:30); 
P  = houghpeaks(H,5,'threshold',ceil(0.8*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',5,'MinLength',5); 

% Se calculan los puntos de los extremos de la línea más larga
% figure, imshow(I), hold on
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

% Visualización de la línea más larga encontrada sobre la imagen
% imshow(BWed), hold on 
% plot(xy_long(:,1),xy_long(:,2),'LineWidth',2,'Color','blue');
% plot([punto1(1),punto2(1)]',[punto1(2),punto2(2)]','LineWidth',2,'Color','blue');

% 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-90,'crop');

%% Extracción área de interés

% Se aplica una apertura para convertir el conjunto de dígitos y el
% conjunto de barras en "regiones  blancas" separadas
se = strel('line', 20, 0);
BW2=imopen(rotBW,se);
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 busca el elemento de área mayor,
% que corresponderá a la región de las barras.
CC = bwconncomp(BW2);
S = regionprops(CC,'Area');
[maxi,ind]=max([S.Area]);

% Se extrae su BoundingBox y nos quedamos únicamente con esa parte de la
% imagen.
T = regionprops(CC, 'BoundingBox');
min_y=T(ind).BoundingBox(2);
max_y=T(ind).BoundingBox(2)+T(ind).BoundingBox(4);
BW3=rotBW(min_y:max_y,:);

%% Reconstrucción

% Se aplica una apertura con un elemento tipo columna para reconstruir los
% códigos degradados:
[alto,ancho]=size(BW3);
se = strel('line', alto/4, 90);
imprep=imopen(BW3,se);

end

function linea=leerLinea(I,i)
% Esta función extrae, a partir de la imagen de un código de barras, un   
% vector con las anchuras de las barras del código.                       
%                                                                         
% Entrada:                                                                
%  I: imagen del código de barras. No debe contener nada aparte del       
%  código. 
%  i: número de la línea (fila) a leer.
%                                                                         
% Salida:                                                                 
%  linea: vector con la anchura de las barras de ambos colores, leídas    
%  de izda. a dcha.                                                         
                                                                        
% Posición de las transiciones de blanco a negro y viceversa
V=I(i,:);
T=find(diff(V));

% Diferencia entre transiciones, que equivale a la anchura de las barras
linea = diff(T);

function codigo=devolverCodigo(linea)
% Esta función decodifica  un código de barras interleaved 2de5, a partir 
% de un vector con la anchura de las barras blancas y negras del mismo.   
%                                                                         
% Entrada:                                                                
%  linea: vector con la anchura de las barras de ambos colores, leídas    
%  de izda. a dcha.                                                        
%                                                                         
% Salida:                                                                 
%  codigo: vector con el codigo numérico decodificado                      

%% Clasificación barra fina/barra gruesa
% Calculamos una media entre la barra más fina y la más gruesa y la
% utilizamos como frontera para determinar si una barra es fina (0) o
% gruesa (1)
pixnorm=linea;
med=(max(pixnorm)+min(pixnorm))/2;
for i=1:length(pixnorm)
    if pixnorm(i)>=med
        pixnorm(i)=1;
    else
        pixnorm(i)=0;
    end
end

%% Creación diccionario
% Creamos un diccionario con los diferentes caracteres de Interleaved 2de5
C0=[0,0,1,1,0];
C1=[1,0,0,0,1];
C2=[0,1,0,0,1];
C3=[1,1,0,0,0];
C4=[0,0,1,0,1];
C5=[1,0,1,0,0];
C6=[0,1,1,0,0];
C7=[0,0,0,1,1];
C8=[1,0,0,1,0];
C9=[0,1,0,1,0];

%% Decodificación
% Recorremos la línea y vamos decodificando. 

% Empezamos desde la posición 5 ya que las cuatro primeras barras 
% corresponden al código de inicio. Tenemos en cuenta también que el último
% código empieza en la barra fin - 3 (código de fin) - 9 (cinco barras 
% alternadas) = -12. 

% Se decodifica de dos en dos (5 barras negras/5 barras blancas) y por
% tanto la posición salta de 10 en 10.

codigo=[];
for i=5:10:length(pixnorm)-12  
    % En cada bucle se guardan en un vector cinco barras negras, se
    % decodifican comparándolas con el diccionario y se guarda el
    % resultado.
    A=pixnorm(i:2:i+8);
    if A==C0
        C=0;
    elseif A==C1
        C=1;
    elseif A==C2
        C=2;
    elseif A==C3
        C=3;
    elseif A==C4
        C=4;
    elseif A==C5
        C=5;
    elseif A==C6
        C=6;
    elseif A==C7
        C=7;
    elseif A==C8
        C=8;
    elseif A==C9
        C=9;
    else
        C=NaN;
    end
    codigo=[codigo,C];
    
    % Se realiza el mismo proceso con las barras blancas, que comienzan en
    % la posición inmediatamente siguiente a las negras.
    B=pixnorm(i+1:2:i+1+8); 
    if B==C0
        C=0;
    elseif B==C1
        C=1;
    elseif B==C2
        C=2;
    elseif B==C3
        C=3;
    elseif B==C4
        C=4;
    elseif B==C5
        C=5;
    elseif B==C6
        C=6;
    elseif B==C7
        C=7;
    elseif B==C8
        C=8;
    elseif B==C9
        C=9;
    else
        C=NaN;
    end
    codigo=[codigo,C];
end