Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

2
respostas

Faça como eu fiz: identificando objetos com MobileNetV2

import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.applications.mobilenet_v2 import (
MobileNetV2,
preprocess_input,
decode_predictions
)

from tensorflow.keras.preprocessing import image
from google.colab import files
modelo = MobileNetV2(weights='imagenet')

print("Modelo carregado com sucesso!")
uploaded = files.upload()

nome_arquivo = list(uploaded.keys())[0]
img = image.load_img(nome_arquivo)

plt.imshow(img)
plt.axis("off")
plt.show()
img = image.load_img(
nome_arquivo,
target_size=(224, 224)
)

img_array = image.img_to_array(img)
img_array = np.expand_dims(img_array, axis=0)
img_array = preprocess_input(img_array)
predicoes = modelo.predict(img_array)

resultado = decode_predictions(predicoes, top=3)[0]
print("\nTop 3 previsões:\n")

for i, (codigo, classe, confianca) in enumerate(resultado):
print(
f"{i+1}. Classe: {classe} "
f"- Confiança: {confianca*100:.2f}%"
)

print(
f"\nClasse identificada: "
f"{resultado[0][1]}"
)

2 respostas

Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5
14536120/14536120 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
Modelo carregado com sucesso!
01.jpg(image/jpeg) - 303223 bytes, last modified: 12/08/2012 - 100% done

(function(scope) {
function span(text, styleAttributes = {}) {
const element = document.createElement('span');
element.textContent = text;
for (const key of Object.keys(styleAttributes)) {
element.style[key] = styleAttributes[key];
}
return element;
}

// Max number of bytes which will be uploaded at a time.
const MAX_PAYLOAD_SIZE = 100 * 1024;

function _uploadFiles(inputId, outputId) {
const steps = uploadFilesStep(inputId, outputId);
const outputElement = document.getElementById(outputId);
// Cache steps on the outputElement to make it available for the next call
// to uploadFilesContinue from Python.
outputElement.steps = steps;

return _uploadFilesContinue(outputId);
}

function _uploadFilesContinue(outputId) {
const outputElement = document.getElementById(outputId);
const steps = outputElement.steps;

const next = steps.next(outputElement.lastPromiseValue);
return Promise.resolve(next.value.promise).then((value) => {
// Cache the last promise value to make it available to the next
// step of the generator.
outputElement.lastPromiseValue = value;
return next.value.response;
});
}

function* uploadFilesStep(inputId, outputId) {
const inputElement = document.getElementById(inputId);
inputElement.disabled = false;

const outputElement = document.getElementById(outputId);
outputElement.innerHTML = '';

const pickedPromise = new Promise((resolve) => {
inputElement.addEventListener('change', (e) => {
resolve(e.target.files);
});
});

const cancel = document.createElement('button');
inputElement.parentElement.appendChild(cancel);
cancel.textContent = 'Cancel upload';
const cancelPromise = new Promise((resolve) => {
cancel.onclick = () => {
resolve(null);
};
});

// Wait for the user to pick the files.
const files = yield {
promise: Promise.race([pickedPromise, cancelPromise]),
response: {
action: 'starting',
}
};

cancel.remove();

// Disable the input element since further picks are not allowed.
inputElement.disabled = true;

if (!files) {
return {
response: {
action: 'complete',
}
};
}

for (const file of files) {
const li = document.createElement('li');
li.append(span(file.name, {fontWeight: 'bold'}));
li.append(span(
(${file.type || 'n/a'}) - ${file.size} bytes, +
last modified: ${ file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() : 'n/a'} - ));
const percent = span('0% done');
li.appendChild(percent);

outputElement.appendChild(li);

const fileDataPromise = new Promise((resolve) => {
  const reader = new FileReader();
  reader.onload = (e) => {
    resolve(e.target.result);
  };
  reader.readAsArrayBuffer(file);
});
// Wait for the data to be ready.
let fileData = yield {
  promise: fileDataPromise,
  response: {
    action: 'continue',
  }
};

// Use a chunked sending to avoid message size limits. See b/62115660.
let position = 0;
do {
  const length = Math.min(fileData.byteLength - position, MAX_PAYLOAD_SIZE);
  const chunk = new Uint8Array(fileData, position, length);
  position += length;

  const base64 = btoa(String.fromCharCode.apply(null, chunk));
  yield {
    response: {
      action: 'append',
      file: file.name,
      data: base64,
    },
  };

  let percentDone = fileData.byteLength === 0 ?
      100 :
      Math.round((position / fileData.byteLength) * 100);
  percent.textContent = `${percentDone}% done`;

} while (position < fileData.byteLength);

}

// All done.
yield {
response: {
action: 'complete',
}
};
}

scope.google = scope.google || {};
scope.google.colab = scope.google.colab || {};
scope.google.colab._files = {
_uploadFiles,
_uploadFilesContinue,
};
})(self);
Saving 01.jpg to 01.jpg
1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 1s/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json
35363/35363 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step

Top 3 previsões:

  1. Classe: sunscreen - Confiança: 18.61%
  2. Classe: Band_Aid - Confiança: 6.12%
  3. Classe: jersey - Confiança: 3.24%

Classe identificada: sunscreen

Olá, Marcelo. Como vai?

Agora sim! Sua publicação traz a execução perfeita do pipeline de Visão Computacional utilizando a rede neural MobileNetV2 para classificação de imagens. O código cobriu todas as etapas essenciais do processamento digital: carregamento dos pesos pré-treinados da ImageNet, captura do upload via Google Colab, redimensionamento da imagem para o formato esperado pelo modelo e o tratamento dos canais de cores.

Analisando a saída do seu script, o modelo classificou o objeto com maior probabilidade como sunscreen (protetor solar), seguido por Band_Aid (curativo) e jersey (camisa/uniforme).

Gostaria de complementar o seu projeto trazendo uma explicação técnica sobre a importância de duas funções específicas que você utilizou e que são o segredo para o sucesso da classificação:

  • **O papel de target_size=(224, 224)**: Redes neurais convolucionais de arquitetura fixa, como a MobileNetV2, possuem camadas totalmente conectadas (Dense Layers) no final de sua estrutura. Essas camadas exigem uma quantidade exata de neurônios na entrada, o que significa que o modelo só consegue processar tensores que tenham rigorosamente as dimensões nas quais ele foi treinado. O redimensionamento garante essa compatibilidade física com a rede.
  • **A função de np.expand_dims(img_array, axis=0)**: Quando convertemos uma imagem colorida isolada para um array, ela possui três dimensões: altura, largura e canais de cor (RGB). No entanto, o TensorFlow trabalha com processamento em lote (batch processing). A função de expansão de dimensão adiciona um quarto eixo no início do tensor, transformando o formato de (224, 224, 3) para (1, 224, 224, 3). Esse 1 indica ao modelo que ele está processando um lote contendo exatamente uma imagem.

Como boa prática de codificação para os seus próximos experimentos de Visão Computacional, uma recomendação valiosa é isolar a lógica de predição em uma função reaproveitável. Isso evita que você tenha que rodar blocos soltos no notebook toda vez que quiser testar um arquivo novo.

Veja como encapsular essa estrutura de forma limpa:

def classificar_imagem_local(caminho_arquivo):
    # 1. Carrega e redimensiona
    img = image.load_img(caminho_arquivo, target_size=(224, 224))
    
    # 2. Transforma em array e adiciona a dimensão de batch
    img_array = image.img_to_array(img)
    img_array = np.expand_dims(img_array, axis=0)
    
    # 3. Normaliza os pixels conforme o padrão MobileNetV2
    img_array = preprocess_input(img_array)
    
    # 4. Executa a inferência e decodifica os resultados
    predicoes = modelo.predict(img_array)
    return decode_predictions(predicoes, top=3)[0]

# Uso prático simplificado:
# resultado = classificar_imagem_local(nome_arquivo)

A MobileNetV2 é uma excelente escolha para projetos reais devido ao seu design otimizado por convoluções separáveis em profundidade (depthwise separable convolutions), o que a torna leve o suficiente para rodar de forma eficiente até mesmo em dispositivos móveis e navegadores web.

Parabéns pela conclusão do desafio e por compartilhar todo o log do fluxo com a comunidade!

Espero que possa ter lhe ajudado!