sábado, 29 de diciembre de 2018

DEMOSTRACION DE COLORES RGB


EXTRACTOR DE CANALES RGB




Este es un programa para extraer los canales RGB de una imagen. El prorgama genera las imagenes de cada canal y las guarda como imagenes de mapa de bits (.bmp) además genera las matrices de intensidad de color y los guarda como  archivos separados por coma (.csv).


Este programa fué probado en windows 7 con python 3.7
Para correrlo debe tener instaladas las librerías conocidas como pil y wxpython.

Copie el código fuente mostrado más abajo

Si le interesa saber más sobre adquisición de imagenes digitales, visite mi otro post: 

ADQUISICION DE UNA IAGEN DIGITAL


Aplicacion: Carga la imagen y genera los resultados mostrados
 
Puede usar mi otro programa para convertir el script de python en ejecutable y poder usar el ejecutable en otras máquinas sin tener que instalar de nuevo python y todas las demás dependencias:
 

from PIL import Image
import wx
import os

class MainFrame(wx.Frame):   
    def __init__(self, parent, title ="Extractor"):
        super().__init__(parent, title = title , size = (500,130))
        self.locale = wx.Locale(wx.LANGUAGE_ENGLISH) # Arreglar el error de locale
        self.SetMinSize(size = (500,130))            # Tamaño minimo de la ventana
        # ----------------------------------
        #             VARIABLES
        #-----------------------------------
        self.ImageDir = ""
        self.SaveDir  = ""
        self.currentDirectory = os.getcwd()
        # ----------------------------------
        #             WIDGETS
        #-----------------------------------
        MainPanel  = wx.Panel(self)
        
        BtnOpenImg = wx.Button(MainPanel, wx.ID_ANY, 'Abrir Img', wx.DefaultPosition)
        self.TxtDirOpen = wx.TextCtrl(MainPanel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirOpen.SetEditable(False)
        SizrSlecImg = wx.BoxSizer(wx.HORIZONTAL)
        SizrSlecImg.Add(BtnOpenImg,     0, wx.ALL,2)
        SizrSlecImg.Add(self.TxtDirOpen,1, wx.ALL,2)

        BtnSaveDir = wx.Button(MainPanel, wx.ID_ANY, 'Guardar en', wx.DefaultPosition)
        self.TxtDirSave = wx.TextCtrl(MainPanel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirSave.SetEditable(False)
        SizSaveDir = wx.BoxSizer(wx.HORIZONTAL)
        SizSaveDir.Add(BtnSaveDir,     0, wx.ALL,2)
        SizSaveDir.Add(self.TxtDirSave,1, wx.ALL,2)
        
        
        BtnProcesar = wx.Button(MainPanel, wx.ID_ANY, 'Procesar', wx.DefaultPosition)
        #-----------------------------------
        #             BINDS
        #-----------------------------------
        BtnOpenImg .Bind(wx.EVT_BUTTON, self.SelecIMG,BtnOpenImg )
        BtnProcesar.Bind(wx.EVT_BUTTON, self.procesar,BtnProcesar)
        BtnSaveDir .Bind(wx.EVT_BUTTON, self.savedir, BtnSaveDir )
        #-----------------------------------
        #             LAYOUT
        #-----------------------------------
        MainSizer   = wx.BoxSizer(wx.VERTICAL)
        
        MainSizer.Add(SizrSlecImg,0, wx.ALL|wx.EXPAND)
        MainSizer.Add(SizSaveDir, 0, wx.ALL|wx.EXPAND)
        MainSizer.Add(BtnProcesar,0, wx.ALL|wx.EXPAND)
        MainPanel.SetSizer(MainSizer)
        MainPanel.Layout()

    def SelecIMG(self, event):
        wildcard = "jpg, png, tiff o bmp|*.jpg;*.png;*.tiff;*.bmp"

        dlg = wx.FileDialog(
            self, message="Abrir archivo de imagen",
            defaultDir=self.currentDirectory, 
            defaultFile="",
            wildcard=wildcard,
            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)

        if dlg.ShowModal() == wx.ID_OK:
            self.ImageDir = dlg.GetPath()
            self.TxtDirOpen.SetValue(self.ImageDir)
        dlg.Destroy()

    def savedir(self, event):
        dlg = wx.DirDialog(self, "Elija el directorio:",
                           style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.SaveDir = dlg.GetPath()
            self.TxtDirSave.SetValue(self.SaveDir)
        dlg.Destroy()
    
    def procesar(self, event):
        #-----------------------------------
        #     COMPROBAR CAMPOS REQUERIDOS
        #-----------------------------------
        continuar = True
        mensajes = ''
        if self.ImageDir == '':
            continuar = False
            mensajes+= '* No ha elegido la imagen.\n'
        if self.SaveDir == '':
            continuar = False
            mensajes+= '* No ha elegido donde guardar los archivos resultantes.\n'
        #-----------------------------------
        if continuar == True:
            #-----------------------------------
            #         GENERAR ARCHIVOS
            #-----------------------------------
            try:
                self.ExtraerCnal('R')
                self.ExtraerCnal('G')
                self.ExtraerCnal('B')
                self.ExtraerCnalenIMG('R')
                self.ExtraerCnalenIMG('G')
                self.ExtraerCnalenIMG('B')
            except:
                mensaje = 'Ocurrieron errores al crearlos archivos\n'\
                          'la imagen podría estar dañada.'
                wx.MessageBox(message=mensaje,
                        caption='Aviso', style=wx.OK | wx.ICON_INFORMATION)     
        else:
            wx.MessageBox(message = mensajes,
                        caption='Aviso', style=wx.OK | wx.ICON_INFORMATION)
    
    def ExtraerCnal (self, color):
        im = Image.open(self.ImageDir) # Puede ser jpg, png, tiff, gif
        pix = im.load()
        size = im.size  # Obtener el manaño de la imagen para iterar
        #print(size)
        filename = ''
        chanel = 0
        if color == 'R':
            filename = self.SaveDir+'\Rojo.csv'
            chanel = 0
        if color == 'G':
            filename = self.SaveDir+'\Verde.csv'
            chanel = 1
        if color == 'B':
            filename = self.SaveDir+'\Azul.csv'
            chanel = 2  
        file = open(filename,"w")
        Line = ""
        for x in range(size[1]):
            if Line != '':
                file.write(Line+"\n")
                #print(Line) # esta linea es para debugear
                Line = ""
            for y in range(size[0]):
                Line +=  str(pix[y,x][chanel])+','  # 0 = R, 1 = G, 2 = B 
        file.close() 
        print('TERMINADO')


    def ExtraerCnalenIMG (self, color):
        im = Image.open(self.ImageDir) # Puede ser jpg, png, tiff, gif
        pix = im.load()
        size = im.size  # Obtener el manaño de la imagen para iterar
        #print(size)
        filename = ''
        chanel = 0
        if color == 'R':
            filename = self.SaveDir+'\Rojo.bmp'
            chanel = 0
        if color == 'G':
            filename = self.SaveDir+'\Verde.bmp'
            chanel = 1
        if color == 'B':
            filename = self.SaveDir+'\Azul.bmp'
            chanel = 2 
        for x in range(size[1]):
            for y in range(size[0]):
                extrColor = pix[y,x][chanel]  # 0 = R, 1 = G, 2 = B
                if color == 'R':
                    newcolor = (extrColor,0,0)
                if color == 'G':
                    newcolor = (0,extrColor,0)
                if color == 'B':
                    newcolor = (0,0,extrColor)
                pix[y,x]  = newcolor
        im.save(filename)
        print('TERMINADO')

app = wx.App(0)
frame = MainFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()

VISOR DE PIXELES RGB

Este otro programa muestra cada color de pixel en una cuadricula. Tal como se vería cada LED RGB si estuviésemos muy cerca del monitor. Se distingue la imagen original al alejarse del monitor. Se recomienda cargar imágenes pequeñas (menos de 200 X 200 pixeles) para que no tarde demasiado.


Copie el código fuente mostrado más abajo y corralo en el IDE de python (IDLE)

Puede usar estas imágenes que re-dimensione para este ejemplo.





#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#------------------------------------------------------
#  Desarrollado por: Carlos Alberto Rodriguez Martinez
#  http://mecatroncharly.blogspot.com/
#
#  Este es un Demuestra la composicion de colores RGB
#------------------------------------------------------
import wx
import os
import wx.grid as gridlib
from PIL import Image

class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, parent=None, title="Visor de pixeles RGB")
        # ----------------------------------
        #             VARIABLES
        #-----------------------------------
        self.ImageDir = ""
        self.currentDirectory = os.getcwd()
        self.image   = None
        self.Pixels  = None
        self.ImgSize = None
        # ----------------------------------
        #             GUI
        #-----------------------------------
        MainPanel = wx.Panel(self)

        BtnProcesar = wx.Button(MainPanel, wx.ID_ANY, 'Procesar', wx.DefaultPosition)

        BtnOpenImg = wx.Button(MainPanel, wx.ID_ANY, 'Abrir Img', wx.DefaultPosition)
        self.TxtDirOpen = wx.TextCtrl(MainPanel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirOpen.SetEditable(False)
        SizrSlecImg = wx.BoxSizer(wx.HORIZONTAL)
        SizrSlecImg.Add(BtnOpenImg,     0, wx.ALL,2)
        SizrSlecImg.Add(self.TxtDirOpen,1, wx.ALL,2)

        self.ChkBxGrdLnes = wx.CheckBox(MainPanel, label = 'Mostrar lineas en la cuadricula')
        self.ChkBxGrdLnes.SetValue(True)
 
        self.MyGrid = gridlib.Grid(MainPanel)
        self.MyGrid.CreateGrid(0, 0)

        self.MyGrid.SetRowLabelSize(0) # hide the rows
        self.MyGrid.SetColLabelSize(0) # hide the columns
        #-----------------------------------
        #             BINDS
        #-----------------------------------
        BtnOpenImg       .Bind(wx.EVT_BUTTON, self.SelecIMG,BtnOpenImg )
        BtnProcesar      .Bind(wx.EVT_BUTTON, self.procesar,BtnProcesar)
        self.ChkBxGrdLnes.Bind(wx.EVT_CHECKBOX,self.onShowLines) 
        #-----------------------------------
        #             LAYOUT
        #-----------------------------------
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(SizrSlecImg,      0, wx.EXPAND|wx.ALL,5)
        sizer.Add(self.ChkBxGrdLnes,0, wx.EXPAND|wx.ALL,5)
        sizer.Add(BtnProcesar,      0, wx.EXPAND|wx.ALL,5)
        sizer.Add(self.MyGrid,      1, wx.EXPAND|wx.ALL,5)
        MainPanel.SetSizer(sizer)

        infoTxt = 'Cargue imagenes pequeñas no más ' \
                  'de 200 X 200 pixeles. De lo contrario' \
                  'el programa podria demorarse o bloquearse.'
        wx.MessageBox(infoTxt, 'Info', wx.OK | wx.ICON_INFORMATION)

    def resetGrid(self):
        Cols = self.MyGrid.GetNumberCols()
        Rows = self.MyGrid.GetNumberRows()
        if Cols > 0:
            self.MyGrid.DeleteCols(0,Cols)
        if Rows > 0:
            self.MyGrid.DeleteRows(0,Rows)

    def onShowLines(self, event):
        checkbox = event.GetEventObject()
        if checkbox.GetValue():
            self.MyGrid.EnableGridLines(True)
        else:
            self.MyGrid.EnableGridLines(False)

    def procesar(self, event):
        if self.ImageDir != "":
            bi = wx.BusyInfo("Procesandio, por favor espere", self)
            self.resetGrid()
            #-------------------------------------------------
            #     Preparar la cuadricula para la imagen
            #-------------------------------------------------
            #self.MyGrid.EnableGridLines(False)
            self.MyGrid.SetRowLabelSize(0) #esconder indices
            self.MyGrid.SetColLabelSize(0) #esconder indices
        
            sizeX = self.ImgSize[0]*3 #Ancho = Numero de pixelesX * 3
            sizeY = self.ImgSize[1]   #Largo = Numero de pixelesY

            self.MyGrid.AppendCols(sizeX) # Agregar columnas Nesesarias
            self.MyGrid.AppendRows(sizeY) # Agregar Filas Nesesarias
            #-------------------------------------------------
            #   Ajustar tamano de celdas para conservar
            #   la relacion de aspecto
            #-------------------------------------------------
            for x in range(sizeX):              #ajustar tamano de Filas a 15 px
                self.MyGrid.SetColSize(x,15)

            for x in range(sizeY):              #ajustar tamano de Columnas a 15px * 3 = 45px
                self.MyGrid.SetRowSize(x,45)
            #-------------------------------------------------
            #   Colorear las celdas dependiendo del
            #   Valor RGB de cada pixel
            #-------------------------------------------------
            for x in range(self.ImgSize[0]):
                for y in range(sizeY):
                
                    color = wx.Colour(self.Pixels[x,y][0], 0, 0) 
                    self.MyGrid.SetCellBackgroundColour(y,x*3,color)

                    color = wx.Colour(0, self.Pixels[x,y][1], 0) 
                    self.MyGrid.SetCellBackgroundColour(y,(x*3)+1,color)

                    color = wx.Colour(0, 0,self.Pixels[x,y][2]) 
                    self.MyGrid.SetCellBackgroundColour(y,(x*3)+2,color)
            del bi
        else:
            wx.MessageBox("No has elegido ninguna imagen.", 'Info', wx.OK | wx.ICON_INFORMATION)
        
    def SelecIMG(self, event):
        wildcard = "jpg, png, tiff o bmp|*.jpg;*.png;*.tiff;*.bmp"

        dlg = wx.FileDialog(
            self, message="Abrir archivo de imagen",
            defaultDir=self.currentDirectory, 
            defaultFile="",
            wildcard=wildcard,
            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)

        if dlg.ShowModal() == wx.ID_OK:
            self.ImageDir = dlg.GetPath()
            self.TxtDirOpen.SetValue(self.ImageDir)

            self.image = Image.open(self.ImageDir) # Puede ser jpg, png, tiff, gif
            self.Pixels  = self.image.load()
            self.ImgSize = self.image.size  # Obtener el manaño de la imagen para iterar
        dlg.Destroy()
 
if __name__ == "__main__":
    app = wx.App()
    frame = MyForm().Show()
    app.MainLoop()

martes, 25 de diciembre de 2018

ADQUISICIÓN DE UNA IMAGEN DIGITAL


Todos conocemos y usamos comúnmente las imágenes digitales en nuestra computadora, teléfono o cámara fotográfica, parecería muy obvio definir lo que es una imagen digital, sin embargo, científicamente una imagen digital es un tanto más complejo que una simple fotografía. Por definición, se podría decir que una imagen digital es una representación en 2D de una realidad análoga en 3D. 

El proceso de crear una imagen digital es muy similar a la forma en que nuestros ojos y cerebro perciben e interpretan nuestro entorno. En este artículo trataré de explicar este proceso de la forma más simple posible. Comenzaremos por ver a grandes rasgos las similitudes que tiene una cámara digital con el ojo humano. 

El ojo humano tiene una pequeña apertura lladada pupila, por donde pasan los rayos de luz hacia el cristalino, el cual dispersa los rayos de luz y los proyecta hacia la retina, coroides y demás sensores orgánicos, quienes captan la información y la mandan hacia el cerebro a través del nervio óptico. La información se procesa en el cerebro, quien a su vez corrige la orientación de la imagen, debido a que, al pasar los rayos de luz por el cristalino, cambia a dirección los rayos , poniendo la imagen de cabeza como muestra la ilustración de abajo en el inciso A. El cerebro después convierte la imagen en información útil como: noción del espacio, reconocimiento de objetos, distinción de tonalidades de color etc. 




Por su parte, una cámara digital (inciso B) tiene su obturador, quien cumpliría la función de la pupila, su lente convexo sería como el cristalino en el ojo humano, la matriz de foto celdas sería como la retina, coroides y demás, el nervio óptico serían los cables o uniones con soldadura hacia el procesador, quien sería como el cerebro. En el procesador se corrige la orientación de la imagen y se lleva a cabo un proceso llamado cuantización, donde se convierte la señal análoga captada por la matriz de foto celdas, en números con base en binario de acuerdo a una escala de niveles de intensidad pre-establecidos llamados niveles de cuantización. Estos niveles de cuantización son comúnmente de 0 a 255 (8 bits de profundidad), hablare sobre la cuantización en otro post.

La matriz de foto celdas capta las intensidades de luz en la forma de una combinación de 3 colores primarios: rojo, verde y azul referidos comúnmente como RGB por sus siglas en ingles. Recuerdas cuando eras niño o niña y mezclabas tus acuarelas para crear nuevos colores, pues esto es igual, pero con intensidades de luz. Los monitores de televisión, de hecho, tienen estos puntos de 3 colores a lo largo y ancho, si te acercas mucho a la pantalla, tal vez con una lupa, alcances a distinguir un montón de leds RGB diminutos. Cada punto RGB que conforma la imagen, se almacena digitalmente como un trio de números enteros, y se dice cada punto RGB es un elemento de la imagen o un pixel (acrónimo en ingles de picture element).


Esta mezcla de intensidades hace posible la representación de los colores de una escena real. Entonces una imagen digital podría decirse que es la composición de 3 matrices de intensidad de rojo, verde y azul. Si tomamos el fragmento marcado en la primera imagen de abajo, y hacemos coincidir cada pixel con cada punto RGB de un monitor (sin repetir pixeles debido al aumento de tamaño), se vería claramente la composicion de las 3 matrices (segunda imagen de abajo). En la imagen siguiente se muestran las matrices de color de los canales R, G y B por separado,  donde se puede ver la intensidad de cada punto, pero también se puede leer el valor numérico de intensidad tal como como lo haría la computadora.



Sabiendo que un pixel tiene 3 colores, y si la imagen se cuantiza a 8 bits (cada canal puede tener una intensidad de 0 a 255), las posibles combinaciones de color que se pueden representar en un solo pixel son 256^3 = 16,777,216 posibilidades.

A continuación pongo un enlace hacia unos programas donde demuestro lo que comento arriba.


¿Qué hay de las imágenes en escala de grises?.
Las imágenes en escala de grises podrian ser RGB o tener una sola matriz de color,  dependiendo del formato en que se graben. Si una imagen a escala de grises se graba en un formato especial, podria ocupar una sola matriz de color, pero al momento de mostrarlas en un monitor, se debe hacer una conversión a RGB. La ventaja es que ocupa menos espacio en memoria. Sin embargo si se graba com RGB, el resultado visualmente es el mismo, pero en este formato ocupa mas espacio en memoria.
Las imágenes grabadas en escala de grises no son tan comunes y se usan más bien para aplicaciones de visión por computadora.

Hay varios parámetros a considerar a la hora de elegir una cámara de visión, para que la imagen resulte nítida: el enfoque, el campo de visión o field of view (FOV por sus siglas en ingles) y la distancia focal (f) de la lente, el radio (r) de la lente, y el tamaño de la matriz de foto celdas (d), el índice de refracción del lente (n). 



La acción de enfocar según la real academia de la lengua española se refiere a hacer coincidir los rayos de luz reflejados a través de una lente en un plano focal (matriz de foto celdas) de modo que la imagen sea nítida. Las fórmulas que relacionan estos parámetros son las siguientes:
$$f = \dfrac{r}{2(n-1)}$$

$$FOV_{MAX} = 2 tan^{-1} \frac{d}{2f}$$


De estas fórmulas se puede deducir que, Cuanto mayor sea el radio de la lente mayor será el campo de visión, pero la distancia dentro de la cual se puede enfocar una imagen será menor. Por otro lado, el campo de visión máximo de una cámara se ve limitado por el tamaño de la matriz de foto celdas.Se debe encontrar un balance de estos parametros, entre otras cosas, para elegir la camara adecuada para la aplicación.