Acesso multi-processamento ao Intel Neural Computer Stick via REST

Problema de tarefa única


Na última série, coloquei o Intel Neural Computer Stick 2 no tanque e joguei todos os cálculos de rede neural nele, abandonando o Tensorflow e o OpenCV-DNN.

Havia um problema que eu já encontrei na época - a incapacidade de trabalhar com o NCS de vários processos simultaneamente. Então não foi crítico, mas agora é hora de descobrir.

Ao tentar carregar um modelo a partir do segundo processo, o OpenVino começou a jurar:

E: [ncAPI] [ 926029] resetAll:348 Failed to connect to stalled device, rc: X_LINK_ERROR E: [ncAPI] [ 933282] ncDeviceOpen:672 Failed to find suitable device, rc: X_LINK_DEVICE_NOT_FOUND 

Ao pesquisar no Fórum de suporte da Intel, um problema semelhante foi encontrado .

A partir daí, fomos transferidos para a documentação onde está claramente indicado:

Um único dispositivo não pode ser compartilhado entre vários processos.

Nesta experiência, você pode minimizar e começar a fazer o acesso multiprocessos.

Serviço NCS


É completamente lógico colocar o trabalho direto com o NCS em um serviço separado e distribuir a API a todos os clientes através dos quais eles trabalharão.

Em geral, esse deveria ser um tópico sobre o robô e suas novas conquistas em termos de redes neurais. Mas aconteceu que o material na API do NCS é bastante atraído para um artigo separado.

API do NCS


Em um nível baixo, a API do NCS é muito simples:
- modelo de carga
- iniciar cálculo
- obtenha uma lista de modelos
- obter propriedades do modelo

Se tudo for inequívoco ao carregar o modelo, a exaustão do cálculo é um tensor sensível ao contexto que o cliente pode não precisar de todos.

A obtenção de uma lista de modelos também é bastante transparente e, a partir das propriedades, a dimensão do tensor de entrada vem à mente imediatamente - em termos humanos, isso significa que seria bom ajustar as imagens antecipadamente nas configurações de rede.

Além disso, um nível baixo é bom, mas se você oferecer suporte a operações especializadas, simplificará a lógica e os dados.

Assim, além da base, há uma tarefa de dar suporte à API para classificação, detecção e segmentação.

Infelizmente, os modelos de segmentação mais interessantes não são suportados no NCS; portanto, você deve se limitar ao mais simples, com a estrada e a marcação.

Qualquer uma dessas operações usa o cálculo básico do modelo, mas elas diferem na interpretação do tensor de saída.

Interface principal


Portanto, a interface principal inclui métodos:

  • POST: / load - carrega o modelo
  • POST: / unload / $ model - exclua o modelo (do serviço, é impossível removê-lo do dispositivo)
  • GET: / list - obtém uma lista de modelos
  • GET: / input / shape / $ model - descubra a dimensão do tensor de entrada
  • POST: / inference / file / $ model - faça um cálculo com dados da memória
  • POST: / inference / path / $ model - faça um cálculo com dados no sistema de arquivos

Aqui estão duas palavras sobre dados da memória e do sistema de arquivos:

Se o serviço NCS e seu usuário estiverem sendo executados no mesmo Raspberry, faz sentido economizar na transferência da imagem e, em vez disso, transfira o caminho para que o próprio serviço leia o arquivo.
Se a imagem já estiver na memória (ou não existir no sistema de arquivos), então a transferiremos diretamente a partir daí.

Os testes mostram que a transferência de bytes da memória é significativamente mais lenta (medição feita para 1000 tentativas):

De memória: 87,5 segundos
Caminho do arquivo: 63.3150 segundos

No entanto, essas duas opções são suportadas para qualquer método, tanto para cálculo geral quanto para casos especiais abaixo.

Em geral, o método de inferência tira uma foto na forma de uma matriz numpy como entrada e produz um tensor no mesmo formato.
Como interpretar o escape já é um problema do cliente.
Para facilitar essa tarefa, o serviço suporta métodos especializados que extraem informações significativas em forma humana do tensor de saída.

Classificação


Para classificação, criamos um método REST separado, que converte o tensor de saída em um conjunto de pares (classe, pontuação).

 def get_class_tensor(data): ret = [] thr = 0.01 while(True): cls = np.argmax(data) if data[cls] < thr: break; logging.debug(("Class", cls, "score", data[cls])) c = {"class" : int(cls), "score" : int(100 * data[cls])} data[cls] = 0 ret.append(c) return ret def classify(model_id, img): rc, out = run_inference(model_id, img) if not rc: return rc, out return True, get_class_tensor(out) 

Como no caso da saída normal, dois métodos são suportados - por meio de um arquivo na memória e um caminho no disco.

  • POST: / classify / file / $ model
  • POST: / classify / path / $ model

Detecção


O tensor de saída do detector contém um conjunto (classe, probabilidade, coordenadas normalizadas) e parece bastante complicado.

Nós o transformamos em uma forma compreensível, cortando opções improváveis:

 def get_detect_from_tensor(t, rows, cols): score = int(100 * t[2]) cls = int(t[1]) left = int(t[3] * cols) top = int(t[4] * rows) right = int(t[5] * cols) bottom = int(t[6] * rows) return {"class" : cls, "score" : score, "x" : left, "y" : top, "w" : (right - left), "h" : (bottom - top)} def build_detection(data, thr, rows, cols): T = {} for t in data: score = t[2] if score > thr: cls = int(t[1]) if cls not in T: T[cls] = get_detect_from_tensor(t, rows, cols) else: a = T[cls] if a["score"] < score: T[cls] = get_detect_from_tensor(t, rows, cols) return list(T.values()) def detect(model_id, img): rc, out = run_inference(model_id, img) if not rc: return rc, out rows, cols = img.shape[:2] return True, build_detection(out[0], 0.01, rows, cols) 

Como de costume, ambos os métodos são suportados:

  • POST: / detect / file / $ model
  • POST: / detect / caminho / $ modelo

Segmentação


O tensor de segmentação contém probabilidades por classe e até na dimensão da rede neural.
Converta isso simplesmente em uma máscara de classe:

 def segment(model_id, img): rc, out = run_inference(model_id, img) if not rc: return rc, out out = np.argmax(out, axis=0) out = cv.resize(out, (img.shape[1], img.shape[0]),interpolation=cv.INTER_NEAREST) return True, out 

  • POST: / segmento / arquivo / $ modelo
  • POST: / segmento / caminho / $ model

Conclusão


Como já mencionado, originalmente planejei falar sobre o serviço em um dos capítulos do artigo sobre seu uso, mas verificou-se que o volume está atraindo um documento separado.

Novamente, eu uso o serviço no Raspberry Pi, mas ele pode ser executado em qualquer plataforma que possua python e OpenVino com NCS.

Referências


Source: https://habr.com/ru/post/pt469265/


All Articles