OpenCV C ++ / Obj-C: Detectando uma folha de papel / detecção de quadrados

Implementei com sucesso o exemplo de detecção de quadrado do OpenCV em meu aplicativo de teste, mas agora preciso filtrar a saída, porque é um pouco confuso – ou meu código está errado?

Estou interessado nos quatro pontos de canto do papel para redução de distorção (assim) e processamento adicional …

Entrada e saída: Entrada e saída

Imagem original:

clique

Código:

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) { double dx1 = pt1.x - pt0.x; double dy1 = pt1.y - pt0.y; double dx2 = pt2.x - pt0.x; double dy2 = pt2.y - pt0.y; return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); } - (std::vector<std::vector >)findSquaresInImage:(cv::Mat)_image { std::vector<std::vector > squares; cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray; int thresh = 50, N = 11; cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2)); cv::pyrUp(pyr, timg, _image.size()); std::vector<std::vector > contours; for( int c = 0; c < 3; c++ ) { int ch[] = {c, 0}; mixChannels(&timg, 1, &gray0, 1, ch, 1); for( int l = 0; l = (l+1)*255/N; } cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); std::vector approx; for( size_t i = 0; i  1000 && cv::isContourConvex(cv::Mat(approx))) { double maxCosine = 0; for( int j = 2; j < 5; j++ ) { double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); maxCosine = MAX(maxCosine, cosine); } if( maxCosine < 0.3 ) { squares.push_back(approx); } } } } } return squares; } 

EDIT 17/08/2012:

Para desenhar os quadrados detectados na imagem, use este código:

 cv::Mat debugSquares( std::vector<std::vector > squares, cv::Mat image ) { for ( int i = 0; i< squares.size(); i++ ) { // draw contour cv::drawContours(image, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector(), 0, cv::Point()); // draw bounding rect cv::Rect rect = boundingRect(cv::Mat(squares[i])); cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0); // draw rotated rect cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i])); cv::Point2f rect_points[4]; minRect.points( rect_points ); for ( int j = 0; j < 4; j++ ) { cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue } } return image; } 

Este é um assunto recorrente no Stackoverflow e, como não consegui encontrar uma implementação relevante, decidi aceitar o desafio.

Fiz algumas modificações na demo de quadrados presente no OpenCV e o código C ++ resultante abaixo é capaz de detectar uma folha de papel na imagem:

 void find_squares(Mat& image, vector >& squares) { // blur will enhance edge detection Mat blurred(image); medianBlur(image, blurred, 9); Mat gray0(blurred.size(), CV_8U), gray; vector > contours; // find squares in every color plane of the image for (int c = 0; c < 3; c++) { int ch[] = {c, 0}; mixChannels(&blurred, 1, &gray0, 1, ch, 1); // try several threshold levels const int threshold_level = 2; for (int l = 0; l < threshold_level; l++) { // Use Canny instead of zero threshold level! // Canny helps to catch squares with gradient shading if (l == 0) { Canny(gray0, gray, 10, 20, 3); // // Dilate helps to remove potential holes between edge segments dilate(gray, gray, Mat(), Point(-1,-1)); } else { gray = gray0 >= (l+1) * 255 / threshold_level; } // Find contours and store them in a list findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // Test contours vector approx; for (size_t i = 0; i < contours.size(); i++) { // approximate contour with accuracy proportional // to the contour perimeter approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true); // Note: absolute value of an area is used because // area may be positive or negative - in accordance with the // contour orientation if (approx.size() == 4 && fabs(contourArea(Mat(approx))) > 1000 && isContourConvex(Mat(approx))) { double maxCosine = 0; for (int j = 2; j < 5; j++) { double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); maxCosine = MAX(maxCosine, cosine); } if (maxCosine < 0.3) squares.push_back(approx); } } } } } 

Após este procedimento ser executado, a folha de papel será o maior quadrado no vector > :

detecção de folha de papel opencv

Eu estou deixando você escrever a function para encontrar o maior quadrado. 😉

A menos que haja algum outro requisito não especificado, eu simplesmente converteria sua imagem colorida em escala de cinza e trabalharia apenas com isso (sem necessidade de trabalhar nos 3 canais, o contraste presente já é muito alto). Além disso, a menos que haja algum problema específico com relação ao redimensionamento, eu trabalharia com uma versão reduzida de suas imagens, já que elas são relativamente grandes e o tamanho não adiciona nada ao problema que está sendo resolvido. Então, finalmente, seu problema é resolvido com um filtro mediano, algumas ferramentas morfológicas básicas e statistics (principalmente para o limiar Otsu, que já foi feito para você).

Aqui está o que eu obtenho com sua imagem de amostra e alguma outra imagem com uma folha de papel que encontrei em volta:

insira a descrição da imagem aquiinsira a descrição da imagem aqui

O filtro mediano é usado para remover pequenos detalhes da imagem, agora em escala de cinza. Ele possivelmente removerá linhas finas dentro do papel esbranquiçado, o que é bom, porque você terminará com pequenos componentes conectados que são fáceis de descartar. Após a mediana, aplique um gradiente morfológico (simplesmente dilationerosion ) e binarize o resultado por Otsu. O gradiente morfológico é um bom método para manter bordas fortes, deve ser usado mais. Então, como esse gradiente aumentará a largura do contorno, aplique um afinamento morfológico. Agora você pode descartar componentes pequenos.

Neste ponto, aqui está o que temos com a imagem à direita acima (antes de desenhar o polígono azul), a esquerda não é mostrada porque o único componente restante é aquele que descreve o papel:

insira a descrição da imagem aqui

Dados os exemplos, agora a única questão que resta é distinguir entre componentes que se parecem com retângulos e outros que não. Esta é uma questão de determinar uma relação entre a área do casco convexo que contém a forma e a área de sua checkbox delimitadora; a proporção 0.7 funciona bem para esses exemplos. Pode ser que você também precise descartar componentes que estão dentro do papel, mas não nestes exemplos usando este método (no entanto, fazer esta etapa deve ser muito fácil, especialmente porque pode ser feito diretamente através do OpenCV).

Para referência, aqui está um exemplo de código no Mathematica:

 f = Import["http://thwartedglamour.files.wordpress.com/2010/06/my-coffee-table-1-sa.jpg"] f = ImageResize[f, ImageDimensions[f][[1]]/4] g = MedianFilter[ColorConvert[f, "Grayscale"], 2] h = DeleteSmallComponents[Thinning[ Binarize[ImageSubtract[Dilation[g, 1], Erosion[g, 1]]]]] convexvert = ComponentMeasurements[SelectComponents[ h, {"ConvexArea", "BoundingBoxArea"}, #1 / #2 > 0.7 &], "ConvexVertices"][[All, 2]] (* To visualize the blue polygons above: *) Show[f, Graphics[{EdgeForm[{Blue, Thick}], RGBColor[0, 0, 1, 0.5], Polygon @@ convexvert}]] 

Se existem situações mais variadas em que o retângulo do papel não é tão bem definido, ou a abordagem o confunde com outras formas – essas situações podem acontecer devido a várias razões, mas uma causa comum é a má aquisição de imagens – tente combinar – etapas de processamento com o trabalho descrito no documento “Detecção de Retângulos baseado em uma Transformação Hough Windowed”.

Estou atrasado.


Na sua imagem, o papel é white , enquanto o fundo é colored . Então, é melhor detectar o papel é o canal de Saturation(饱和度) no HSV color space . Tome consulte primeiro o wiki HSL_and_HSV . Então copio a maior parte da ideia da minha resposta neste Detectar Segmento Colorido em uma imagem .


Passos principais:

  1. Leia no BGR
  2. Converta a imagem do bgr para o espaço hsv
  3. Limiar o canal S
  4. Então encontre o contorno externo máximo (ou faça Canny , ou HoughLines como quiser, eu escolho findContours ), aproximadamente para obter os cantos.

Este é o meu resultado:

insira a descrição da imagem aqui


O código Python (Python 3.5 + OpenCV 3.3):

 #!/usr/bin/python3 # 2017.12.20 10:47:28 CST # 2017.12.20 11:29:30 CST import cv2 import numpy as np ##(1) read into bgr-space img = cv2.imread("test2.jpg") ##(2) convert to hsv-space, then split the channels hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h,s,v = cv2.split(hsv) ##(3) threshold the S channel using adaptive method(`THRESH_OTSU`) or fixed thresh th, threshed = cv2.threshold(s, 50, 255, cv2.THRESH_BINARY_INV) ##(4) find all the external contours on the threshed S _, cnts, _ = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) canvas = img.copy() #cv2.drawContours(canvas, cnts, -1, (0,255,0), 1) ## sort and choose the largest contour cnts = sorted(cnts, key = cv2.contourArea) cnt = cnts[-1] ## approx the contour, so the get the corner points arclen = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02* arclen, True) cv2.drawContours(canvas, [cnt], -1, (255,0,0), 1, cv2.LINE_AA) cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 1, cv2.LINE_AA) ## Ok, you can see the result as tag(6) cv2.imwrite("detected.png", canvas) 

Respostas relacionadas:

  1. Detectar segmento colorido em uma imagem
  2. Detecção de borda no android opencv
  3. OpenCV C ++ / Obj-C: Detectando uma folha de papel / detecção de quadrados

O que você precisa é de um quadrângulo em vez de um retângulo girado. RotatedRect lhe dará resultados incorretos. Além disso, você precisará de uma projeção em perspectiva.

Basicamente, o que deve ser feito é:

  • Passe por todos os segmentos de polígonos e conecte os que são quase iguais.
  • Classifique-os para ter os 4 maiores segmentos de linha.
  • Cruze essas linhas e você tem os 4 pontos de canto mais prováveis.
  • Transforme a matriz sobre a perspectiva reunida dos pontos de canto e a proporção do object conhecido.

Eu implementei um Quadrangle class que cuida do contorno para a conversão do quadrângulo e também o transformará na perspectiva correta.

Veja uma implementação de trabalho aqui: Java OpenCV deskewing um contorno

Detectar folha de papel é meio velha escola. Se você quiser lidar com a detecção de distorção, é melhor se você procurar imediatamente a detecção da linha de texto. Com isso você terá os extremas à esquerda, direita, superior e inferior. Descarte quaisquer charts na imagem, se você não quiser e, em seguida, faça algumas statistics nos segmentos de linha de texto para encontrar o intervalo de ângulo que mais ocorre ou melhor ângulo. É assim que você vai se reduzir a um bom ângulo de inclinação. Agora, depois disso, você coloca esses parâmetros, o ângulo de inclinação e os extremas, para compensar e cortar a imagem para o que é necessário.

Quanto ao requisito de imagem atual, é melhor tentar CV_RETR_EXTERNAL em vez de CV_RETR_LIST.

Outro método de detecção de bordas é treinar um classificador de florestas aleatórias nas bordas do papel e, em seguida, usar o classificador para obter o mapa de borda. Este é, de longe, um método robusto, mas requer treinamento e tempo.

As florestas aleatórias funcionarão com cenários de diferença de baixo contraste, por exemplo, papel branco sobre fundo branco.