Mostrando postagens com marcador converte. Mostrar todas as postagens
Mostrando postagens com marcador converte. Mostrar todas as postagens

quarta-feira, 22 de novembro de 2017

Detectando Cores

Aqui vamos supor que se deseja converter uma imagem colorida em escala (tons) de cinza, tal qual fizemos anteriormente em Convertendo Imagens para Preto e Branco, em Halftone Floyd-Steinberg e em Halftone Floyd-Steinberg - Falso e Verdadeiro, mas desejamos manter os tons de uma determinada cor na imagem.

Para que seja definido quais cores (tonalidades) serão convertidas para tons de cinza e quais permanecerão na tonalidade escolhida, leva-se em conta um pouco de subjetividade, mas também é preciso calcular a "distância" entre a cor que vamos usar como referência para que sejam mantidos os tons e a cor da imagem original, estipulando uma tolerância (um limiar), pois dependendo da abrangência mínima ou máxima poderão ser atingidas tonalidades próximas a outras cores e essas serão (ou não) afetadas também no processo da conversão, ou seja, cores não desejadas podem ser mantidas coloridas ao invés de convertidas em tons de cinza.

O mais comum para estabelecer a "distância" ou "diferença" entre 2 cores R1G1B1 e R2G2B2 é o uso da fórmula Euclidiana:


Tentando estabelecer uma fórmula que gere tons com maior afinidade com a percepção do olho humano, uma das propostas é o uso de pesos na fórmula Euclidana:


Uma proposta alternativa (CompuPhase) é a de levar em consideração, na fórmula de pesos Euclidiana, o quanto de vermelho há em um determinado tom de cor. Primeiro, calcula-se o nível médio de vermelho entre as cores e depois os deltas RGB entre as cores como função desse nível médio. Ainda persiste a questão subjetiva da escolha da cor de referência a ser comparada com cada tom na imagem, mas os resultados são aceitáveis e com bom desempenho.


Uma possível implementação segue abaixo, onde a função FSbutRGB recebe os parâmetros $Img (imagem colorida), $cor (cor de referência) e $tolerancia (se a diferença das cores for menor que a tolerância, mantém a cor original).
Function FSbutRGB([System.Drawing.Image]$Img, [System.Drawing.Color]$cor, [Int32]$tolerancia) {
    Write-Host -ForegroundColor Green "Convertendo para escala de cinza, com tolerância: $tolerancia"
    Write-Host -ForegroundColor DarkYellow "Será mantida na imagem tons da cor:"$cor.Name
    Write-Host -ForegroundColor Green "Será salvo na mesma pasta um arquivo NomeOriginal_FSbutRGB.jpg"
    [Int32]$p=0
    Foreach($y in (0..($Img.Height-1))) {
        $p++
        If ($p -gt (($Img.Height)*0.05)) { 
            Write-Host -NoNewLine '.'
            $p = 0
        }
        Foreach($x in (0..($Img.Width-1))) {
            $ColorPixel = $Img.GetPixel($x,$y)
            ## Algoritmo Color Metric
            ## https://www.compuphase.com/cmetric.htm
            $rMed = (($ColorPixel.R)+$cor.R)/2
            $deltaR = (($ColorPixel.R)-$cor.R)
            $deltaG = (($ColorPixel.G)-$cor.G)
            $deltaB = (($ColorPixel.B)-$cor.B)
            $deltaC = [math]::Sqrt((2+$rMed/256)*$deltaR*$deltaR+4*$deltaG*$deltaG+(2+(255-$rMed)/256)*$deltaB*$deltaB)
            If ($deltaC -le $tolerancia) {
                $R = $ColorPixel.R
                $G = $ColorPixel.G
                $B = $ColorPixel.B
            }
            Else {
                $R = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
                $G = $R
                $B = $R
            }
            $A = $ColorPixel.A
            $Img.SetPixel($x,$y,[System.Drawing.Color]::FromArgb([Int32]$A,[Int32]$R,[Int32]$G,[Int32]$B))
        } ## $x
    } ## $y
} ## EndFunction FSbutRGB

$Arquivo = New-Object System.Windows.Forms.OpenFileDialog
$Arquivo.filter = "Imagens (*.PNG;*.BMP;*.JPG;*.JPEG;*.GIF)|*.PNG;*.BMP;*.JPG;*.JPEG;*.GIF"
$Arquivo.ShowDialog() | Out-Null
$ArquivoSemExt = ($Arquivo.filename).Substring(0,($Arquivo.filename).Length-4)
$Img = [System.Drawing.Image]::FromFile($Arquivo.filename)

Clear-Host
Write-Host "##############################################"
Write-Host "Arquivo de Imagem:"$Arquivo.filename
Write-Host "##############################################"

FSbutRGB $Img '0x00007F' 100

Write-Host ""
Write-Host -ForegroundColor Yellow "Conversão concluída!"
$Img.Save($ArquivoSemExt+"_FSbutRGB.jpg","JPEG")
$Img.Dispose()


Supondo a seguinte imagem colorida:


No código-exemplo, estamos convertendo essa imagem para tons (escala) de cinza, e gostaríamos de manter a cor de referência cujo valor hexa é 0x00007F, com uma tolerância de valor 100. O resultado é o seguinte:


Utilizando para a cor de referência um valor hexa de 0x004401, mantendo-se a mesma tolerância.


Agora para "pegar" a cor vermelha, através de uma cor de referência, em hexa, 0xB40608 foi preciso ajustar a tolerância para 150.


O caso da cor amarela é mais interessante de ser observado, pois há 3 tons de amarelo na imagem. Um deles próximo à cor verde, outro próximo a cor vermelha e, finalmente, o tom de amarelo que decidimos manter, com uma cor hexa de referência 0xF7D600.

Primeiro experimento com uma tolerância de valor 150, produz o seguinte:


Segundo experimento com uma tolerância de valor 100, produz o seguinte:

No terceiro experimento encontramos um valor de tolerância que atinge o objetivo (70):

quinta-feira, 9 de novembro de 2017

Convertendo Imagens para Preto e Branco

Aqui vamos tirar proveito da habilidade do Powershell em obter as informações de um arquivo de imagem pixel a pixel. Powershell é capaz de obter os atributos ARGB do pixel. Com isso, podemos implementar algoritmos para criar uma imagem em preto e branco, ou melhor dizendo, em tons de cinza, a partir de um arquivo de imagem colorida fornecido.

Implementaremos 3 algoritmos básicos:
  • AVERAGE
  • ## METODO AVERAGE
    ## Compõe cada novo RGB em escala de cinza
    ## Somando-se RGB colorido e dividindo por 3
    $R = (($ColorPixel.R)+($ColorPixel.G)+($ColorPixel.B)) / 3
    $G = (($ColorPixel.R)+($ColorPixel.G)+($ColorPixel.B)) / 3
    $B = (($ColorPixel.R)+($ColorPixel.G)+($ColorPixel.B)) / 3
    

  • LIGHTNESS
  • ## METODO LIGHTNESS
    ## Compõe cada novo RGB em escala de cinza
    ## Calculando: (Max(RGB)+Min(RGB)) / 2
    $R = ((MaxRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B)) + `
                         (MinRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B))) / 2
    $G = ((MaxRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B)) + `
                         (MinRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B))) / 2
    $B = ((MaxRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B)) + `
                         (MinRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B))) / 2
    

  • LUMINOSITY
  • ## METODO LUMINOSITY
    ## Calcula cada novo RGB em escala de cinza
    ## Através de pesos diferentes em cada RGB
    ## R tem peso 0.2155
    ## G tem pseo 0.7154
    ## B tem peso 0.0721
    $R = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
    $G = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
    $B = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
    

O Script completo ficou assim:
Function PretoBranco([System.Drawing.Image]$Img, [String]$Metodo) {
    If ( ($Metodo.ToUpper() -eq "LUMINOSITY") -Or `
        ($Metodo.ToUpper() -eq "AVERAGE") -Or `
        ($Metodo.ToUpper() -eq "LIGHTNESS") ) {
        Write-Host -ForegroundColor Green "Convertendo a imagem para preto e branco..."
        Write-Host -ForegroundColor Green "Será salvo na mesma pasta um arquivo NomeOriginal_PretoBranco"
    }
    Else { 
        Write-Host -ForegroundColor Yellow "$Metodo [Método de conversão especificado não é suportado]"
        Write-Host -ForegroundColor White "Será utilizado o método: [LUMINOSITY]"
        $Metodo = "LUMINOSITY"
        Write-Host -ForegroundColor Green "Convertendo a imagem para preto e branco..."
        Write-Host -ForegroundColor Green "Será salvo na mesma pasta um arquivo NomeOriginal_PretoBranco"
    }
    [Int32]$p=0
    Foreach($y in (1..($Img.Height-1))) {
        $p++
        If ($p -gt (($Img.Height)*0.05)) { 
            Write-Host -NoNewLine '.'
            $p = 0
        }
        Foreach($x in (1..($Img.Width-1))) {
            $ColorPixel = $Img.GetPixel($x,$y)
            ## METODO LUMINOSITY
            If ($Metodo.ToUpper() -eq "LUMINOSITY") {
               $R = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
               $G = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
               $B = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
            }

            ## METODO AVERAGE
            If ($Metodo.ToUpper() -eq "AVERAGE") {
                $R = (($ColorPixel.R)+($ColorPixel.G)+($ColorPixel.B)) / 3
                $G = (($ColorPixel.R)+($ColorPixel.G)+($ColorPixel.B)) / 3
                $B = (($ColorPixel.R)+($ColorPixel.G)+($ColorPixel.B)) / 3
            }

            ## METODO LIGHTNESS
            If ($Metodo.ToUpper() -eq "LIGHTNESS") {
                $R = ((MaxRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B)) + `
                     (MinRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B))) / 2
                $G = ((MaxRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B)) + `
                     (MinRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B))) / 2
                $B = ((MaxRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B)) + `
                     (MinRGB ($ColorPixel.R) ($ColorPixel.G) ($ColorPixel.B))) / 2
            }

            ## Set GrayScale Pixel
            $A = $ColorPixel.A
            $Img.SetPixel($x,$y,[System.Drawing.Color]::FromArgb([Int32]$A,[Int32]$R,[Int32]$G,[Int32]$B))
        } ## ForEach $x
    } ## ForEach $y
} ## EndFunction PretoBranco

Function MaxRGB([Int32]$R, [Int32]$G, [Int32]$B) {
    Return ([math]::Max([math]::Max($R,$G),$B))
}

Function MinRGB([Int32]$R, [Int32]$G, [Int32]$B) {
    Return ([math]::Min([math]::Min($R,$G),$B))
}

$Arquivo = New-Object System.Windows.Forms.OpenFileDialog
$Arquivo.filter = "Imagens (*.PNG;*.BMP;*.JPG;*.GIF)|*.PNG;*.BMP;*.JPG;*.GIF"
$Arquivo.ShowDialog() | Out-Null
$Ext = ($Arquivo.filename).Substring(($Arquivo.filename).Length-3,3)
$ArquivoSemExt = ($Arquivo.filename).Substring(0,($Arquivo.filename).Length-4)
$Img = [System.Drawing.Image]::FromFile($Arquivo.filename)

Clear-Host
Write-Host "##############################################"
Write-Host "Arquivo de Imagem:"$Arquivo.filename
Write-Host "##############################################"

PretoBranco $Img LUMINOSITY

Write-Host ""
Write-Host -ForegroundColor Yellow "Conversão concluída!"
$Img.Save($ArquivoSemExt+"_PretoBranco."+$Ext)
$Img.Dispose()

Abaixo, ilustra-se exemplos de execução com cada método. Observou-se baixo desempenho com o método LIGHTNESS, em decorrência das várias chamadas de função para os cálculos MaxRGB e MinRGB.

IMAGEM COLORIDA 1


MÉTODO LUMINOSITY


MÉTODO AVERAGE


MÉTODO LIGHTNESS


IMAGEM COLORIDA 2


MÉTODO LUMINOSITY


MÉTODO AVERAGE


MÉTODO LIGHTNESS

Decimal para Binário

Este é um post bem simples, onde o objetivo é de apenas ilustrar como poderia ser implementado com o Powershell a conversão de um número decimal para binário através de um função recursiva. Não se quer, com isso, demonstrar o melhor método, nem tão pouco o mais otimizado.

$Global:Bin=""
Function Dec2Bin([Int32]$Num) {
    If ($Num -lt 0) {Return "Número a converter deve ser positivo"}
    If ($Num -lt 2) {Return (($Global:Bin+=$Num)|%{-join $_[$_.Length..0]})}
    $Global:Bin += ($Num % 2)
    If ($Num -eq 2) {Return (($Global:Bin+=1)|%{-join $_[$_.Length..0]})}
    Return Dec2Bin ([math]::Truncate($Num/2))
}

Clear-Host
[Int32]$Dec = Read-Host "Insira um número para converter em binário"
Write-Host "O equivalente em binário de $Dec é:" (Dec2Bin $Dec)

Sabemos que uma maneira de ser realizada a conversão é através dos restos sucessivos da divisão por 2. Por exemplo, na conversão do número 19.


Se bem observarmos na implementação proposta, são utilizadas chamadas recursivas à função Dec2Bin. Ainda (usando uma função recursiva), os valores de resto são empilhados em uma variável global do tipo String. Assim sendo, no teste de parada da recursividade, efetuamos uma inversão da pilha, ou seja, da String que armazena os restos.

Se colocarmos como entrada o número 19 no script, o resultado será:

Insira um número para converter em binário: 19
O equivalente em binário de 19 é: 10011