Código para OMRON HVC em Python

OMRON HVC

Lembra da OMRON? Bem, faz parte da minha atual tarefa compará-lo a outros dispositivos e por acaso ele não possui nenhum código que não para Windows. Pior ainda, por se tratar de um componente para integração com outros sensores biométricos e/ou senhas para se tornar um produto, a comunicação com esse dispositivo é realmente de baixo nível. Mas justamente por ser tão baixo nível resolvi compartilhar o código para poder comentar a respeito. Quem é de baixo nível não verá novidade mas quem ainda não teve esse tipo de contato verá algo novo, que normalmente não temos no dia-a-dia maker.

Folha de comandos da OMRON

Primeiramente, eu não encontrei esse documento na Internet. Quando você compra o produto, recebe um link para fazer o download dos PDFs e dos códigos de exemplo (tudo pra Windows em Visual Studio). Estou disponibilizando os comandos mas infelizmente não posso disponibilizar o documento porque está escrito “confidencial” e não lí nada a respeito do acordo de confidencialidade, mas se não tem esse documento publico na Internet, certamente não é aberto.

Os comandos que implementei foram somente os necessários para o meu teste. Tem mais uma série deles, mas irrelevantes para meu propósito.

Comunicação com o dispositivo OMRON

A comunicação é serial, mas os comandos devem ser enviados em hexadecimal; não uma string hexa, mas um emcapsulamento que em Python é feito com o módulo struct.

Para fazer a comunicação serial em Python, deve-se instalar o python-serial. Por fim, para visualizar algumas saídas sem ter que fazer o caminho reverso, instale o hexdump para Python:

sudo apt-get install python-serial
sudo pip install hexdump

Código para o OMRON HVC





Vou mostrar apenas algumas partes do documento pra poder exclarecer como os comandos são montados e vou exemplificar alguns trechos. Se tudo o que você quer é o código, pegue-o completo ao final desse post. Mas eu recomendo a leitura.

A primeira parte é fazer os devidos imports e estabelecer a comunicação serial que no caso, deve ser um dispositivo identificado como “/dev/ttyUSB0”, se não houver outro conectado à sua máquina:

import serial
import struct
import hexdump
import os
import sys
import time

conn = serial.Serial(port='/dev/ttyUSB0',baudrate=921600,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=5)

Essa conexão serial é a mesma que você faz com um Arduino, ESP8266 etc. A diferença é que você não tem uma interface gráfica para fazer a interação. O segundo ponto de diferenciação aqui é a velocidade. Repare que esse dispositivo tem um baud rate que vai até 921600, mas claro, ele precisa estar configurado para suportar isso. Para tal, ele possui 4 seletores que representam 4 bits e conforme a conbinação, vai de 9600 até 921600.

Exemplo de código para fazer face detection

O OMRON é um dispositivo passivo, de modo que seu programa precisa estar em execução continuamente para auto-detectar faces ou então estar ligado a um trigger para fazer detecção apenas em determinado momento. Essas características são implementadas pela empresa que integra o OMRON em seu hardware. O programa que escrevi apenas executa conforme a chamada feita por linha de comando shell. Veja a seguinte página do documento para o face detection:

Especificação de comandos

Especificação de comandos

Comando

Como você pode notar, o cabeçalho indica 4 partes, como uma barra de chocolate que tem seus quadradinhos. Olhe onde está escrito “Command (Host->HVC-P)”. Ele diz que o cabeçalho de envio deve conter o “synchronous code”, o “Command number”, o “Data length” e os dados. Vejamos; o syncCode já sabemos, o número do comando para executar detecção também. O comprimento dos dados é de 3 hexadecimais. Então, no campo data deveremos colocar esses 3 hexas. Mas quais são eles? Repare que em “Data” está escrito “Por favor, veja abaixo para detalhes”. Um pouco mais abaixo da página você encontra “Command Data” e lá está especificado cada um dos bits de cada Byte. Como o intuito é fazer face detection, qual bit deve ser levantado? – Exato, o bit 2, que representa o valor hexa 0x04. Vamos para o segundo Byte

Ora, veja. No segundo Byte não queri usar nada. É só a detecção que me interessa, portanto, o segundo Byte dos dados é 0x00. Agora o terceiro Byte, que pode ser apenas 0x00 para não devolver a imagem, 0x01 para devolver a imagem em formato 320×240 ou 0x02 para devolver a imagem em 160×120. Para não ficar complicado, vamos ignorar a imagem, portanto, mais uma vez 0x00.

Agora vamos montar o comando completo. Mas primeiro, uma coisa precisa ser considerada; não basta escrever os hexadecimais na serial porque eles não passarão de caracteres ASCII. O comando precisa ser enviado no modo binário e para fazermos isso no Python, utilizamos o struct, importado no início do código. Pra finalizar a explicação dessa linha, o início do método struct.pack possui um campo ‘<7B’, que significa “7 Bytes alinhados à esquerda”. Lembrando: FEh, 04h, 03h, 00h, 04h, 00h, 00h – dando o total de 7 Bytes.

comm = struct.pack('<7B', 0xfe, 0x04, 0x03, 0x00, 0x04, 0x00, 0x00)

Agora precisamos ler a resposta.

Resposta





Mais uma vez no início da página, em verde temos o formato da resposta. Mais uma fez o SyncCode é FEh e o código de saída normal é 00h. Ponto. O comprimento dos dados pode ser de até 04h. Não poderei explicar mais detalhes porque a área de dados está em outra página do documento, mas adianto que do mesmo modo que no envio, selecionei apenas o valor que corresponde ao face detection. Lembra aquela analogia boba com o chocolate lá em cima do texto? Agora vamos dividir esse chocolate:

syncCode,respCode,dataLen = struct.unpack_from('<BBl',header,0)

Desse modo temos o syncCode em uma variável, respCode em outra e o dataLen em outra. Não se preocupe, todos os detalhes você poderá contemplar no código adiante. Nessa linha estamos fazendo o unpack da resposta binária. O valor ‘<BBl’ significa 2 Bytes e um long (positivo ou negativo) alinhados à esquerda. O header é a leitura da resposta de escrita. O dataLen pode variar, por isso pegamos o tamanho. Agora faz-se uma segunda leitura para obter os valores que importam:

cursor = 0

myData = conn.read(dataLen)
numBodies, numHands, numFaces = struct.unpack_from('<bbb', myData, cursor)

O que queremos é o número de faces detectadas, que deve ser no mínimo 1, de outro modo, 0. A variável numFaces guarda esse valor, de resto é código comum. Baseado nisso, você já conseguirá interpretar razoavelmente o que significa cada função dentro desse código:

import serial
import struct
import hexdump
import os
import sys
import time

conn = serial.Serial(port='/dev/ttyUSB0',baudrate=921600,parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=5)

myInput = 1
answer  = ''

#formata a area de gravacao de dados de usuarios
def reformatFlashROM():
    #pagina 52
    comm = struct.pack('<4B',0xfe,0x22,0x00,0x00)
    conn.write(comm)

    header = conn.read(6)
    syncCode,respCode,dataLen = struct.unpack_from('<2BL',header,0)
    if syncCode != 0xfe:
        print "erro"
        return
    if not respCode == 0x00:
        print "erro"
        return
    print "Feito."

#carregar album para o OMRON
def loadAlbum(filePath):
    #pagina 51
    f       = open(filePath,'rb')
    album   = f.read()
    dataLen = len(album)
    f.close()

    print "album len %d" % dataLen

    comm  = struct.pack('<4Bl',0xfe,0x21,0x04,0x00,dataLen)
    conn.write(comm)
    conn.write(album)

#salvar album na ROM
def saveAlbum():
    #pagina 50
    comm = struct.pack('4B',0xfe,0x20,0x00,0x00)
    conn.write(comm)

    header = conn.read(6)
    hexdump.hexdump(header)

    syncCode, respCode, dataLen = struct.unpack_from('<BBl', header, 0)
    if syncCode != 0xfe:
        print "erro"
        return
    if not respCode == 0x00:
        print "erro"
        return

    myData = conn.read(dataLen)
    with open('album.hex', 'wb') as out:
        out.write(myData)

#timestamp para nome
def ts2name():
    ts = long(time.time())
    strTS = str(ts)
    return strTS

#excluir foto ou usuario
def delete(userId,dataId):
    if dataId == "completo":
        for k in range(0,5):
            try:
                comm = struct.pack('<4BHB', 0xfe, 0x11, 0x03, 0x00, int(userId), k)
                conn.write(comm)
                header = conn.read(6)
                hexdump.hexdump(header)
            except:
                print
                print "Feito ate: ", k
                return
        print "Feito"
        return

    comm = struct.pack('<4BHB', 0xfe, 0x11, 0x03, 0x00, int(userId), int(dataId))
    conn.write(comm)
    header = conn.read(6)
    hexdump.hexdump(header)

#registar uma pessoa
def registerData(userId):
    #TODO: 5 mais se tiver oculos. tratar erro 01 e 02 (falta ou excesso de face(s))
    setCameraAngle()
    #pagina 45
    #fe 10 0300 | retorno ok = 00 - Erro = 01,02, ff a c0
    #dados:
    # uid, uid, dado. Ex:
    #9001 05
    position = ["baixa","alta","esquerda","direita","frente"]
    for k in range(0,5):
        print "Posicao da cabeca: ", position[k]
        print "(aperte [Enter] quando estiver pronto)"
        null = raw_input()
        comm = struct.pack('<4BHB',0xfe,0x10,0x03,0x00, int(userId), k)
        conn.write(comm)
        header = conn.read(6)
        #hexdump.hexdump(header)
        syncCode, respCode, dataLen = struct.unpack_from('<BBl', header, 0)
        if syncCode != 0xfe:
            print "erro"
            exit()
        if respCode >= 0xc0:
            print "erro"
            exit()
        if respCode == 0x01:
            print "Face nao identificada. Reinicie o processo e nao exagere nas posicoes."
            print "Saindo..."
            exit()

        myData = conn.read(dataLen)
        width, height = struct.unpack_from('<2h', myData, 0)
        print "Imagem %dx%d" % (width, height)
        with open('img.hex', 'wb') as out:
            out.write(myData[4:])

        # imagem: userId-dataId.png
        filename = userId + str(k) + ".png"
        os.system("convert -size 64x64 -depth 8 gray:img.hex %s" % filename)

#Salvar o album na ROM do dispositivo
def saveAlbumInFlashROM():
    comm = struct.pack('<4B',0xfe,0x22,0x00,0x00)
    conn.write(comm)
    answer = conn.read(6)
    syncCode, respCode = struct.unpack_from('<2B',answer,0)
    if syncCode != 0xfe:
        print "erro"
    if respCode >= 0xc0:
        print "erro"

#vira a camera para ela ficar com o cabo usb para baixo
def setCameraAngle():
    #pagina 29
    #fe 01 0100 | retorno ok = 00 - erro = ff a c0
    #usb para baixo = 270 graus = 03h
    #comando: fe 01 0100 03
    comm = struct.pack('<5B',0xfe,0x01,0x01,0x00,0x03)
    conn.write(comm)
    answer = conn.read(6)
    syncCode, respCode = struct.unpack_from('<2B',answer,0)
    if syncCode != 0xfe:
        print "erro"
    if respCode >= 0xc0:
        print "erro"

# face detection
def executeDetection():
    # pagina 32
    setCameraAngle()
    comm = struct.pack('<7B', 0xfe, 0x04, 0x03, 0x00, 0x04, 0x00, 0x00)

    conn.write(comm)

    header = conn.read(6)
    syncCode,respCode,dataLen = struct.unpack_from('<BBl',header,0)
    if syncCode != 0xfe:
        print "erro 167"
        return
    if respCode >= 0xc0:
        print "erro 170"
        return

    cursor = 0

    myData = conn.read(dataLen)
    numBodies, numHands, numFaces = struct.unpack_from('<bbb', myData, cursor)
    #print [numBodies, numHands, numFaces]
    number_of_faces = ""
    img_name        = ""
    event_from      = ""
    cursor += 4
    if numFaces > 0:
        print "Deteccao facial %d" % (numFaces)
        number_of_faces = str(numFaces) + "|"
        img_name        = ts2name() + "|"
        event_from      = "face detection"

    f = open("omron.log", "a")
    f.write(number_of_faces + img_name + event_from + "\n")
    f.close()

    faceRecognition(1,img_name)



#fazer reconhecimento facial
def faceRecognition(img=0,img_ts=0):
    setCameraAngle()
    comm = struct.pack('<7B', 0xfe, 0x04, 0x03, 0x00, 0x04, 0x02, 0x00)
    if img:
        comm = struct.pack('<7B', 0xfe, 0x04, 0x03, 0x00, 0x04, 0x02, 0x02)

    conn.write(comm)

    header = conn.read(6)
    syncCode, respCode, dataLen = struct.unpack_from('<BBl',header,0)
    if syncCode != 0xfe:
        print "erro 180"
        return
    if respCode >= 0xc0:
        print "erro 183"
        return

    cursor = 0

    myData = conn.read(dataLen)
    numBodies,   numHands, numFaces = struct.unpack_from('<bbb', myData, cursor)
    #print [numBodies, numHands, numFaces]

    number_of_faces = str(numFaces) + "|"

    cursor += 4

    user_id         = "["
    user_confidence = ""
    user_score      = ""
    event_from      = "recognition"

    for face in range(numFaces):
        x, y, sz, cf, userId, score = struct.unpack_from('<6h', myData, cursor)
        cursor += 12
        print "Reconhecimento facial %d" % (face+1)
        print "\tx: %d, y: %d, size: %d, confidence: %.1f%%, user id: %d, score: %d" % (x, y, sz, float(cf)/10, userId, score)

        user_id         += str(userId) + "|"
        user_confidence += str(float(cf)/10) + "|"
        user_score      += str(score) + "]"

    if cursor < dataLen:
        width, height = struct.unpack_from('<2h', myData, cursor)
        cursor += 4
        print "Imagem %dx%d" % (width, height)
        with open('img.hex', 'wb') as out:
            out.write(myData[cursor:])

        filename = img_ts
        if img_ts == "0":
            filename = ts2name()
            "Criando nome de arquivo..."
        fname    = "|" + filename

        print filename,type(filename)
        os.system("convert -size %dx%d -depth 8 gray:img.hex %s.png" % (width, height,filename.replace("|","")))
        time.sleep(0.8)
        os.remove("img.hex")

        f = open("omron.log","a")
        f.write(number_of_faces+user_id+user_confidence+user_score+fname+event_from+"\n")
        f.close()

#apenas para ver se recebe a resposta "HVC-P"
def hello():
    comm   = struct.pack('<4B', 0xfe, 0x00, 0x00, 0x00)
    conn.write(comm)
    header = conn.read(6)
    syncCode, responseCode, dataLength = struct.unpack('<bbl', header)
    if syncCode != -2 or responseCode != 0 or dataLength == 0:
        print 'Error in response'
        exit(0)

    data = conn.read(dataLength)
    print "Resposta: "
    hexdump.hexdump(data)

if len(sys.argv) > 1:
    if sys.argv[1] == "teste":
        hello()

    elif sys.argv[1] == "detectar":
        executeDetection()

    elif sys.argv[1] == "carregar":
        if not len(sys.argv) == 3:
            print "Indique o arquivo. Saindo..."
            exit()
        if not os.path.isfile(sys.argv[2]):
            print "Indique o arquivo corretamente. Saindo..."
            exit()
            loadAlbum(sys.argv[2])

    elif sys.argv[1] == "formatar":
        reformatFlashROM()

    elif sys.argv[1] == "reconhecer":
        faceRecognition(1,"0")

    elif sys.argv[1] == "salvar":
        saveAlbumInFlashROM()

    elif sys.argv[1] == "cadastrar":
        if not len(sys.argv) == 3:
            print "Passe o ID para criar o usuario. Saindo..."
            exit()

        if not str(sys.argv[2]).isdigit():
            print "O ID deve ser numerico. Saindo..."

        registerData(sys.argv[2])

    elif sys.argv[1] == "excluir":
        if len(sys.argv) < 4:
            print "Comando: ", sys.argv[0], " UID IMG"
            print "Ex.: ", sys.argv[0], " 1 0"
            print "ou: ", sys.argv[0], " 1 completo"
            exit()

        delete(sys.argv[2], sys.argv[3])

    elif sys.argv[1] == "album":
        saveAlbum()

    elif sys.argv[1] == "help" or sys.argv[1] == "ajuda":
        print "Opcoes:"
        print "teste      - Para testar o funcionamento"
        print "reconhecer - Para fazer o reconhecimento"
        print "detectar   - Faz deteccao facial  apenas"
        print "salvar     - Para salvar permanentemente"
        print "album      - Para salvar o album no host"
        print "cadastrar  - Para cadastrar  um  usuario"
        print "excluir    - Para excluir um  ja usuario"
        print "ajuda/help - Ambos exibem esse menu help"
        print "formatar   - Formatar a area de usuarios"
        print "carregar   - Envia album  ao dispositivo"
        print "help seguido do comando,  para  detalhar"
        print "Ex.: ",sys.argv[0], " help excluir"

        if len(sys.argv) == 3:
            print "Nao implementado ainda."

Para executar, apenas chame o script seguido do parâmetro “help” para ver o menu, então execute o script novamente com a execução pretendida. Repare que se for executado o face detection do exemplo acima, em seguida ele tenta fazer o face recognition, caso contrário, simplesmente sai. Legal, hum?

Inscreva-se no nosso newsletter, alí em cima à direita e receba novos posts por email.

Siga-nos no Do bit Ao Byte no Facebook.

Prefere twitter? @DobitAoByte.

Inscreva-se no nosso canal Do bit Ao Byte Brasil no YouTube.

Nossos grupos:

Arduino BR – https://www.facebook.com/groups/microcontroladorarduinobr/
Raspberry Pi BR – https://www.facebook.com/groups/raspberrybr/
Orange Pi BR – https://www.facebook.com/groups/OrangePiBR/
Odroid BR – https://www.facebook.com/groups/odroidBR/
Sistemas Embarcados BR – https://www.facebook.com/groups/SistemasEmbarcadosBR/
MIPS BR – https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte – https://www.facebook.com/groups/dobitaobyte/

Próximo post a caminho!

Deixe uma resposta