Manual
do
Maker
.
com
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.
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.
A comunicação é serial, mas os comandos devem ser enviados em hexadecimal; não uma string hexa, mas um encapsulamento 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
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.
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:
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 320x240 ou 0x02 para devolver a imagem em 160x120. 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.
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)
Apesar de parecer código antigo, trata-se da versão de Python contido na placa, portanto ainda segue o modelo do Python-2.7.
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 canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.