A visão computacional continua sendo uma aplicação extremamente atraente da inteligência artificial. Seja reconhecendo elementos em um campo de batalha ou prevendo o rendimento das colheitas, a visão computacional é indiscutivelmente um dos domínios da IA mais valiosos comercialmente (e socialmente importantes).
No entanto, um limitador de taxa para a adoção dos melhores recursos de visão computacional são muitas vezes as complexidades associadas à construção de um conjunto de dados e ao projeto de um sistema simples de ponta a ponta que executará sua tarefa de visão computacional em uma nova imagem.
Nesta postagem do blog, daremos uma olhada passo a passo em como resolver esses problemas com as melhores ferramentas da categoria, como CVAT e MinIO Bucket Notifications. Ao final desta postagem, você será capaz de treinar um modelo de detecção de objetos em um conjunto de dados personalizado e usá-lo para fazer previsões sempre que uma nova imagem aparecer.
Digamos que queremos ser capazes de reconhecer os tipos de aeronaves presentes nas imagens de satélite. Suponhamos também que estamos começando do zero: sem conjuntos de dados pré-construídos, sem modelos pré-treinados. Aqui estão dois exemplos de aeronaves que queremos detectar e reconhecer em nossas imagens de satélite:
As etapas descritas nesta postagem podem ser generalizadas para praticamente qualquer domínio. Em vez de detectar tipos de aeronaves, poderíamos classificar o uso da terra ou realizar regressão para prever o rendimento das colheitas. Além das imagens tradicionais, também poderíamos treinar e realizar inferências sobre outros tipos de dados multidimensionais, como nuvens de pontos LiDAR ou imagens sísmicas 3D; torna-se apenas uma questão de aparência dos dados de treinamento (e potencialmente um modelo de aprendizado profundo diferente em vez de YOLO). Se você tiver mais dúvidas sobre como isso seria para um caso de uso específico, sinta-se à vontade para criar um problema no repositório GitHub!
Para este projeto, em grande parte devido ao fato de não possuir um satélite de imagem sob demanda, visitei aeródromos no Google Earth e tirei várias capturas de tela de áreas que tinham alguns desses aviões visíveis. A montagem desse conjunto de imagens levou algum tempo, então armazenei todas elas em um balde em meu servidor MinIO intitulado “detecção de objetos”. Em um ambiente de produção, os benefícios de armazenar as amostras coletadas no MinIO tornam-se ainda mais prescientes. Replicação ativo-ativo, os mais altos níveis de criptografia e GET/PUTs super rápidos (para citar alguns) significam que suas amostras coletadas diligentemente estarão altamente disponíveis, seguras e protegidas.
Para treinar um modelo de detecção de objetos para o seu caso de uso, é necessário um conjunto de dados rotulado (ou 'anotado'). Uma ótima ferramenta para isso é o CVAT da OpenCV. Um recurso interessante é que o CVAT fornece um utilitário para conectar seu bucket MinIO como um “armazenamento em nuvem” para alimentar as imagens do seu bucket diretamente para a ferramenta de anotação do conjunto de dados. Para fazer isso, certifique-se de que o host do seu MinIO Server esteja acessível ao servidor CVAT, especialmente se você estiver executando o MinIO Server no local ou localmente em seu laptop. Além disso, como observação, há duas maneiras de usar o CVAT: (1) usando o aplicativo da web fornecido em app.cvat.ai ou (2) executando-o localmente. Em ambos os casos, depois de abrir o CVAT, clique em “Cloud Storages” na barra de menu. A partir daí, você pode preencher um formulário para anexar seu bucket MinIO (compatível com S3):
Vamos agora criar nossa nova tarefa de rotulagem em “Tarefas”:
Você deverá ser solicitado a preencher um formulário:
Ao criar a tarefa, é importante definir os rótulos das classes corretamente (defini dois rótulos retangulares intitulados “SU30” e “TU95”, correspondentes aos dois planos que queria detectar):
Agora, a etapa restante é anexar nosso bucket MinIO adicionado anteriormente como fonte de dados. Em “Selecionar arquivos”, clique em “Armazenamento em nuvem” e preencha o nome que você forneceu para essa fonte anteriormente. Usei o nome “minio-cv-bucket” acima.
O processo de upload levará alguns minutos. Depois de concluído, você poderá ver seu trabalho de anotação disponível em “Trabalhos”.
Agora, clicando no trabalho, você pode começar a anotar cada uma de suas imagens. Aviso: este pode ser um processo desproporcionalmente demorado. Geralmente, em um ambiente de produção com grandes necessidades de anotação, talvez seja melhor transferir essa tarefa para uma equipe interna dedicada ou para uma empresa terceirizada de rotulagem de dados.
Quando terminar de anotar, exporte o conjunto de dados no formato YOLO.
Seu conjunto de dados exportado estará na forma de um arquivo zip. Depois de descompactá-lo, os arquivos de texto de anotação formatados em YOLO estarão em uma pasta fechada. Sinta-se à vontade para dar uma olhada neles. No formato YOLO, as anotações de cada imagem estão em um arquivo de texto onde cada linha contém dois cantos de uma caixa delimitadora e a classe. O número da turma corresponde à ordem em que você definiu os rótulos ao criar a tarefa. Portanto, neste exemplo, 0 corresponderia ao Su-30 e 1 corresponderia ao Tu-95.
Neste ponto, crie um novo diretório de trabalho (ou insira um que você já criou). Dentro deste diretório, crie um subdiretório chamado 'dataset'. Dentro do 'dataset', crie diretórios de forma que seu diretório de trabalho fique assim:
my_cv_project (WORKING DIRECTORY) |---- dataset |----images |----train |----val |----test |----annotations |----train |----val |----test
Agora você terá que preencher os subdiretórios train, val e test para ambas as imagens e suas anotações correspondentes (os arquivos de texto). Depende de você como deseja recuperar e dividir suas amostras. Uma boa prática é dividir a quantidade total de amostras de treinamento em 80% de treinamento, 10% de validação e 10% de teste. Certifique-se de embaralhar aleatoriamente suas imagens antes de particioná-las.
Pessoalmente, usei o mc cp do MinIO Client na linha de comando para recuperar rapidamente todas as imagens do meu bucket de 'detecção de objetos'. Alternativamente, se você já tiver todas as suas imagens de amostra em um só lugar no seu computador local, poderá trabalhar diretamente com elas. Depois de reunir todas as minhas amostras em um só lugar, usei um script Python para embaralhar, dividir e mover minhas imagens e anotações para os diretórios train, val e test. Aqui está o script fornecido por conveniência . Se você tiver alguma dúvida sobre como usá-lo, fique à vontade para fazer um issue no repo !
Por fim, certifique-se de que, para cada imagem colocada em images/train, images/val ou images/test, o arquivo .txt de anotação correspondente também esteja no subdiretório correspondente dentro do diretório annotations/. Por exemplo:
my_cv_project (WORKING DIRECTORY) |---- dataset |----images |----train - 5.png - 3.png - 2.png |----val - 4.png |----test - 1.png |----annotations |----train - 5.txt - 3.txt - 2.txt |----val - 4.txt |----test - 1.txt
Agora, nossos dados estão no lugar. É hora de dar uma olhada em nosso modelo de detecção de objetos e começar a treinar.
O padrão ouro atual (em termos de desempenho e facilidade de uso) para reconhecimento de objetos é a classe de modelos YOLO (You Only Look Once). No momento em que este artigo foi escrito, YOLOv8 era a versão mais recente e era mantida como código aberto pela Ultralytics. YOLOv8 fornece uma API simples que podemos aproveitar para treinar o modelo em nossas anotações recém-criadas (e, eventualmente, executar inferência também).
Vamos baixar o YOLOv8:
$ pip install ultralytics
Agora podemos usar a ferramenta CLI YOLOv8 ou Python SDK para treinar, validar e prever. Consulte a documentação do YOLOv8 para obter mais informações.
No seu diretório de trabalho, defina um arquivo YAML que especifique os locais do conjunto de dados e os detalhes sobre as classes. Observe como os caminhos são iguais aos que criei anteriormente no diretório de trabalho. Chamei meu arquivo de ' objdetect.yaml .' Além disso, observe que os dois rótulos de classe de aeronave devem ser definidos na mesma ordem em que estavam no CVAT.
train: ./dataset/images/train/ val: ./dataset/images/val/ test: ./dataset/images/test/ # number of classes nc: 2 # class names names: ["SU-30","TU-95"]
Comece a treinar o modelo YOLOv8 em nosso conjunto de dados com o seguinte comando (usando a ferramenta YOLO CLI). Consulte a documentação do YOLO para saber mais sobre todas as diferentes opções que você pode configurar para treinamento. Aqui, estou iniciando o treinamento para 100 épocas e definindo um tamanho de imagem de 640 pixels (todas as nossas imagens de treinamento serão dimensionadas de acordo durante o treinamento):
$ yolo task=detect \ mode=train \ model=yolov8s.pt \ data=objdetect.yaml \ epochs=100 \ imgsz=640
O treinamento vai demorar um pouco, principalmente se você estiver trabalhando em um laptop (como eu), então agora é um bom momento para fazer uma pausa (ou ler mais adiante 😀)!
No final do ciclo de treinamento, seu modelo treinado, juntamente com outros gráficos e tabelas interessantes, serão armazenados em um diretório gerado automaticamente chamado 'execuções'. A saída do terminal (como abaixo) indicará a localização específica dos resultados da última execução. Cada vez que você treinar um modelo, um diretório semelhante será gerado em 'runs/detect/'.
Results saved to runs/detect/train
Nota: runs/detect/train/weights/ conterá os arquivos PT com os pesos exatos treinados. Lembre-se deste local para mais tarde.
Você pode executar a validação com o seguinte comando:
$ yolo task=detect \ mode=val \ model=path/to/best.pt \ data=objdetect.yaml
Os resultados serão armazenados automaticamente em uma pasta em seu diretório de trabalho com um caminho no formato 'runs/detect/val.'
Para realizar inferência no conjunto de testes, você pode usar o seguinte comando:
$ yolo task=detect \ mode=predict \ model=path/to/best.pt \ conf=0.5 \ source=dataset/images/test
Os resultados serão armazenados em 'runs/detect/predict'. Aqui estão alguns resultados de previsão no conjunto de teste:
Agora que temos um modelo treinado que consegue reconhecer alguns tipos de aeronaves presentes em uma imagem de satélite, como podemos utilizá-lo para novas imagens de forma simples ?
MinIO Bucket Notifications é uma ferramenta perfeita para isso. Podemos construir um sistema que possa realizar automaticamente inferência de detecção de objetos em uma nova imagem colocada em nosso balde com a ajuda de um webhook.
Em um nível alto, temos 3 etapas. Primeiro, precisamos definir um endpoint que possa servir como webhook para realizar a detecção de objetos em uma nova imagem com nosso modelo treinado. Em segundo lugar, precisamos configurar algumas variáveis de ambiente para a implantação do nosso servidor MinIO que o instrui a atingir nosso endpoint de webhook após a ocorrência de algum evento. Terceiro, precisamos configurar em quais tipos de eventos de bucket (ou seja, PUT) queremos atuar. Vamos examinar isso passo a passo.
Aqui está o código para um servidor simples baseado em Flask ( detection_server.py ) que executa inferência em uma nova imagem adicionada ao bucket MinIO:
""" This is a simple Flask inference server implementation that serves as a webhook for the event of a new image being added to a MinIO bucket. Object detection using YOLO will be performed on that image and the resulting predictions will be returned. """ from flask import Flask, request, abort, make_response from ultralytics import YOLO import tempfile from minio import Minio # Make sure the following are populated with your MinIO details # (Best practice is to use environment variables!) MINIO_ENDPOINT = '' MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' model = YOLO('/PATH/TO/best.pt') # load a custom model (path to trained weights) client = Minio( MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, ) app = Flask(__name__) @app.route('/', methods=['POST']) async def inference_bucket_webhook(): """ This endpoint will be called when a new object is placed in your inference bucket """ if request.method == 'POST': # Get the request event from the 'POST' call event = request.json bucket = event['Records'][0]['s3']['bucket']['name'] obj_name = event['Records'][0]['s3']['object']['key'] with tempfile.TemporaryDirectory() as temp_dir: temp_file_name = temp_dir+'/'+obj_name client.fget_object(bucket, obj_name, temp_file_name) # See https://docs.ultralytics.com/modes/predict/ for more information about YOLO inference options results = model.predict(source=temp_file_name, conf=0.5, stream=False) # A list of bounding boxes (if any) is returned. # Each bounding box is in the format [x1, y1, x2, y2, probability, class]. result = {"results": results[0].boxes.data.tolist()} print(result) resp = make_response(result, 200) return resp else: abort(400) if __name__ == '__main__': app.run()
Vamos iniciar o servidor de inferência:
$ python detection_server.py * Serving Flask app 'detection_server' * Debug mode: off * Running on http://127.0.0.1:5000 Press CTRL+C to quit
Anote o nome do host e a porta em que o aplicativo Flask está sendo executado.
A seguir, vamos começar a trabalhar na configuração dos webhooks no lado do MinIO. Primeiro, defina as seguintes variáveis de ambiente. Substitua <YOURFUNCTIONNAME> por um nome de função de sua escolha. Para simplificar, optei por 'inferência'. Além disso, certifique-se de que a variável de ambiente do terminal esteja configurada para o host e a porta corretos para seu servidor de inferência. Nesse caso, http://localhost:5000 é onde nosso aplicativo Flask está sendo executado.
$ export MINIO_NOTIFY_WEBHOOK_ENABLE_<YOURFUNCTIONNAME>=on $ export MINIO_NOTIFY_WEBHOOK_ENDPOINT_<YOURFUNCTIONNAME>=http://localhost:5000
Agora, reinicie o servidor MinIO usando o comando mc admin service restart ALIAS ou se estiver iniciando o servidor pela primeira vez, você também pode usar apenas o comando minio server . Para obter mais informações sobre como reiniciar/iniciar o servidor MinIO, consulte a documentação do MinIO. Nota: ALIAS deve ser substituído pelo alias da implantação do servidor MinIO. Para obter mais informações sobre como definir um alias ou visualizar aliases existentes, consulte a documentação .
Por fim, vamos adicionar o intervalo e o evento sobre os quais queremos ser notificados. No nosso caso, queremos ser notificados sobre eventos ` put` (criação de novos objetos) em nosso bucket. Eu criei um balde totalmente novo e vazio para esse fim, intitulado “detectar inferência”, então vou substituí-lo por 'BUCKET'.
$ mc event add ALIAS/BUCKET arn:minio:sqs::<YOURFUNCTIONNAME>:webhook --event put
Você pode verificar se configurou o tipo de evento correto para as notificações de bucket verificando se " s3:ObjectCreated:* " é gerado quando você executa este comando:
$ mc event ls local/detect-inference arn:minio:sqs::<YOURFUNCTIONNAME>:webhook
Para obter uma explicação mais detalhada sobre a publicação de eventos de bucket em um webhook, consulte a documentação . Agora estamos prontos para tentar a detecção de objetos em uma imagem totalmente nova!
Aqui está a nova imagem (intitulada '1.png') sobre a qual desejo realizar inferências:
Deixo cair a nova imagem no meu intervalo de 'detecção de inferência':
Quase instantaneamente, consigo ver os seguintes resultados em meu servidor Flask:
$ python detection_server.py * Serving Flask app 'detection_server' * Debug mode: off * Running on http://127.0.0.1:5000 Press CTRL+C to quit image 1/1 /var/folders/xf/q7x0z8sn5nvckccp1g0m1vpm0000gn/T/tmpo6jx3w8u/1.png: 448x736 2 SU-30s, 101.0ms Speed: 4.1ms preprocess, 101.0ms inference, 5.8ms postprocess per image at shape (1, 3, 448, 736) {'results': [[1927.78369140625, 627.7123413085938, 1995.090576171875, 715.3443603515625, 0.8142037987709045, 0.0], [1735.740234375, 477.2108154296875, 1809.181640625, 555.767578125, 0.7766116261482239, 0.0]]} 127.0.0.1 - - [14/Sep/2023 15:39:21] "POST / HTTP/1.1" 200 -
Observe que cada caixa delimitadora detectada na lista de resultados está no formato YOLO [x1, y1, x2, y2, probabilidade, classe]. Aqui estão as caixas delimitadoras e as classes previstas sobrepostas à imagem original:
Observação: para ambientes de produção e/ou grandes modelos de aprendizado de máquina, é uma boa ideia usar uma estrutura de serviço de modelo estabelecida, como PyTorch Serve ou Triton Server, para tornar a inferência mais robusta e confiável. Se você estiver interessado nisso, confira a postagem anterior sobre Otimizando o serviço de modelo de IA com MinIO e PyTorch Serve .
Conseguimos! Vimos como o MinIO e o CVAT se uniram para manter nossas amostras de imagens coletadas seguras e disponíveis, bem como como criar nosso conjunto de dados de detecção de objetos personalizado. Em seguida, treinamos nosso próprio modelo YOLO personalizado para nossa tarefa personalizada. Por último, com pouco mais de 50 linhas de código, montamos um servidor de inferência usando MinIO Bucket Notifications que poderia executar uma nova imagem além de nosso modelo personalizado de detecção de objetos treinados.
Além disso, para a maioria das aplicações de visão computacional de missão crítica, é melhor conduzir inferências na borda. Caso contrário, esses aplicativos ficarão vulneráveis à latência associada ao upload de novos dados para a nuvem pública e à espera que um servidor de inferência na nuvem retorne com uma resposta – sem mencionar os riscos de uma conexão de rede defeituosa. Por esse motivo, um pipeline de visão computacional centrado no MinIO como camada de dados faz muito mais sentido. Imagine um drone sobrevoando um campo de aviação, sendo capaz de capturar, armazenar e usar nosso modelo treinado em novas imagens com hardware e software totalmente integrados. Com uma implantação local do servidor MinIO, o sistema de inferência baseado em Bucket Notification que construímos no final da postagem funciona perfeitamente para este cenário e inúmeros outros semelhantes.
Se você tiver alguma dúvida, entre em nosso canal Slack ou envie-nos uma mensagem para [email protected] . Estamos aqui para ajudá-lo.
Também publicado aqui .