MASK-RCNN para encontrar telhados a partir de imagens de drones


Em uma cidade branca e branca, em uma rua branca e branca, havia casas brancas e brancas ... E com que rapidez você consegue encontrar todos os telhados das casas nesta foto?

Cada vez mais, pode-se ouvir sobre os planos do governo de realizar um inventário completo de imóveis, a fim de esclarecer dados cadastrais. Para a solução primária para esse problema, um método simples pode ser aplicado com base no cálculo da área do telhado dos prédios principais a partir de fotografias aéreas e comparação adicional com dados cadastrais. Infelizmente, a pesquisa e o cálculo manuais levam muito tempo e, como novas casas são demolidas e construídas continuamente, o cálculo precisa ser repetido várias vezes. Surge imediatamente a hipótese de que esse processo pode ser automatizado usando algoritmos de aprendizado de máquina, em particular o Computer Vision. Neste artigo, falarei sobre como estamos na NORBIT resolveu esse problema e que dificuldades eles encontraram.

Spoiler - nós conseguimos . O serviço de ML desenvolvido é baseado em um modelo profundo de aprendizado de máquina baseado em redes neurais de convolução. O serviço aceita imagens de veículos aéreos não tripulados como entrada, gera um arquivo GeoJSON com o layout dos objetos de construção de capital encontrados com coordenadas geográficas na saída.

Como resultado, fica assim:


Problemas


Vamos começar com os problemas técnicos que encontramos:

  • existe uma diferença significativa entre as fotografias aéreas de inverno e de verão (um modelo treinado apenas em fotografias de verão é completamente incapaz de encontrar telhados no inverno);
  • , ;
  • , ( ), ( ) , ;
  • , , ( ). ;
  • (, ) .

E os drones às vezes trazem essas fotos:


Eu também gostaria de observar os problemas que poderiam ter sido, mas eles não nos diziam respeito:

  • não tivemos a tarefa de realizar inferência por um tempo limitado (por exemplo, diretamente no momento do voo), o que resolveu imediatamente todos os possíveis problemas com o desempenho;
  • na entrada para processamento, imediatamente recebemos imagens de alta qualidade e alta resolução (usando lentes com uma distância focal de 21 mm a uma altura de 250 m, que é de 5 cm / px) de nosso cliente, a empresa Shakhty, poderia usar seus conhecimentos na localização geográfica de objetos em mapas e eles também tiveram a oportunidade de estabelecer um conjunto específico de requisitos para futuros voos de UAV, o que reduziu bastante a probabilidade de peças muito exclusivas que não estavam no conjunto de treinamento;

A primeira solução para o problema, traçado usando a caixa Limite 


Algumas palavras sobre quais ferramentas usamos para criar a solução.

  • O Anaconda é um sistema conveniente de gerenciamento de pacotes para Python e R.
  • O Tensorflow é uma biblioteca de software de aprendizado de máquina de código aberto desenvolvida pelo Google.
  • Keras é um complemento para os frameworks Deeplearning4j, TensorFlow e Theano.
  • O OpenCV é uma biblioteca de algoritmos para visão computacional, processamento de imagens e algoritmos numéricos de código aberto de uso geral.
  • Flask é uma estrutura para criar aplicativos da Web na linguagem de programação Python.

Como o sistema operacional usava o Ubuntu 18.04. Com os drivers na GPU (NVIDIA) no Ubuntu, tudo está em ordem; portanto, a tarefa geralmente é resolvida com um comando:

> sudo apt install nvidia-cuda-toolkit

Preparação de Azulejos


A primeira tarefa que enfrentamos foi dividir as imagens de sobrevôo em blocos (2048x2048 px). Você pode escrever seu próprio script, mas terá que pensar em manter a localização geográfica de cada bloco. Era mais fácil usar uma solução pronta, por exemplo, GeoServer - é um software de código aberto que permite publicar dados geográficos no servidor. Além disso, o GeoServer resolveu outro problema para nós - a exibição conveniente do resultado da marcação automática no mapa. Isso pode ser feito localmente, por exemplo, no qGIS, mas para um comando e demonstração distribuídos, um recurso da Web é mais conveniente.

Para executar lado a lado, você precisa especificar a escala e o tamanho necessários nas configurações.


Para traduções entre sistemas de coordenadas, usamos a biblioteca pyproj:

from pyproj import Proj, transform

class Converter:
    P3857 = Proj(init='epsg:3857')
    P4326 = Proj(init='epsg:4326')
...
    def from_3857_to_GPS(self, point):
        x, y = point
        return transform(self.P3857, self.P4326, x, y)
    def from_GPS_to_3857(self, point):
        x, y = point
        return transform(self.P4326, self.P3857, x, y)
...

Como resultado, foi possível formar facilmente uma grande camada de todos os polígonos e colocá-la sobre o substrato. 


Para instalar o software GeoServer, você deve concluir as seguintes etapas.
  1. Java 8.
  2. GeoServer.
  3. , , /usr/share/geoserver
  4.  

    echo «export GEOSERVER_HOME=/usr/share/geoserver» >> ~/.profile
  5. :

    sudo groupadd geoserver
  6. , , :

    sudo usermod -a -G geoserver <user_name>
  7. - :

    sudo chown -R :geoserver /usr/share/geoserver/
  8. :

    sudo chmod -R g+rwx /usr/share/geoserver/
  9. GeoServer 

    cd geoserver/bin && sh startup.sh

O GeoServer não é o único aplicativo que nos permite resolver nosso problema. Como alternativa, por exemplo, você pode considerar o ArcGIS for Server, mas este produto é proprietário, portanto não o usamos.

Em seguida, cada telha tinha que encontrar todos os telhados visíveis. A primeira abordagem para resolver o problema foi usar a detecção de objeto do conjunto de modelos / pesquisa Tensorflow. Dessa maneira, as classes nas imagens podem ser encontradas e localizadas com uma seleção retangular (caixa delimitadora). 

Marcação de dados de treinamento 


Obviamente, para treinar o modelo, você precisa de um conjunto de dados rotulado. Por uma coincidência de sorte, além de circular, em nossos compartimentos o conjunto de dados para 50 mil telhados foi preservado desde os bons velhos tempos, quando todos os conjuntos de dados para treinamento ainda eram de domínio público em todos os lugares.

É difícil prever com antecedência o tamanho exato da amostra de treinamento necessária para obter uma precisão aceitável do modelo. Pode variar de acordo com a qualidade das imagens, seu grau de dissimilaridade entre si e as condições em que o modelo será usado na produção. Tivemos casos em que 200 peças foram suficientes e, às vezes, 50 mil amostras marcadas também estavam faltando. No caso de falta de imagens marcadas, geralmente adicionamos métodos de aumento: curvas, reflexos no espelho, classificação de cores, etc.

Agora, existem muitos serviços disponíveis que permitem marcar imagens - tanto com código-fonte aberto para instalação em seu computador / servidor quanto com soluções corporativas, incluindo o trabalho de avaliadores externos, como o Yandex.Toloka. Neste projeto, usamos o mais simples VGG Image Annotator . Como alternativa, você pode tentar anotador de coco ou estúdio de etiqueta . Normalmente, usamos o último para marcar arquivos de texto e áudio.


Para treinar a marcação de vários anotadores, geralmente é necessário executar uma pequena troca de campos, um exemplo para o VGG .

Para calcular corretamente a área do telhado que caiu na área de alocação retangular, é necessário observar várias condições:

  • / . :


  • , , :


Para resolver o segundo problema, você pode tentar treinar um modelo separado que determine o ângulo de rotação correto do bloco para marcação, mas tudo ficou um pouco mais fácil. As próprias pessoas se esforçam para reduzir a entropia, para alinhar todas as estruturas criadas pelo homem em relação umas às outras, especialmente com edifícios densos. Se você olhar de cima, em uma área localizada, cercas, passarelas, plantações, estufas e mandris serão paralelos ou perpendiculares aos limites dos telhados. Resta apenas encontrar todas as linhas claras e calcular o ângulo de inclinação mais comum em relação à vertical. Para isso, o OpenCV possui uma ótima ferramenta HoughLinesP. 

...

lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength=minLineLength, maxLineGap=5)
if lines is not None:
    length = image.shape[0]
    angles = []
    for x1, y1, x2, y2 in lines[0]:
        angle = math.degrees(math.atan2(y2 — y1, x2 - x1))
        angles.append(angle)
    parts_angles.append(angles)
    median_angle = np.median(angles)
...

#     

for x in range(0, image.shape[0]-1, image.shape[0] // count_crops):
    for y in range(0, image.shape[1]-1, image.shape[1] // count_crops):
        get_line(image[x:x+image.shape[0]//count_crops, y:y+image.shape[1]//count_crops, :])
...

#      

np.median([a if a>0 else 90+a for a in np.array(parts_angles).flatten()])

Depois de encontrar o ângulo, giramos a imagem usando a transformação afim:


h, w = image.shape[:2]
image_center = (w/2, h/2)

if size is None:
    radians = math.radians(angle)
    sin = math.sin(radians)
    cos = math.cos(radians)
    size = (int((h * abs(sin)) + (w * abs(cos))), int((h * abs(cos)) + (w * abs(sin))))
    rotation_matrix = cv2.getRotationMatrix2D(image_center, angle, 1)
    rotation_matrix[0, 2] += ((size[0] / 2) — image_center[0])
    rotation_matrix[1, 2] += ((size[1] / 2) — image_center[1])
else:
    rotation_matrix = cv2.getRotationMatrix2D(image_center, angle, 1)

cv2.warpAffine(image, rotation_matrix, size)

O código de exemplo completo está aqui . Aqui está o que parece:



O método de transformar ladrilhos e marcar com retângulos funciona mais rápido do que com máscaras, quase todos os telhados são encontrados, mas na produção esse método é usado apenas como auxiliar devido a várias desvantagens:

  • há muitos sobrevôos em que há um grande número de telhados não retangulares, por isso há muito trabalho manual para refinar a área,
  • às vezes encontrado em casa com orientações diferentes no mesmo bloco,
  • Às vezes, existem muitas linhas falsas nos ladrilhos, o que acaba levando a uma curva errada. Se parece com isso:



A solução final baseada em Mask-RCNN


A segunda tentativa foi procurar e destacar os telhados por máscaras pixel por pixel e, em seguida, delinear automaticamente os contornos das máscaras encontradas e criar polígonos vetoriais.  

Já existem materiais suficientes sobre os princípios de operação, tipos e tarefas de redes neurais convolucionais, incluindo as em russo, por isso não iremos abordá-las neste artigo. Vamos nos concentrar apenas em uma implementação específica, Mask-RCNN - uma arquitetura para localizar e destacar os contornos dos objetos nas imagens. Existem outras soluções excelentes com suas vantagens e desvantagens, por exemplo, UNet, mas foi possível obter melhor qualidade no Mask-RCNN.

No processo de seu desenvolvimento, passou por várias etapas. A primeira versão do R-CNN foi desenvolvida em 2014. O princípio de seu trabalho é destacar pequenas áreas na imagem, para cada uma das quais é feita uma estimativa da probabilidade da presença de um objeto alvo nessa área. A R-CNN fez um excelente trabalho com a tarefa, mas sua velocidade deixou muito a desejar. O desenvolvimento lógico foram as redes Fast R-CNN e Faster R-CNN, que receberam melhorias no algoritmo de rastreamento de imagem, o que permitiu aumentar significativamente a velocidade. Na saída para o Faster R-CNN, aparece uma marcação com uma seleção retangular indicando os limites do objeto, o que nem sempre é suficiente para resolver o problema. 

A máscara R-CNN também adiciona uma sobreposição de máscara pixel por pixel para obter o contorno exato do objeto.

A caixa delimitadora e as máscaras podem ser vistas claramente no resultado da operação do modelo (o filtro pela área mínima de construção está ativado):


Convencionalmente, existem 4 estágios na operação desta rede:

  • padrão para todas as redes neurais convolucionais, a alocação de recursos na imagem, como linhas, curvas, limites contrastantes e outros;
  • A Region Proposal Network (RPN) varre pequenos fragmentos da imagem, chamados âncoras (âncoras) e determina se essa âncora contém sinais específicos para a classe de destino (no nosso caso, o telhado);
  • Classificação da região de interesse e caixa delimitadora. Nesse estágio, a rede, com base nos resultados do estágio anterior, está tentando destacar grandes áreas retangulares na fotografia, presumivelmente contendo o objeto de destino;
  • Máscaras de segmentação. Nesse estágio, a máscara do objeto desejado é obtida da área retangular obtida pela aplicação da caixa de limite.

Além disso, a rede mostrou-se muito flexível na configuração e fomos capazes de reconstruí-la para processar imagens com camadas de informações adicionais.

O uso de imagens exclusivamente RGB não nos permitiu obter a precisão de reconhecimento necessária (o modelo perdeu edifícios inteiros, houve um erro médio de 15% no cálculo da área do telhado); portanto, fornecemos ao modelo dados úteis adicionais, como mapas de altura obtidos por fotogrametria. 


Métricas usadas para avaliar a qualidade do modelo


Ao determinar a qualidade dos modelos, geralmente usamos a métrica Intersecção sobre União (IoU)


Código de exemplo para calcular a IoU usando a biblioteca geometry.shapely:

from shapely.geometry import Polygon

true_polygon = Polygon([(2, 2), (2, 6), (5, 6), (5, 2)])
predicted_polygon = Polygon([(3, 3), (3, 7), (6, 7), (6, 3)])
print(true_polygon.intersection(predicted_polygon).area / true_polygon.union(predicted_polygon).area)

>>> 0.3333333333333333

O rastreamento do processo de treinamento de modelos é convenientemente controlado usando o Tensorboard, uma ferramenta de controle métrico conveniente que permite receber dados em tempo real sobre a qualidade do modelo e compará-los com outros modelos.


O Tensorboard fornece dados sobre muitas métricas diferentes. Os mais interessantes para nós são:

  • val_mrcnn_bbox_loss - mostra quão bem o modelo localiza objetos (ou seja, impõe uma caixa de limite);
  • val_mrcnn_mask_loss - mostra o quão bem o modelo segmenta objetos (ou seja, impõe uma máscara).

Treinamento e validação de modelos


Durante o treinamento, usamos a prática padrão de dividir aleatoriamente um conjunto de dados em 3 partes - treinamento, validação e teste. No processo de aprendizado, a qualidade do modelo é avaliada em uma amostra de validação e, após a conclusão, passa no teste final nos dados de teste que foram fechados no processo de aprendizado. 

Iniciamos nosso primeiro treinamento com um pequeno conjunto de fotos de verão e, decidindo como o modelo será bom no inverno, esperamos receber um resultado decepcionante. A opção de usar modelos diferentes para estações diferentes, é claro, é uma excelente maneira de sair da situação, mas isso acarretaria uma série de inconvenientes, por isso decidimos tentar universalizar o modelo. Ao experimentar diferentes configurações das camadas e também fechar o peso de camadas individuais devido a alterações de peso, descobrimos a estratégia ideal para treinar o modelo aplicando alternadamente imagens de verão e inverno na entrada.

Criando um serviço em segundo plano para reconhecimento


Agora que temos um modelo em funcionamento, podemos criar um serviço de API em segundo plano a partir de um script de reconhecimento que obtém uma imagem como entrada e gera json com polígonos de telhado encontrados na saída. Isso não afeta diretamente a solução do problema, mas pode ser útil para alguém. 

O Ubuntu usa systemd, e um exemplo será dado especificamente para este sistema. O código do serviço em si pode ser visto aqui . As unidades de usuário estão localizadas no diretório / etc / systemd / system, onde criaremos nosso arquivo de serviço. Edite o arquivo:

cd /etc/systemd/system

sudo touch my_srv.service




sudo vim my_srv.service

A unidade systemd consiste em três seções:

  • [Unidade] - descreve a ordem e condição da partida (por exemplo, você pode dizer ao processo para aguardar o início de um determinado serviço e só então iniciá-lo você mesmo);
  • [Serviço] - descreve parâmetros de inicialização;
  • [Instalar] - descreve o comportamento do serviço ao adicioná-lo à inicialização.

Como resultado, nosso arquivo ficará assim:

[Unit]
Description=my_test_unit

[Service]
WorkingDirectory=/home/user/test_project
User=root
ExecStart=/home/user/test_project/venv/bin/python3 /home/user/test_project/script.py

[Install]
WantedBy=multi-user.target

Agora recarregue a configuração do systemd e execute nosso serviço:

sudo systemctl daemon-reload
sudo systemctl start my_srv.service

Este é um exemplo simples de um processo em segundo plano, o systemd suporta muitos parâmetros diferentes que permitem configurar de forma flexível o comportamento do serviço, mas nada mais complicado é necessário para a nossa tarefa.

achados


O principal resultado do projeto foi a capacidade de detectar automaticamente inconsistências no desenvolvimento real e nas informações contidas nos dados cadastrais.

Como resultado da avaliação da precisão do modelo nos dados de teste, foram obtidos os seguintes valores: número de telhados encontrados - 91%, precisão dos polígonos do contorno do telhado - 94%.

Foi possível obter uma qualidade aceitável dos modelos nos voos de verão e inverno, mas a qualidade do reconhecimento pode diminuir nas imagens imediatamente após uma queda de neve.

Agora, nem mesmo a Ópera de Sydney escapará dos olhos da nossa modelo. 


Planejamos colocar esse serviço com um modelo treinado em nosso desmembramento. Se você estiver interessado em experimentar o serviço com suas próprias fotos, envie aplicativos para ai@norbit.ru.

All Articles