Na
série anterior, conduzi um experimento com o movimento autônomo do
meu tanque doméstico . A estrada foi reconhecida usando um filtro de cores e a máscara resultante foi para a entrada de uma rede neural classificadora especialmente treinada, que optou por ir para a direita, esquerda ou reta.
O ponto fraco foi o reconhecimento da própria rodovia devido à variabilidade das tonalidades das cores, pelas quais a rede neural de tomada de decisão produziu resultados estranhos. Os comentários sobre esse artigo recomendaram prestar atenção à segmentação semântica. O tópico mostrou-se promissor e o uso de redes neurais segmentadas trouxe suas vantagens, mas também suas desvantagens, onde estaria sem elas.
Mas as primeiras coisas primeiro e um pouco de equipamento.
Segmentação
A segmentação é o processo de destacar algumas partes de uma imagem. O tipo mais simples e óbvio de segmentação é a cor. No entanto, usando esse método, é impossível entender o que e onde está representado na figura.
Aqui está um
bom artigo que descreve abordagens primitivas.
Segmentação semântica
Segmentação semântica - dividindo uma imagem em objetos com a determinação dos tipos desses objetos.
Parece algo como isto:

Os resultados são muito impressionantes, vamos ver o que vale a pena traduzir para a vida real.
U-net
A rede neural mais famosa, originalmente desenvolvida para medicina.
Fonte primáriaAs pessoas rapidamente perceberam que a abordagem pode ser usada para todas as ocasiões.
Existem muitos artigos na Internet sobre como preparar dados e treinar redes U-net:
No entanto, não encontrei uma rede U-net pronta para fazer e experimentar rapidamente.
E-net
Uma rede mais jovem e menos conhecida. Projetado apenas para reconhecer as ruas da cidade.
Dados
Os conjuntos de dados mais populares para segmentação de ruas (eles ensinaram inicialmente a E-net):
Nos mesmos conjuntos de dados, o U-net está sendo treinado.
Escolha de implementação
A enxurrada de novas informações sobre segmentação foi bastante esmagadora. Instintivamente, eu queria entender algo mais simples. Não senti o zen interno para entender a arquitetura das redes e passar um tempo aprendendo. Mas no artigo da
PyImageSearch havia uma rede neural pronta e treinada, além disso, em um formato compatível com o OpenCV-DNN.
Portanto, a escolha foi feita em direção à menor resistência.
O uso é muito simples:
(O mais preocupante é que a rede é treinada em fotos de 1024x512 - isto é, primeiro, mais do que a câmera oferece no Raspberry e, segundo, o desempenho necessário para processar essa quantidade de dados é um pouco confuso. Como resultado, o principal problema será exatamente isso).
Lemos a rede neural a partir dos arquivos (em um, o próprio modelo, nos outros nomes de classe, nas terceiras cores).
def load_segment_model(): try: classes = None with open(PiConf.SEGMENT_CLASSES) as f: classes = f.read().strip().split("\n") colors = None with open(PiConf.SEGMENT_COLORS) as f: colors= f.read().strip().split("\n") colors = [np.array(c.split(",")).astype("int") for c in colors] colors = np.array(colors, dtype="uint8") print("[INFO] loading model...") net = cv2.dnn.readNet(PiConf.SEGMENT_MODEL) return net, classes, colors except Exception as e: logging.exception("Cannot load segment model") return None, None, None
Segmentamos a imagem, marcando simultaneamente segmentos na parte superior da imagem original
(No meu caso, todas as classes, exceto a estrada, são invisíveis).
def segment_image(image_path, seg_net, seg_classes, seg_colors): image0 = cv2.imread(image_path) image = cv2.resize(image0, (1024, 512),interpolation=cv2.INTER_NEAREST) blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (1024, 512), 0, swapRB=True, crop=False) seg_net.setInput(blob) start = time.time() output = seg_net.forward() end = time.time() print("[INFO] inference took {:.4f} seconds".format(end - start)) (numClasses, height, width) = output.shape[1:4] classMap = np.argmax(output[0], axis=0) mask = seg_colors[classMap] mask = cv2.resize(mask, (image0.shape[1], image0.shape[0]),interpolation=cv2.INTER_NEAREST) classMap = cv2.resize(classMap, (image0.shape[1], image0.shape[0]), interpolation=cv2.INTER_NEAREST) gmask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) gmask = cv2.resize(gmask, (128, 64), interpolation=cv2.INTER_NEAREST) gmask = gmask[0:64,32:96] output = ((0.6 * image0) + (0.4 * mask)).astype("uint8") return output, gmask
Verifique
Tiramos fotos prontas do tanque e montamos uma rede neural segmentada sobre eles.
1

Somente o lado esquerdo da calçada é reconhecido como caro.
Comprimimos a imagem e tiramos dela um tamanho central de 64x64:
(Esse tamanho é esperado pela rede neural, que decide mudar de direção)

A rede neural da direção (de fato - o classificador) comanda para levar para a esquerda. Não muito correto, mas suportável.
2

Uma situação semelhante, novamente, o canto inferior direito é perdido (também há asfalto molhado).
No entanto, a maior parte da estrada ainda é reconhecida.

O classificador se oferece para seguir em frente.
3
A situação em que o robô estava no meio da calçada.

A estrada é reconhecida quase perfeitamente.

O classificador ordena que você vá para a direita (para encontrar a beira da estrada na próxima vez).
Aplicação
Tendo conjurado um pouco sobre o firmware do tanque, substituí o detector de estrada colorido por uma rede neural segmentada.
Ao lançar tudo isso no Raspberry Pi, a primeira coisa que saiu foi uma queda no desempenho.
Leva 6 segundos para segmentar uma imagem - durante esse período, o tanque consegue deslizar por todas as curvas com um trote vigoroso.
Em testes reais, isso aconteceu - apesar do reconhecimento quase perfeito da calçada e dos comandos corretos da rede neural de controle - durante o tempo em que a imagem foi processada, o tanque conseguiu se afastar.

Em geral, imagens desse tamanho não podem ser digeridas no Raspberry.
Parece que você ainda precisa fazer o treinamento de uma rede neural especializada.
Referências