Dithering — Parte I — Halftone via Pontilhado Ordenado

Halftone (meio-tom) é uma técnica de processamento de imagens que emprega padrões de pontos pretos ou brancos para reduzir o número de níveis de cinza de uma representação. Devido ao fato do sistema visual humano atenuar a distinção entre os pontos com tons diferentes, os padrões de pontos pretos e brancos produzem um efeito visual como se a imagem fosse composta por mais tons de cinza.

Esta técnica é muito utilizada em aplicações de impressão em jornais e faxes, onde apenas os níveis brancos e pretos são necessários. Há diferentes abordagens para produção de tais padrões, tais como o pontilhado ordenado (ordered dithering) e pontilhado com difusão de erro (dithered with error difusion).

Neste exemplo, implementamos um algoritmo de halftoning típico para impressão, baseado em padrões de pontos com pontilhado ordenado. A figura abaixo mostra os padrões que podem ser utilizados para aproximar dez níveis de cinza:



Cada conjunto de níveis de cinza é representado por um padrão \(3 \times 3\) de pontos brancos ou pretos. Uma área de \(3 \times 3\) pixels cheia de pontos pretos é uma aproximação de um nível de cinza preto (ou 0). De forma análoga, uma área \(3 \times 3\) de pontos totalmente brancos será uma aproximação do branco em uma escala de nível de cinza (ou 9). Os demais padrões intermediários entre \(]0, 255[ \) serão re-escalados para o intervalo \([0, 9]\), conforme ilustrado na imagem abaixo.



É possível notar que utilizamos nove limiares (threshold) para a sub-codificação: o intervalo \([0, 25.5[ \) é truncado para \(0 \), \([25.5, 51[ \) é truncado para \(1 \), \([51, 76.5[\) para \(2 \), e assim por diante. Assim, cada pixel na imagem de entrada irá corresponder a um padrão de \(3 \times 3 \) pixels na imagem gerada. Desta forma, a resolução espacial será reduzida a \(33\% \) da resolução espacial original. Isto é, antes da substituição de cada pixel pela máscara devemos reduzir em \(\frac{1}{3} \) as dimensões da imagem para que o resultado tenha o mesmo tamanho da original.

Todo o processo descrito é implementado na seguinte função;

def halftone(im, predown = True, histeq = False):
    """
    Convert a image from grayscale image to binary image using halftone
 
    Parameters
    ----------
    * im: numpy matrix (image)
    * predown: boolean (default True)
        divide original image by 3 before halftoning
    * histeq: boolean (default False)
        perform a histogram equalization before halftone
 
    Written by Pedro Garcia Freitas [sawp@sawp.com.br]
    Copyright 2010 by Pedro Garcia Freitas
 
    see: http://www.sawp.com.br
    """
    def halft(im):
        # provides the threshold levels for binarization
        level9 = array([[1,1,1],[1,1,1],[1,1,1]])
        level8 = array([[1,0,1],[1,1,1],[1,1,1]])
        level7 = array([[1,0,1],[1,1,1],[1,1,0]])
        level6 = array([[0,0,1],[1,1,1],[1,1,0]])
        level5 = array([[0,0,1],[1,1,1],[1,0,1]])
        level4 = array([[0,0,0],[1,1,1],[0,1,0]])
        level3 = array([[0,0,0],[1,1,0],[0,1,0]])
        level2 = array([[0,0,0],[1,1,0],[0,0,0]])
        level1 = array([[0,0,0],[0,1,0],[0,0,0]])
        level0 = array([[0,0,0],[0,0,0],[0,0,0]])
 
        # create and rescale new image to fit levels
        (h,l) = im.shape
        masked = (10.0 / 255.0) * im
        masked = ceil(masked)
        rIm = zeros((3 * h, 3 * l))
 
        # generate the halftoned image_path
        k = 0
        r = 0
 
        for i in xrange(h):
            for j in xrange(l):
                mask = masked[i, j]
                if mask == 1:
                    selected = level0
                elif mask == 2:
                    selected = level1
                elif mask == 3:
                    selected = level2
                elif mask == 4:
                    selected = level3
                elif mask == 5:
                    selected = level4
                elif mask == 6:
                    selected = level5
                elif mask == 7:
                    selected = level6
                elif mask == 8:
                    selected = level7
                elif mask == 9:
                    selected = level8
                else:
                    selected = level9
                xs = i + k
                xf = i + k + 3
                ys = j + r
                yf = j + r + 3
 
                rIm[xs:xf, ys:yf] = selected[:,:]
                r = r + 2
            r = 0
            k = k + 2
        return rIm
 
    # try generate halftoned image with original size
    if predown == True:
        im = imresize(im, 1.0 / 3.0)
    if histeq == False:
        retIm = halft(im)
    else:
        equalized = normalize(im)
        equalized = halft(equalized)
        retIm = equalized
        retIm = halft(im)
        retIm = ceil((equalized + retIm) * 0.5)
    return retIm

Os resultados do retorno desta função podem ser vistos abaixo:


Original (parâmetro)

Halftoned (retorno)