Estou trabalhando no reconhecimento de vários dígitos Java
, usando a OpenCV
biblioteca para pré-processamento e segmentação, e um Keras
modelo treinado no MNIST (com precisão de 0,98) para reconhecimento.
O reconhecimento parece funcionar muito bem, além de uma coisa. A rede geralmente falha em reconhecer esses (número "um"). Não consigo descobrir se isso ocorre devido ao pré-processamento / implementação incorreta da segmentação ou se uma rede treinada no MNIST padrão simplesmente não viu o número um que se parece com os meus casos de teste.
Veja como são os dígitos problemáticos após o pré-processamento e a segmentação:
torna - se e é classificado como 4
.
torna - se e é classificado como 7
.
torna - se e é classificado como 4
. E assim por diante...
Isso pode ser corrigido com a melhoria do processo de segmentação? Ou melhor, aprimorando o conjunto de treinamento?
Editar: o aprimoramento do conjunto de treinamento (aumento de dados) definitivamente ajudaria, que eu já estou testando, a questão do pré-processamento correto ainda permanece.
Meu pré-processamento consiste em redimensionar, converter em escala de cinza, binarização, inversão e dilatação. Aqui está o código:
Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);
Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);
Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);
Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);
Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);
A imagem pré-processada é então segmentada em dígitos individuais da seguinte maneira:
List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
// code to sort contours
// code to check that contour is a valid char
List rects = new ArrayList<>();
for (MatOfPoint contour : contours) {
Rect boundingBox = Imgproc.boundingRect(contour);
Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);
rects.add(rectCrop);
}
for (int i = 0; i < rects.size(); i++) {
Rect x = (Rect) rects.get(i);
Mat digit = new Mat(preprocessed, x);
int border = 50;
Mat result = digit.clone();
Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));
Imgproc.resize(result, result, new Size(28, 28));
digits.add(result);
}
fonte
Respostas:
Acredito que seu problema é o processo de dilatação. Entendo que você deseja normalizar os tamanhos de imagem, mas não deve quebrar as proporções, redimensionar para o máximo desejado por um eixo (aquele que permite maior redimensionamento sem permitir que outra dimensão de eixo exceda o tamanho máximo) e preencha com a cor de fundo, o resto da imagem. Não é que "o MNIST padrão simplesmente não tenha visto o número um que se parece com os seus casos de teste", você faz suas imagens parecerem com números treinados diferentes (aqueles que são reconhecidos)
Se você manteve a proporção correta de suas imagens (de origem e pós-processada), poderá ver que não apenas redimensionou a imagem, mas a "distorceu". Pode ser o resultado de dilatação não homogênea ou redimensionamento incorreto
fonte
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);
. Aqui a relação de aspecto permanece a mesma, onde eu quebro as proporções?Já existem algumas respostas postadas, mas nenhuma delas responde à sua pergunta real sobre pré-processamento de imagem .
Por minha vez, não vejo problemas significativos com sua implementação, desde que seja um projeto de estudo, bem feito.
Mas uma coisa a notar você pode perder. Existem operações básicas em morfologia matemática: erosão e dilatação (usadas por você). E há operações complexas: várias combinações de operações básicas (por exemplo, abertura e fechamento). O link da Wikipedia não é a melhor referência de currículo, mas você pode começar com ele para ter uma idéia.
Geralmente, é melhor usar abertura em vez de erosão e fechamento em vez de dilatação, pois nesse caso a imagem binária original muda muito menos (mas é atingido o efeito desejado de limpar bordas afiadas ou preencher lacunas). Portanto, no seu caso, você deve verificar o fechamento (dilatação da imagem seguida de erosão com o mesmo núcleo). Caso a imagem extra-pequena 8 * 8 seja bastante modificada quando você dilatar mesmo com o núcleo 1 * 1 (1 pixel é mais do que 16% da imagem), o que é menor nas imagens maiores).
Para visualizar a ideia, veja as seguintes fotos (dos tutoriais do OpenCV: 1 , 2 ):
dilatação:
fechamento:
Espero que ajude.
fonte
Portanto, você precisa de uma abordagem complexa, pois cada etapa de sua cascata de computação se baseia nos resultados anteriores. No seu algoritmo, você tem os seguintes recursos:
Como mencionado anteriormente, se você aplicar o redimensionamento, perderá informações sobre as proporções da imagem. É necessário fazer o mesmo reprocessamento de imagens de dígitos para obter os mesmos resultados implícitos no processo de treinamento.
Melhor maneira, se você apenas recortar a imagem em imagens de tamanho fixo. Nessa variante, você não precisará de contornos para localizar e redimensionar a imagem do dígito antes do processo de treinamento. Em seguida, você pode fazer uma pequena alteração no algoritmo de corte para melhor reconhecimento: encontre o contorno e coloque seu dígito sem redimensionar no centro do quadro de imagem relevante para reconhecimento.
Além disso, você deve prestar mais atenção ao algoritmo de binarização. Tive experiência estudando o efeito dos valores limite de binarização no erro de aprendizado: posso dizer que esse é um fator muito significativo. Você pode tentar outros algoritmos de binarização para verificar essa ideia. Por exemplo, você pode usar esta biblioteca para testar algoritmos alternativos de binarização.
Para melhorar a qualidade do reconhecimento, você usa a validação cruzada no processo de treinamento. Isso ajuda a evitar o problema de ajuste excessivo nos dados de treinamento. Por exemplo, você pode ler este artigo, onde explicou como usá-lo com o Keras.
Às vezes, taxas mais altas de medida de precisão não dizem nada sobre a qualidade real do reconhecimento, pois a RNA treinada não encontrou o padrão nos dados de treinamento. Ele pode estar conectado ao processo de treinamento ou ao conjunto de dados de entrada, conforme explicado acima, ou pode causar a escolha da arquitetura da ANN.
É um grande problema. Como definir a melhor arquitetura de RNA para resolver a tarefa? Não há maneiras comuns de fazer isso. Mas existem algumas maneiras de se aproximar do ideal. Por exemplo, você pode ler este livro . Isso ajuda você a ter uma visão melhor do seu problema. Além disso, você pode encontrar aqui algumas fórmulas heurísticas para ajustar o número de camadas / elementos ocultos para sua RNA. Também aqui você encontrará uma pequena visão geral sobre isso.
Espero que isso ajude.
fonte
Após algumas pesquisas e experimentos, cheguei à conclusão de que o pré-processamento da imagem em si não era o problema (alterei alguns parâmetros sugeridos, como por exemplo, tamanho e forma da dilatação, mas eles não eram cruciais para os resultados). O que ajudou, no entanto, foram duas coisas a seguir:
Como o @ f4f notou, eu precisava coletar meu próprio conjunto de dados com dados do mundo real. Isso já ajudou tremendamente.
Fiz alterações importantes no meu pré-processamento de segmentação. Depois de obter contornos individuais, primeiro normalizo o tamanho das imagens para caber em uma
20x20
caixa de pixels (como estãoMNIST
). Depois disso, centralizo a caixa no meio da28x28
imagem usando o centro de massa (que para imagens binárias é o valor médio em ambas as dimensões).Obviamente, ainda existem casos de segmentação difíceis, como sobreposição ou dígitos conectados, mas as alterações acima responderam à minha pergunta inicial e melhoraram meu desempenho de classificação.
fonte