
Introdução . Uma startup de visão computacional usando o desenvolvimento de baixo custo como conceito central. A equipe é bastante consistente com o espírito: 3 a 5
Resumidamente sobre o produto (pc + software) - um sistema de vigilância por vídeo inteligente conectado a uma rede local e produzindo processamento de vídeo por conta própria . Dois pré-requisitos: a presença de uma interface de usuário com direitos diferenciados e a máxima autonomia dos algoritmos.
Do lado técnico, não houve restrições ao hardware, o principal é que funcionava bem; mas com o financeiro era. Para tudo sobre tudo ~ $ 500. Claro, apenas componentes novos e modernos. A escolha deles não é ótima, mas existe!
Decidimos pelo hardware e depois pelo software. A escolha recaiu sobre uma arquitetura de microsserviço usando docker por algum motivo suficiente.
O desenvolvimento de recursos passou de simples e necessário (trabalhar com streams e arquivos de vídeo) a complexo, com revisão periódica. Nós montamos o MVP, vários sprints de otimização nos trouxeram visivelmente mais perto de nosso objetivo estimado - completar todos os 4 pontos simultaneamente, e não separadamente:
- Mais de 16 câmeras IP (FHD / 25 fps) ao vivo, evento ou tempo de reprodução e gravação
- Operação paralela de todos os algoritmos CV disponíveis
- O usuário usa intensamente a interface sem atrasos - assistindo às transmissões
- A carga da CPU é inferior a 90% e tudo funciona (!)
Um pouco sobre a pilha, a escolha recaiu sobre: C / C +, Python + TensorFlow, PHP, NodeJS, TypeScript, VueJS, PostgreSQL + Socket.io e outras coisinhas.
As funcionalidades implementadas foram deliberadamente ocultadas de forma a nos determos em mais detalhes, talvez, na funcionalidade mais interessante e encantadora da área de CV e, até certo ponto, ML.
"Usuário Único"
Um exemplo de uso é coletar o histórico de visitas de cada visitante específico, e os funcionários devem ser considerados separadamente, mesmo que não saibamos que se trata de um funcionário (Exemplo - um shopping center).
E parece que esse problema foi resolvido mais de 100.500 vezes e os telefones e qualquer outra coisa já podem reconhecer rostos e lembrá-los, enviá-los para algum lugar, salvar. Mas 95% das soluções são utilizadas no ACS, onde o próprio usuário, tentando ser reconhecido, fica em frente a uma câmera de 5MP a uma distância de 30-50 cm por vários segundos, até que seu rosto verifique com um ou vários rostos do banco de dados.
No nosso caso, tais condições eram um luxo. Os usuários se moviam de forma irregular, olhando para o smartphone a uma distância suficiente da câmera montada no teto. Além disso, as próprias câmeras apresentavam dificuldades, na maioria das vezes são câmeras de baixo custo com 1,3-2MP e algum tipo de renderização de cores incompreensível, sempre diferente.
Parcialmente, esse problema foi resolvido pela formação de especificações técnicas para as condições de instalação das câmeras, mas em geral o sistema deveria ter sido capaz de reconhecer em tais condições (claro, pior).
Abordagem para a solução: a tarefa foi decomposta em 2 tarefas + estrutura de banco de dados.
Memória de curto prazo
Um serviço separado, onde o processo em tempo real ocorre principalmente, na entrada é um quadro da câmera (na verdade, outro serviço), na saída - uma solicitação http com um vetor X de 512 dimensões normalizado (face-id) e alguns metadados, por exemplo, carimbo de hora.
Existem muitas soluções interessantes no campo da lógica e da otimização dentro dela, mas isso é tudo; por enquanto tudo ...
Memória de longo prazo
Um serviço separado, onde os requisitos em tempo real não são agudos, mas em alguns casos é importante (por exemplo, uma pessoa de uma lista de parada). Em geral, nos limitamos a 3 segundos para processamento.
Na entrada do serviço - http da memória de curto prazo com um vetor de 512 dimensões dentro; na saída - o Id do visitante.
Os primeiros pensamentos são óbvios, a solução para o problema é bastante simples: peguei http → fui ao banco de dados, peguei o que é → comparado com o preenchimento de http, se houver, então é; se não, então novo.
As vantagens de tal solução são inúmeras, mas apenas um menos - ele não funciona.
O problema foi resolvido, embora seguíssemos o caminho do samurai, tentando várias abordagens, olhando periodicamente para a vastidão da Internet. Em geral, a decisão acabou sendo moderadamente lacônica. O conceito é bastante simples e é baseado em clustering:
- Cada vetor (a-vetor) pertencerá a algum usuário; cada cluster (não mais do que M vetores, fora da caixa M = 30) pertence a algum usuário. Se o vetor a pertence ao cluster A não é um fato. Os vetores no cluster definem a interação do cluster, os vetores no usuário definem apenas o histórico do usuário.
- Cada cluster terá um centróide (na verdade, um vetor A) e seu próprio raio (doravante denominado intervalo) de interação com outros vetores ou clusters.
- O centróide e o intervalo serão uma função de cluster, não estática.
- A proximidade dos vetores é determinada pela distância euclidiana quadrada (em casos especiais, caso contrário). Embora existam alguns outros métodos decentes aqui, apenas paramos por aí.
Nota: desde utilizamos vetores normalizados, a distância entre eles foi garantida de 0 a 2. A seguir, sobre o algoritmo para implementação do conceito.
# 1 O círculo de suspeitos. Centroid como função hash
O vetor X obtido da memória de curto prazo é comparado com os centróides do cluster (vetor A) disponíveis no banco de dados por proximidade, distantes, onde intervalo [X, A]> 1 - foram descartados. Se não sobrar ninguém, um novo cluster é criado.
Em seguida, o mínimo é procurado entre o vetor X e todos os vetores a restantes (intervalo_min [X, a])
# 2 Propriedades exclusivas do cluster. Entidade autorreguladora
O próprio intervalo_A do cluster é calculado, cujo vetor está mais próximo do vetor X. Aqui usamos a função linear inversa do número de vetores (N) já neste cluster (const * (1 - N / 2M)); fora da caixa const = 0,67).
# 3 Validação e mal-entendido. Se não for alguém - então quem !?
Se range_A> min_range [X, a], então o vetor X é marcado como pertencente ao A-cluster. Se não, então ... Oh ... Isso é um pouco semelhante à descrição do modelo matemático de mal-entendido.
Decidimos que, neste caso, iremos criar um novo cluster, cometendo deliberadamente um erro do primeiro tipo “Alvo ausente”.
# 4 Treinamento adicional. Como os números formam os sinais
A experiência subjetiva é quando os dados se tornam uma ferramenta. Reconhecemos antes, mas possivelmente com um erro. Devo confiar no vetor X para usá-lo na próxima partida !? Verificando! O vetor X deve:
- estar perto o suficiente do centróide A (intervalo_A> intervalo [X, A])
- ser úteis e diversificados, porque por um lado minimizamos o risco de erros, por outro também não necessitamos de cópias (Config_Max [0.35]> range [X, a]> Config_Max [0.125]). Assim, os configs determinam a velocidade e acerto do "aprendizado".
Cumprindo essas condições, o vetor X é incluído no cluster A (antes simplesmente pertencia ao usuário). Se houver mais vetores no cluster, então removemos o mais central (min_range [A, a]) - ele apresenta a menor variedade e é apenas uma função dos outros; além disso, o centróide já está envolvido na correspondência.
# 5 Trabalhe com bugs. Transformamos desvantagens em vantagens
Em cada escolha difícil, demos um passo em direção ao erro “Alvo ausente” - criamos um novo cluster e usuário. É hora de revisitá-los ... todos. Depois de # 4, temos um cluster A modificado. Em seguida, recalculamos seu centróide (vetor A) e procuramos a distância mínima para todos os centróides disponíveis em nosso espaço de 512 dimensões. Nesse caso, a distância é considerada mais difícil, mas não é tão importante agora. Quando a distância min_range [A, B] é menor que um certo valor (fora da caixa range_unity = 0,25) combinamos dois conjuntos, calculamos um novo centróide e nos livramos de vetores menos "úteis" se houver muitos deles.
Em outras palavras: se houver 2+ clusters, de fato, pertencentes ao mesmo usuário, então, após uma série de detecções, eles se tornarão próximos e se fundirão em um junto com suas histórias.
# 6 Recursos combinatórios. Quando o carro ... pensa!?
Vale a pena definir um novo termo aqui neste artigo. Um vetor fantasma é um vetor obtido não como resultado da atividade de memória de curto prazo, mas como resultado de uma função sobre N vetores do cluster (a1, a2, a3, a4 ...). Obviamente, os vetores obtidos desta forma são armazenados e contabilizados separadamente e não representam nenhum valor até que, como resultado da correspondência, sejam determinados como os mais próximos (consulte # 3). O principal benefício dos vetores fantasmas é acelerar o aprendizado inicial de um cluster .
O sistema já está em produção. O resultado foi obtido em dados reais fora do ambiente de teste para mais de 5000 usuários; lá também foram percebidas várias "fraquezas", que foram reforçadas e consideradas neste texto.
Curiosamente, esta abordagem não possui configurações de usuário e todo o trabalho não é controlado de forma alguma, tudo é completamente autônomo . Além disso, a análise de série temporal permite classificar o usuário em diferentes categorias de maneira semelhante, criando assim associações. Foi assim que a questão foi resolvida - quem são os funcionários e quem são os visitantes.
A função do usuário do sistema é simples - você precisa verificar periodicamente seu e-mail ou a interface do sistema para novos relatórios de atividades.
Resultado
O valor de proximidade para reconhecimento com base na memória de longo prazo é ~ 0,12-0,25 para um cluster moderadamente treinado (contém 6-15 vetores a). Além disso, o aprendizado fica mais lento devido a um aumento na probabilidade de "cópias de vetores", mas no longo prazo a proximidade tende a valores de ~ 0,04-0,12, quando o cluster já contém mais de 20 vetores a. Observe que dentro da memória de curto prazo, de quadro a quadro, o mesmo parâmetro tem um valor de ~ 0,5-1,2, que soa algo como: "Uma pessoa se parece mais com ela mesma com óculos há 2 anos do que 100 ms atrás." Essas oportunidades são abertas pelo uso de clustering na memória de longo prazo .
Enigma
Um dos testes resultou em uma observação interessante.
Condições iniciais:
- Um sistema de videovigilância absolutamente idêntico com configurações absolutamente idênticas é implantado em dois PCs absolutamente idênticos. Estão conectados a uma única câmera ip, localizada corretamente, de acordo com o TOR.
Aja:
- Os sistemas são iniciados ao mesmo tempo e deixados sozinhos por uma semana com todos os algoritmos funcionando. O tráfego corresponde ao tráfego normal sem alteração.
Resultado:
- O número de usuários, clusters e vetores a criados é o mesmo, mas os centróides são diferentes, não significativamente - mas diferentes. A questão é por quê? Quem sabe - escreva nos comentários ou aqui )
É uma pena não poder escrever sobre muitas coisas aqui, mas talvez possa descrever algo com os mesmos detalhes em outro artigo. Talvez tudo isso já tenha sido descrito em algum manual maravilhoso, mas para minha tristeza, nunca encontrei um.
Para concluir, direi que é muito interessante observar de dentro como um sistema de IA autônomo classifica o espaço circundante, implementando várias características inerentes a ele ao longo do caminho. As pessoas não percebem muitas coisas devido à experiência acumulada de percepção (passo 4).
Eu realmente espero que este artigo seja útil para qualquer pessoa em seu projeto.