Mon 21 October 2024

Filed under posts

Tags image processing chapel

Imagens podem ser entendidas como sinais bidimensionais (ou tridimensionais no caso de volumes 3D).

Pensando no caso de imagens digitais, vamos representá-las como um matriz de pixels representando o brilho de uma determinada região.

Para incluir informação de cor, cada pixel passa a ser representada por uma tupla de 3 valores RGB que representam a componente vermelha, verde e azul, respectivamente.

Ate aí, nada fora do usual. Contudo, isso torna a vida de implementar bibliotecas em processamento de imagens com suporte a cor um inferno na terra um pouco inconveniente, nos obrigando a lidar com um mar de cópias e conversões desnecessárias de imagens, sempre tendo que tratar casos com cor e sem cor.

Mas tem um truque interessante aplicado nos primordios da transmissao de sinais de tv a cores que pode nos ser útil. Na era mezozoica em que as pessoas dividiam espaço com os terriveis dinossauros, elas sofriam com polio e TVs sem cores. Obviamente, o problema das TVs a cores foi atacado primeiro, e com seu desenvolvimento surgiu um problema: o sistema de transmissao de TV analógico (não temos pixels aqui, mas a ideia segue) é contruído em torno de sistemas de vídeo sem cores, que contém apenas a informação de brilho. Como podemos adaptar os sistemas de transmissão, para enviar informações de cor, enquanto mantemos compatibilidade com sistemas sem cor?

A resposta encontrada foi o uso do espaço de cores YCbCr, definido por nossos amigos da NTSC, um sistema de cores onde separamos as informações de luminância (Y) e crominância (Cb e Cr). De forma que aparelhos antigos pudessem interpretar a informação de luminância e ignorar a informação de croma, mantendo-se acessivel a TVs preto e branco.

Embora possamos usar YCbCr nas implementações futuras, pretendo usar Lab, que tambem possui a característica interessante de separar luminância e crominância, esse formato ainda se propõe a ser um formato onde variações numericas correspondam a percepção de mudanca de cor percebida por meros humanos.

O espaço Lab é composto por luminância (L) que representa a informação de brilho que é representada num dominio de [0,100], e as componentes de cor (ab) que representam a mudanca entre verde a vermelho (a) r azul a amarelo (b) ambas representadas num domínio de [-128, 128].

Mão na massa

Para podermos converter nossas imagens, normalmente representadas num espaço RGB (ou sRGB pra ser mais preciso), vamos precisar introduzir o espaço de cores XYZ (sim, temos mais espaços de cores do que pode imaginar). A ideia é que XYZ é um espaço de cores extremamente abrangente e que consegue abrigar todas as cores visíveis, o que acaba fazendo desse formato uma excelente opção de formato intermediário de conversão entre todos os demais formatos. Com isso ele será o nosso comutador de cores, e pra incluir conversão de cores para qualquer novo formato, nos basta implementar uma opção de conversão de/para o formato XYZ.

Então, nosso primeiro passo é converter nossa entrada RGB para o espaço XYZ, que consiste de três operações básicas:

  1. Escalarmos nosso valor RGB de tal forma que cada componente fique dentro do dominio [0,1], usualmente a representação de RGB vai depender do numero de bits útilizado na representação de cores da imagem ([0,256) ou [0,65536) para imagens de 8 ou 16 bits).
  2. Uma correção de gamma que é responsável por resolver alguns problemas de não linearidade na representação usual de cores.
  3. A conversão de fato, que consiste de uma transformação linear para o espaço XYZ.
proc rgb2xyz(red, green, blue, max: real = 255.0): (real, real, real) {
    var r: real = red / max;
    var g: real = green / max;
    var b: real = blue / max;

    // gamma correction
    r = if r < 0.04045 then r / 12.92 else ((r + 0.055) / 1.055)**2.4;
    g = if g < 0.04045 then g / 12.92 else ((g + 0.055) / 1.055)**2.4;
    b = if b < 0.04045 then b / 12.92 else ((b + 0.055) / 1.055)**2.4;

    // convert xyz
    const x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b;
    const y = 0.2126729 * r + 0.7151522 * g + 0.072175 * b;
    const z = 0.0193339 * r + 0.119192 * g + 0.9503041 * b;

    return (x, y, z);
}

A conversão entre XYZ e Lab é bastante similar e consiste de 3 passos:

  1. Escalarmos nosso valor, considerando um determinado ponto de referencia para o branco (usualmente D65 que representa o branco percebido em ambientes de luz natural).
  2. Aplicarmos uma função \(f(x)=\cases{\sqrt[3]{x} & if x>(216/24389) \\ { { (24389/27) x + 16} \over {116}} & \text{otherwise}}\) que aplica a transformação responsável por aproximar o espaço de representação da percepção humana.
  3. A conversão que consiste de uma transformação para o espaço Lab.
proc xyz2lab(x, y, z): (real, real, real) {

    // reference white point (D65)
    const wx = 0.95047;
    const wy = 1.00000;
    const wz = 1.08883;

    var fx = x / wx;
    var fy = y / wy;
    var fz = z / wz;

    const epsilon = 216.0/24389.0;
    const kappa = 24389.0/27.0;

    fx = if fx > epsilon then cbrt(fx) else (kappa * fx + 16.0) / 116.0;
    fy = if fy > epsilon then cbrt(fy) else (kappa * fy + 16.0) / 116.0;
    fz = if fz > epsilon then cbrt(fz) else (kappa * fz + 16.0) / 116.0;

    const l = (116.0 * fy) - 16.0; // [0,100]
    const a = 500.0 * (fx - fy); // [-128, 128]
    const b = 200.0 * (fy - fz); // [-128, 128]

    return (l, a, b);
}

A operação inversa, a qual vamos precisar para salvar nossas imagens de volta no espaço RGB, consiste simplesmente da inversa das mesmas funções.

proc lab2xyz(l: real, a: real, b: real): (real, real, real) {
    //white reference
    const wx = 0.95047;
    const wy = 1.00000;
    const wz = 1.08883;

    var fy = (16.0 + l) / 116.0;
    var fx = fy + (a / 500.0);
    var fz = fy - (b / 200.0);

    const kappa = 24389.0/27.0;
    const epsilon = 216.0/24389.0;

    const delta = cbrt(epsilon);

    fx = if fx > delta then fx ** 3.0 else (116.0 * fx - 16.0) / kappa;
    fy = if fy > delta then fy ** 3.0 else (116.0 * fy - 16.0) / kappa;
    fz = if fz > delta then fz ** 3.0 else (116.0 * fz - 16.0) / kappa;

    return (fx * wx, fy * wy, fz * wz);
}

proc xyz2rgb(x: real, y: real, z: real, maxval: real = 255.0): (real, real, real) {
    var r: real = 3.2406 * x - 1.5372 * y - 0.4986 * z;
    var g: real = -0.9689 * x + 1.8758 * y + 0.0415 * z;
    var b: real = 0.0557 * x - 0.2040 * y + 1.0570 * z;

    // gamma correction
    r = if r < 0.0031308 then r * 12.92 else 1.055 * (r ** (1/2.4) - 0.055);
    g = if g < 0.0031308 then g * 12.92 else 1.055 * (g ** (1/2.4) - 0.055);
    b = if b < 0.0031308 then b * 12.92 else 1.055 * (b ** (1/2.4) - 0.055);

    return (r*maxval, g*maxval, b*maxval);
}

E é isso aí. Caso queira implementar por conta, recomendo o colorizer como uma referencia muito útil para os testes.

Comment

Wed 05 February 2020

Filed under posts

Tags machine learning data science chapel

Umas das principais ferramentas na nossa caixa de ferramentas de reconhecimento de padrões certamente vem dos classificadores de padrões.

Então para começarmos nossa jornada, vamos entender alguns conceitos fundamentais.

Classificação de Padrões

Imagine um cenário onde temos diversas amostras separadas em diferentes grupos.

De forma bastante simples, qualquer pessoa mesmo …

Read More

Wed 12 April 2000

Filed under posts

Tags machine learning data science

Constantemente me deparo com pessoas interessadas em aprender mais sobre data science. E isso se reflete principalmente na quantidade de blogs e materiais do tipo:

PROGRAMADORES ODEIAM ELE.
Torne-se um cientista de dados em 30 dias. Clique aqui.

Embora eles façam um papel interessante popularizando os conceitos e permitindo um …

Read More

Espaço do Peixinho © Alan Peixinho Powered by Pelican and Twitter Bootstrap. Icons by Font Awesome and Font Awesome More