Eu implementei com sucesso o exemplo de detecção quadrada do OpenCV no meu aplicativo de teste, mas agora preciso filtrar a saída, porque é bastante confuso - ou meu código está errado?
Estou interessado nos quatro pontos de canto do papel para a redução da inclinação (como que ) e posterior processamento ...
Imagem original:
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<cv::Point> >)findSquaresInImage:(cv::Mat)_image
{
std::vector<std::vector<cv::Point> > 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<cv::Point> > contours;
for( int c = 0; c < 3; c++ ) {
int ch[] = {c, 0};
mixChannels(&timg, 1, &gray0, 1, ch, 1);
for( int l = 0; l < N; l++ ) {
if( l == 0 ) {
cv::Canny(gray0, gray, 0, thresh, 5);
cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
}
else {
gray = gray0 >= (l+1)*255/N;
}
cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
std::vector<cv::Point> approx;
for( size_t i = 0; i < contours.size(); i++ )
{
cv::approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);
if( approx.size() == 4 && fabs(contourArea(cv::Mat(approx))) > 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<cv::Point> > 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<cv::Vec4i>(), 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;
}
Respostas:
Esse é um assunto recorrente no Stackoverflow e, como não consegui encontrar uma implementação relevante, decidi aceitar o desafio.
Fiz algumas modificações na demonstração de quadrados presente no OpenCV e o código C ++ resultante abaixo é capaz de detectar uma folha de papel na imagem:
Após a execução deste procedimento, a folha de papel será o maior quadrado em
vector<vector<Point> >
:Estou deixando você escrever a função para encontrar o quadrado maior. ;)
fonte
for (int c = 0; c < 3; c++)
, responsável pela iteração em todos os canais da imagem. Por exemplo, você pode configurá-lo para iterar em apenas um canal :) Não esqueça de votar.angle()
é uma função auxiliar . Como indicado na resposta, esse código é baseado em samples / cpp / squares.cpp presente no OpenCV.A menos que haja outro requisito não especificado, eu simplesmente converteria sua imagem colorida em escala de cinza e trabalharia apenas com isso (não é necessário trabalhar nos três canais, o contraste presente já é muito alto). Além disso, a menos que haja algum problema específico em relação ao redimensionamento, eu trabalharia com uma versão reduzida das suas imagens, pois elas são relativamente grandes e o tamanho não acrescenta nada ao problema que está sendo resolvido. Finalmente, seu problema é resolvido com um filtro mediano, algumas ferramentas morfológicas básicas e estatísticas (principalmente para o limiar de Otsu, que já foi feito para você).
Aqui está o que eu obtenho com sua imagem de amostra e outra imagem com uma folha de papel que encontrei ao redor:
O filtro mediano é usado para remover pequenos detalhes da imagem, agora em escala de cinza. Possivelmente removerá linhas finas dentro do papel esbranquiçado, o que é bom, pois você terminará com pequenos componentes conectados, fáceis de descartar. Após a mediana, aplique um gradiente morfológico (simplesmente
dilation
-erosion
) e binarize o resultado por Otsu. O gradiente morfológico é um bom método para manter bordas fortes, devendo ser usado mais. Então, como esse gradiente aumentará a largura do contorno, aplique um afinamento morfológico. Agora você pode descartar pequenos componentes.Neste ponto, aqui está o que temos com a imagem correta acima (antes de desenhar o polígono azul), a esquerda não é mostrada porque o único componente restante é o que descreve o artigo:
Dados os exemplos, agora o único problema que resta é distinguir entre componentes que parecem retângulos e outros que não. Trata-se de determinar uma razão entre a área do casco convexo que contém a forma e a área de sua caixa delimitadora; a razã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 nesses exemplos usando esse método (no entanto, executar esta etapa deve ser muito fácil, especialmente porque isso pode ser feito diretamente pelo OpenCV).
Para referência, aqui está um código de exemplo no Mathematica:
Se houver situações mais variadas em que o retângulo do papel não estiver tão bem definido ou a abordagem o confundir com outras formas - essas situações podem ocorrer devido a vários motivos, mas uma causa comum é a má aquisição de imagem -, tente combinar o pré -processo de etapas com o trabalho descrito no artigo "Detecção de retângulos com base em uma transformação de vento em janela".
fonte
Concept is the same
. (Eu nunca usei o Mathematica, por isso não consigo entender o código.) E as diferenças que você mencionou são diferenças, mas não uma abordagem diferente ou as principais. Se você ainda não o fez, por exemplo, verifique o seguinte:Estou atrasado.
Na sua imagem, o papel é
white
, enquanto o fundo écolored
. Portanto, é melhor detectar o papelSaturation(饱和度)
no canalHSV color space
. Consulte o wiki HSL_and_HSV primeiro. Em seguida, copiarei a maioria das ideias da minha resposta neste segmento Detectar cores em uma imagem .Passos principais:
BGR
bgr
para ohsv
espaçoCanny
, ouHoughLines
como quiser, eu escolhofindContours
), aproximadamente para obter os cantos.Este é o meu resultado:
O código Python (Python 3.5 + OpenCV 3.3):
Respostas relacionadas:
fonte
O que você precisa é de um quadrilátero em vez de um retângulo girado.
RotatedRect
fornecerá resultados incorretos. Você também precisará de uma projeção em perspectiva.Basicamente, o que deve ser feito é:
Eu implementei uma classe
Quadrangle
que cuida da conversão de contorno em quadrângulo e também a transformará na perspectiva correta.Veja uma implementação de trabalho aqui: Java OpenCV limpando um contorno
fonte
Depois de detectar a caixa delimitadora do documento, você pode executar uma transformação de perspectiva de quatro pontos para obter uma visão de cima para baixo da imagem. Isso corrigirá a inclinação e isolará apenas o objeto desejado.
Imagem de entrada:
Objeto de texto detectado
Vista de cima para baixo do documento de texto
Código
fonte
Detectar uma folha de papel é meio que uma velha escola. Se você deseja enfrentar a detecção de inclinação, é melhor se você objetiva imediatamente a detecção de linha de texto. Com isso, você obterá os extremas à esquerda, direita, superior e inferior. Descarte todos os gráficos da imagem, se você não quiser, e faça algumas estatísticas nos segmentos de linha de texto para encontrar a faixa de ângulo mais ocorrente ou melhor, o ângulo. É assim que você reduz o ângulo de inclinação. Agora, depois disso, você coloca esses parâmetros no ângulo de inclinação e nos extremas para inclinar e cortar a imagem conforme o necessário.
Quanto ao requisito de imagem atual, é melhor se você tentar CV_RETR_EXTERNAL em vez de CV_RETR_LIST.
Outro método para detectar bordas é treinar um classificador de florestas aleatórias nas bordas do papel e, em seguida, usar o classificador para obter o mapa das bordas. Este é de longe um método robusto, mas requer treinamento e tempo.
As florestas aleatórias funcionarão com cenários com diferenças de baixo contraste, por exemplo, white paper sobre fundo branco.
fonte