Tudo sobre YOLOs - Parte5 - Como codificá-lo

Nesta postagem, explicarei como usar a versão básica do YOLOv3 para detectar objetos do conjunto de dados COCO e também como treinar seu próprio conjunto de dados para seu próprio caso de uso.

O código de detecção Yolo aqui é baseado na implementação de Erik Lindernoren do trabalho de Joseph Redmon e Ali Farhadi.

Aqui estão os links para a série.

Tudo sobre YOLOs - Parte 1 - um pouco de História

Tudo sobre YOLOs - Parte 2 - O primeiro YOLO

Tudo sobre YOLOs - Parte3 - O YOLOv2 melhor, mais rápido e mais forte

Tudo sobre YOLOs - Parte4 - YOLOv3, uma melhoria incremental

Tudo sobre YOLOs - Parte5 - Instalação e funcionamento

Por favor, encontre a pasta do projeto no meu gitrepo.

Na pasta do projeto, você encontrará uma subpasta chamada config com arquivos de configuração, nomes de classes e variáveis ​​de ambiente, pasta de dados com conjuntos de dados e pasta utils com algumas funções úteis do python.

Primeiro, baixe o arquivo de pesos YOLOv3 e coloque-o na pasta de configuração no projeto executando o seguinte comando. (Adicionei um arquivo .sh para fazer isso no repositório)

wget https://pjreddie.com/media/files/yolov3.weights

Uma vez baixado, o conteúdo da pasta de configuração deve se parecer com o seguinte.

Como colocar o YOLOv3 em funcionamento para detectar objetos COCO?

Vejamos a implementação do YOLO baunilha para inferir objetos COCO.

  • Importe os módulos necessários.
dos modelos import * dos utilitários import * import os, sys, time, datetime, tocha de importação aleatória de torch.utils.data import DataLoader dos conjuntos de dados de importação torchvision, transformações de torch.autograd import Variableimport matplotlib.pyplot como plt import matplotlib.patches patches de PIL import Image
  • Carregue a configuração e os pesos pré-treinados, bem como os nomes das classes do conjunto de dados COCO no qual o modelo Darknet foi treinado. img_size, conf_thres e num_thresold são parâmetros que podem ser ajustados com base no caso de uso.
Nota: Defina o modelo para o modo de avaliação para inferir.
config_path = 'config / yolov3.cfg' weights_path = 'config / yolov3.weights' class_path = 'config / coco.names' img_size = 416 conf_thres = 0.8 nms_thres = 0.4
# Carregar modelo e pesos model = Darknet (config_path, img_size = img_size) model.load_weights (weights_path) model.cuda () model.eval () classes = utils.load_classes (class_path) Tensor = torch.cuda.FloatTensor
  • Escreva uma função para realizar a detecção básica dada uma imagem. Por favor, veja os comentários sobre o que o código faz. Principalmente é o pré-processamento da imagem.
def detect_image (img): # escala e proporção da imagem da almofada = min (tamanho_img / img.size [0], tamanho_img / tamanho_img [1]) imw = round (tamanho da img [0] * proporção) imh = round ( img.size [1] * proporção) img_transforms = transforma.Compose ([transforma.Resize ((imh, imw)), transforma.Pad ((max (int ((imh-imw) / 2), 0), max ( int ((imw-imh) / 2), 0), max (int ((imh-imw) / 2), 0), max (int ((imw-imh) / 2), 0)), (128,128,128) ), transforms.ToTensor (),]) # converte imagem em tensor image_tensor = img_transforms (img) .float () image_tensor = image_tensor.unsqueeze_ (0) input_img = Variável (image_tensor.type (Tensor)) # inferência de execução no modelo e obtenha detecções com torch.no_grad (): detections = model (input_img) detections = utils.non_max_suppression (detections, 80, conf_thres, nms_thres) retorna detecções [0]
  • Agora, o código para usar esta função para obter inferências. Isso funciona com qualquer objeto existente no conjunto de dados COCO. Novamente, a maior parte do código trata do pré-processamento da imagem e da plotagem das caixas delimitadoras.
# carregar imagem e obter detecções img_path = "images / blueangels.jpg" prev_time = time.time () img = Image.open (img_path) detecções = detect_image (img) inference_time = datetime.timedelta (segundos = time.time () - prev_time) print ('Tempo de inferência:% s'% (inference_time)) # # Obter cores da caixa delimitadora cmap = plt.get_cmap ('tab20b') colors = [cmap (i) para i no espaço np.linspace (0, 1, 20)] img = np.array (img) plt.figura () fig, ax = plt.subplots (1, figsize = (12,9)) ax.imshow (img) pad_x = max (img.shape [0] - img.shape [1], 0) * (img_size / max (img.shape)) pad_y = max (img.shape [1] - img.shape [0], 0) * (img_size / max (img.shape) )) unpad_h = img_size - pad_y unpad_w = img_size - pad_xif as detecções não são Nenhuma: unique_labels = detections [:, -1] .cpu (). unique () n_cls_preds = len (unique_labels) bbox_colors = random.sample (colors, n_cls_preds) # procure detecções e desenhe caixas delimitadoras para x1, y1, x2, y2, conf, cls_conf, cls_pred nas detecções: box_h = ((y2 - y1) / unpad_h) * img.shape [0] box_w = ((x2 - x1) / unpad_w) * i mg.shape [1] y1 = ((y1 - pad_y // 2) / unpad_h) * img.shape [0] x1 = ((x1 - pad_x // 2) / unpad_w) * img.shape [1] color = bbox_colors [int (np.where (unique_labels == int (cls_pred)) [0])] bbox = patches.Rectangle ((x1, y1), box_w, box_h, linewidth = 2, edgecolor = color, facecolor = 'none' ) ax.add_patch (bbox) plt.text (x1, y1, s = classes [int (cls_pred)], color = 'branco', verticalalignment = 'top', bbox = {'color': color, 'pad': 0}) plt.axis ('off') # salva imagem plt.savefig (img_path.replace (". Jpg", "-det.jpg"), bbox_inches = 'tight', pad_inches = 0.0) plt.show ()
  • O script acima lida com a obtenção de detecções nas imagens. Agora vamos ver como fazê-lo funcionar para vídeos.
videopath = 'video / sample_video.mp4'
% pylab inline import cv2 a partir do IPython.display import clear_outputcmap = plt.get_cmap ('tab20b') colors = [cmap (i) [: 3] para i no np.linspace (0, 1, 20)] # initialize vid = cv2 .VideoCapture (videopath) # while (True): para ii no intervalo (40): ret, frame = vid.read () frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB) pilimg = Detecções Image.fromarray (frame) = detect_image (pilimg) img = np.array (pilimg) pad_x = max (img.shape [0] - img.shape [1], 0) * (img_size / max (img.shape)) pad_y = max (img.shape [1] - img.shape [0], 0) * (img_size / max (img.shape)) unpad_h = img_size - pad_y unpad_w = img_size - pad_x se as detecções não forem None: unique_labels = detections [:, -1]. cpu (). unique () n_cls_preds = len (unique_labels) para x1, y1, x2, y2, conf, cls_conf, cls_pred nas detecções: box_h = int (((y2 - y1) / unpad_h) * img.shape [0] ) box_w = int (((x2 - x1) / unpad_w) * img.shape [1]) y1 = int (((y1 - pad_y // 2) / unpad_h) * img.shape [0]) x1 = int ( ((x1 - pad_x // 2) / unpad_w) * img.shape [1]) color = colors [int (cls_conf. item ())% len (cores)] color = [i * 255 para i em cores] cls = classes [int (cls_pred)] cv2.rectangle (quadro, (x1, y1), (x1 + box_w, y1 + box_h ), cor, 4) cv2.rectangle (quadro, (x1, y1-35), (x1 + len (cls) * 19 + 60, y1), cor, -1) cv2.putText (quadro, cls + "- "+ str (cls_conf.item ()), (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255.255.255), 3) fig = figure (figsize = (12, 8)) title (" Video Stream ") imshow (quadro) show () clear_output (espera = True)
Nota: O código acima também pode ser usado para transmissão ao vivo, alterando o caminho do vídeo para um fluxo de câmera IP.

Como ajustar o modelo YOLOv3 de baunilha para trabalhar com objetos personalizados?

Vejamos o treinamento personalizado YOLOv3 para nosso próprio caso de uso. São várias maneiras de conseguir isso. Mas as etapas a seguir explicam a maneira mais fácil que encontrei na Internet.

Pré-requisitos

Dados

O primeiro passo seria criar seus dados de treinamento, ou seja, marcar as imagens com uma caixa delimitadora e um rótulo de classe para o qual você deseja que as detecções ocorram.

Existem muitas ferramentas para fazer isso. A maneira mais fácil que encontrei é usando o LabelImg. É uma ferramenta de anotação de imagem gráfica. Você pode instalar usando o comando pip.

pip install labelImg

Clique no link para encontrar um bom tutorial sobre como usá-lo.

Rotular uma imagem como simples significa marcar as coordenadas e a classe da caixa delimitadora. Portanto, para cada imagem, o rótulo gerado (arquivo .txt) teria uma única linha. Isso é chamado de formato YOLO.

Nota: Certifique-se de selecionar o formato YOLO ao marcar as imagens usando a ferramenta LabelImg.
#class xy ​​largura altura 1 0,351466 0,427083 0,367168 0,570486

Depois que as imagens são marcadas e os rótulos (arquivos .txt) são gerados, execute um script para dividir o conjunto de dados para treinar e validar. Por favor, execute o seguinte código python apenas uma vez para conseguir isso. datasplit.py é o arquivo com este código no repositório.

importar glob importar os importar numpy como np import syscurrent_dir = "./data/artifacts/images" split_pct = 10 # 10% conjunto de validação file_train = open ("data / artifacts / train.txt", "w") file_val = open ( "data / artifacts / val.txt", "w") counter = 1 index_test = round (100 / split_pct) para o caminho completo em glob.iglob (os.path.join (current_dir, "* .JPG")): title, ext = os.path.splitext (os.path.basename (caminho completo)) if counter == index_test: counter = 1 file_val.write (current_dir + "/" + title + '.JPG' + "\ n") else: file_train.write (current_dir + "/" + title + '.JPG' + "\ n") counter = counter + 1 file_train.close () file_val.close ()

Isso criará 2 arquivos, train.txt e val.txt, contendo os caminhos completos de todas as imagens, com 90% em train e 10% em val.

Uma vez feito, certifique-se de obter o conjunto de dados na seguinte estrutura de pastas.

Pasta principal --- dados --- nome do conjunto de dados --- imagens --- img1.jpg --- img2.jpg .......... --- labels --- img1.txt --- img2.txt .......... --- train.txt --- val.txt

Config

Agora, para os arquivos de configuração na pasta config /. Primeiro, coco.data ficaria assim:

classes = 80 # número de classes vai aqui train = data / alpha / train.txt # o caminho do train.txt vai aqui válido = data / alpha / val.txt # o caminho do val.txt vai aqui names = config /coco.names # edite o arquivo de nomes com rótulos de classe backup = backup / # Mantenha esse parâmetro como está

Edite esses valores de acordo com seu conjunto de dados personalizado. Edite as “classes” para conter o número de classes a serem detectadas no seu caso de uso. Train e válido mantém o caminho para o train.txt e val.txt respectivamente. Edite o arquivo "coco.names" com rótulos de classe. deve listar, um por linha, os nomes das classes (para o arquivo de anotações, o primeiro corresponde a 0, ao lado de 1, etc.)

class1 class2 ...
Nota: O parâmetro de backup não é usado, mas parece ser necessário.

Agora, para o arquivo yolov3.cfg. Ele contém os detalhes arquitetônicos do algoritmo YOLOv3.

Na primeira seção [net], ajuste o valor do lote e as subdivisões para caber na memória da GPU. Quanto maior o tamanho do lote, melhor e mais rápido o treinamento, mas mais memória será necessária. Também aqui você pode ajustar a taxa de aprendizado.

Para uma GPU Nvidia com 11Gb de memória, um lote de 16 e 1 subdivisão é bom.

Há duas outras coisas cruciais a serem alteradas, que são os valores das classes e dos filtros finais da camada. E você tem que fazer isso em três lugares diferentes no arquivo.

Se você pesquisar o arquivo, encontrará 3 seções [yolo]. Dentro dessa seção, defina classes para o número de classes no seu modelo.

Você também deve alterar o valor dos filtros na seção [convolucional] logo acima de [yolo] para o seguinte.

filtros = (classes + 5) x 3

com todo o conjunto acima, você estará pronto para treinar o modelo.

Código

Implementação do ciclo de treinamento

  • Importando bibliotecas
da divisão de importação __future__
de modelos import * de utils.utils import * de utils.datasets import * de utils.parse_config import *
importar sistema de importação sys tempo de importação data e hora de importação argparse
importar torch de torch.utils.data importar DataLoader de torchvision importar conjuntos de dados de torchvision importar transformações de torch.autograd import Variável import torch.optim as optim
  • Configuração de parâmetro
epochs = 20 image_folder = "data / dataset / images" batch_size = 16 model_config_path = "config / yolov3.cfg" data_config_path = "config / coco.data" weights_path = "config / yolov3.weights" class_path = "config / coco.names "conf_thres = 0.8 nms_thres = 0.4 n_cpu = 0 img_size = 416 checkpoint_interval = 1 checkpoint_dir = 'checkpoints' use_cuda = True
  • Use CUDA, se disponível
cuda = torch.cuda.is_available () e use_cuda
  • Obter configuração e parâmetros de dados na memória
# Carregar classes os.makedirs ("pontos de verificação", exist_ok = True) classes = load_classes (class_path)
# Obter configuração de dados data_config = parse_data_config (data_config_path) train_path = data_config ["train"]
# Obter hiperparâmetros hyperparams = parse_model_config (model_config_path) [0] learning_rate = float (hiperparams ["learning_rate"]) momentum = float (hiperparams ["momentum"]) decay = float (hiperparams ["decay"]) burn_in = int ( hiperparams ["burn_in"])
  • Inicie o modelo e defina para treinar.
# Iniciar modelo de modelo = Darknet (model_config_path) model.load_weights (weights_path)
if cuda: model = model.cuda () model.train ()
  • Obtenha o carregador de dados e defina o otimizador
# Get dataloader dataloader = torch.utils.data.DataLoader (ListDataset (train_path), batch_size = batch_size, shuffle = False, num_workers = n_cpu)
Tensor = torch.cuda.FloatTensor se for outro tocha.FloatTensor
# Obtenha otimizador otimizador = torch.optim.Adam (filter (lambda p: p.requires_grad, model.parameters ()))
  • Agora, para o ciclo de treinamento principal.
para época no intervalo (épocas): para lote_i, (_, imgs, destinos) em enumerar (carregador de dados): imgs = Variável (imgs.type (Tensor)) destinos = Variável (destinos.tipo (tensor), required_grad = False)
optimizer.zero_grad ()
perda = modelo (imgs, destinos)
loss.backward () optimizer.step ()
print ("[Época% d /% d, lote% d /% d] [Perdas: x% f, y% f, w% f, h% f, conf% f, cl%%, cls% f, total% f, recordação :% .5f, precisão:% .5f] "% (época, época, lote_i, len (carregador de dados), model.losses [" x "], model.losses [" y "], model.losses [" w " ], model.losses ["h"], model.losses ["conf"], model.losses ["cls"], loss.item (), model.losses ["recall"], model.losses ["precision "])))
model.seen + = imgs.size (0)
if epoch% checkpoint_interval == 0: print ("saving") model.save_weights ("% s /% d.weights"% (checkpoint_dir, "latest"))

O loop acima treina e salva um arquivo de pesos na pasta dos modelos para cada época com o número da época como o nome. Também imprime várias perdas para monitorar o progresso do treinamento.

Implementação da Inferência

  • Importando bibliotecas
dos modelos import * dos utils import * import cv2 import os, sys, time, datetime, tocha de importação aleatória de torch.utils.data import DataLoader dos conjuntos de dados de importação torchvision, transformações de torch.autograd import
importar matplotlib.pyplot como plt importar matplotlib.patches como patches do PIL import Importar imagens imutils de imutils.video import WebcamVideoStream
  • Defina os parâmetros. Para usar o novo modelo para detecção, substitua o arquivo de pesos mais recente gerado na pasta dos modelos pelo arquivo yolov3.weights na pasta de configuração. Verifique se o caminho de pesos no código de inferência aponta para o caminho de pesos mais recente.
num_classes = 1 config_path = 'config / yolov3.cfg' weights_path = 'checkpoint_19.weights' class_path = 'config / coco.names' img_size = 416 conf_thres = 0,95 nms_thres = 0,95
  • Carregar modelo e definido para avaliar para inferências
# Carregar modelo e pesos model = Darknet (config_path, img_size = img_size) model.load_weights (weights_path) # model.cuda () model.eval () classes = load_classes (class_path) Tensor = torch.FloatTensor
  • Defina funções para carregar classes e detectar a imagem.
def load_classes (path): "" "Carrega rótulos de classe em 'path'" "" fp = open (path, "r") nomes = fp.read (). split ("\ n") [:] retorna nomes
def detect_image (img): # escala e proporção da imagem da almofada = min (tamanho_img / img.size [0], tamanho_img / tamanho_img [1]) imw = round (tamanho da img [0] * proporção) imh = round ( img.size [1] * proporção) img_transforms = transforma.Compose ([transforma.Resize ((imh, imw)), transforma.Pad ((max (int ((imh-imw) / 2), 0), max ( int ((imw-imh) / 2), 0), max (int ((imh-imw) / 2), 0), max (int ((imw-imh) / 2), 0)), (128,128,128) ), transforms.ToTensor (),]) # converte imagem em tensor image_tensor = img_transforms (img) .float () image_tensor = image_tensor.unsqueeze_ (0) input_img = Variável (image_tensor.type (Tensor)) # inferência de execução no modelo e obtenha detecções com torch.no_grad (): detections = model (input_img) detections = utils.non_max_suppression (detections, num_classes, conf_thres, nms_thres) retorna detecções [0]
  • Agora, para o ciclo de inferência.
videopath = 'video / sample_video.mp4'
% pylab inline import cv2 a partir do IPython.display import clear_outputcmap = plt.get_cmap ('tab20b') colors = [cmap (i) [: 3] para i no np.linspace (0, 1, 20)] # initialize vid = cv2 .VideoCapture (videopath) # while (True): para ii no intervalo (40): ret, frame = vid.read () frame = cv2.cvtColor (quadro, cv2.COLOR_BGR2RGB) pilimg = Detecções Image.fromarray (quadro) = detect_image (pilimg) img = np.array (pilimg) pad_x = max (img.shape [0] - img.shape [1], 0) * (img_size / max (img.shape)) pad_y = max (img.shape [1] - img.shape [0], 0) * (img_size / max (img.shape)) unpad_h = img_size - pad_y unpad_w = img_size - pad_x se as detecções não forem None: unique_labels = detections [:, -1]. cpu (). unique () n_cls_preds = len (unique_labels) para x1, y1, x2, y2, conf, cls_conf, cls_pred nas detecções: box_h = int (((y2 - y1) / unpad_h) * img.shape [0] ) box_w = int (((x2 - x1) / unpad_w) * img.shape [1]) y1 = int (((y1 - pad_y // 2) / unpad_h) * img.shape [0]) x1 = int ( ((x1 - pad_x // 2) / unpad_w) * img.shape [1]) color = colors [int (cls_conf. item ())% len (cores)] color = [i * 255 para i em cores] cls = classes [int (cls_pred)] cv2.rectangle (quadro, (x1, y1), (x1 + box_w, y1 + box_h ), cor, 4) cv2.rectangle (quadro, (x1, y1-35), (x1 + len (cls) * 19 + 60, y1), cor, -1) cv2.putText (quadro, cls + "- "+ str (cls_conf.item ()), (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255.255.255), 3) fig = figure (figsize = (12, 8)) title (" Video Stream ") imshow (quadro) show () clear_output (espera = True)

Encontre os cadernos jupyter para treinamento e inferência no meu git-repo.

Espero que esta série tenha lhe dado uma imagem clara de tudo o que há para saber sobre o YOLO e que você comece suas próprias implementações.

Se você deseja ver um blog sobre um tópico específico, não hesite em mencioná-lo na seção de respostas. Eu farei o meu melhor :)

Recursos:

YOLO: https://arxiv.org/pdf/1506.02640.pdf

YOLOv2 e YOLO9000: https://arxiv.org/pdf/1612.08242.pdf

YOLOv3: https: //arxiv.org/pdf/1804.02767.pdf

Sobre mim

Sou especialista sênior em IA da Wavelabs.ai. Na Wavelabs, ajudamos você a aproveitar a Inteligência Artificial (AI) para revolucionar as experiências do usuário e reduzir custos. Aprimoramos exclusivamente seus produtos usando a IA para atingir todo o seu potencial de mercado. Tentamos trazer pesquisas de ponta para suas aplicações.

Sinta-se livre para explorar mais em Wavelabs.ai.

Bem, isso é tudo neste post. Obrigado pela leitura :)

Fique curioso!

Você pode entrar em contato comigo no LinkedIn.