OpenCV C ++ / Obj-C: Bir sayfayı algılama / Kare Algılama


178

Test uygulamamda OpenCV kare algılama örneğini başarıyla uyguladım, ancak şimdi çıktıyı filtrelemeliyim, çünkü oldukça dağınık - veya kodum yanlış mı?

Ben (gibi çarpık azaltılması için kağıt dört köşe noktalarında ilgilenen kulüpler olduğunu ) ve daha ileri işleme ...

Giriş çıkış: Giriş çıkış

Gerçek görüntü:

Tıklayın

Kod:

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:

Görüntüde algılanan kareleri çizmek için bu kodu kullanın:

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;
}


1
Daha uygun olduğunu düşünüyorsanız, bir kağıdın algılanması gibi bir soru için sorunun başlığını ayarlayabileceğinizi düşünüyorum.
karlphillip

1
@moosgummi "Yakalanan görüntünün / belgenin Köşelerini Algıla" gibi aynı işlevselliğe sahip olmak istiyorum. Bunu nasıl başardınız? İPhone uygulamamda OpenCV kullanabilir miyim? Lütfen bana bunun için daha iyi bir yol önerin ..
Ajay Sharma

1
Hiç OpenCV ile bir şey yaptın mı? Herhangi bir uygulama var mı?
karlphillip

6
CV_RETR_EXTERNAL bayrağının, kapalı bir şeklin içindeki tüm konturları reddetmek için sayılar bulunurken kullanılabileceğini belirtmek gerekir.
mehfoos yacoob

Yanıtlar:


162

Bu, Stackoverflow'da tekrar eden bir konudur ve ilgili bir uygulama bulamadığım için bu meydan okumayı kabul etmeye karar verdim.

OpenCV'de bulunan kareler demosunda bazı değişiklikler yaptım ve sonuçta ortaya çıkan C ++ kodu görüntüdeki bir sayfayı algılayabiliyor:

void find_squares(Mat& image, vector<vector<Point> >& squares)
{
    // blur will enhance edge detection
    Mat blurred(image);
    medianBlur(image, blurred, 9);

    Mat gray0(blurred.size(), CV_8U), gray;
    vector<vector<Point> > 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<Point> 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);
                    }
            }
        }
    }
}

Bu prosedür yürütüldükten sonra, kağıt sayfası en büyük kare olacaktır vector<vector<Point> >:

opencv kağıt yaprak algılama

En büyük kareyi bulmak için işlevi yazmanıza izin veriyorum. ;)


4
Bu yüzden kaynak kontrolü kullanıyorum. Kodda yanlışlıkla yapılan en küçük değişiklik kolayca bulunabilir. Hiçbir şeyi değiştirmediyseniz, diğer görüntülerle test etmeyi deneyin ve son olarak opencv'yi yeniden derleyin / yeniden yükleyin.
karlphillip

2
OpenCV tüm platformlar için hemen hemen aynıdır (Win / Linux / Mac / iPhone / ...). Fark, bazılarının OpenCV'nin GPU modülünü desteklememesidir. İOS için OpenCV'yi zaten oluşturdunuz mu? Test edebildiniz mi? Daha ileri bir şey denemeden önce cevaplamanız gereken sorular olduğunu düşünüyorum. Bebek adımları!
karlphillip

1
@karlphillip Bu kodu test ettim ve kağıdı net bir şekilde algılayabildim, ancak çok zaman alıyor. Kod gerçekten ağır mı? bu tespitin bir video akışından gerçek zamanlı olarak gerçekleştiği SayText adlı bir uygulama var. Bu kod gerçek zamanlı olarak pratik olmaz, değil mi?
alandalusi

1
Muhtemelen. Bu akademik bir cevaptır, endüstri için çok pratik değildir. for (int c = 0; c < 3; c++)Görüntünün her kanalında yinelemekten sorumlu olan sayacın tanımından başlayarak deneyebileceğiniz her türlü optimizasyon vardır . Örneğin, yalnızca bir kanalda yineleme yapacak şekilde ayarlayabilirsiniz :) Oy vermeyi unutmayın.
karlphillip

3
@SilentPro angle()a, yardımcı fonksiyon . Yanıtta belirtildiği gibi, bu kod OpenCV'de bulunan samples / cpp / squares.cpp örneklerini temel alır .
karlphillip

40

Belirtilmeyen başka bir gereksinim olmadıkça, renkli görüntünüzü gri tonlamaya dönüştürürüm ve sadece bununla çalışırım (3 kanalda çalışmaya gerek yoktur, mevcut kontrast zaten çok yüksektir). Ayrıca, yeniden boyutlandırma ile ilgili belirli bir sorun yoksa, göreceli olarak büyük olduklarından ve boyut çözülecek soruna hiçbir şey eklemediğinden, resimlerinizin küçültülmüş bir sürümü ile çalışacağım. Son olarak, sorununuz medyan bir filtre, bazı temel morfolojik araçlar ve istatistikler (çoğunlukla sizin için zaten yapılmış olan Otsu eşiği için) ile çözülür.

Örnek görüntünüzle ve bulduğum bir kağıda sahip başka bir görüntüyle elde ettiğim şey:

resim açıklamasını buraya girin resim açıklamasını buraya girin

Medyan filtresi, şimdi gri tonlamalı görüntüdeki küçük ayrıntıları kaldırmak için kullanılır. Muhtemelen beyazımsı kağıdın içindeki ince çizgileri kaldıracaktır, bu da iyidir çünkü o zaman atması kolay küçük bağlı bileşenlerle sona ereceksiniz. Ortancadan sonra, morfolojik bir gradyan uygulayın (sadece dilation- erosion) ve sonucu Otsu ile ikilileştirin. Morfolojik gradyan, güçlü kenarları korumak için iyi bir yöntemdir, daha fazla kullanılmalıdır. Daha sonra, bu gradyan kontur genişliğini artıracağından morfolojik bir incelme uygulayın. Artık küçük bileşenleri atabilirsiniz.

Bu noktada, yukarıdaki mavi görüntüyü (mavi poligonu çizmeden önce) sahip olduklarımız, soldaki bileşen gösterilmemiştir çünkü kalan tek bileşen kağıdı tanımlayan bileşendir:

resim açıklamasını buraya girin

Örnekler göz önüne alındığında, geriye kalan tek sorun dikdörtgen gibi görünen bileşenler ile olmayan diğer bileşenler arasında ayrım yapmaktır. Bu, şeklini içeren dışbükey gövdenin alanı ile sınırlayıcı kutusunun alanı arasındaki bir oranın belirlenmesi meselesidir; 0.7 oranı bu örnekler için uygundur. Bu yöntemi kullanarak bu örneklerde değil, kağıdın içindeki bileşenleri de atmanız gerekebilir (yine de, bu adımı gerçekleştirmek özellikle kolay çünkü doğrudan OpenCV yoluyla yapılabilir).

Referans olarak, Mathematica'da bir örnek kod:

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}]]

Kağıdın dikdörtgeninin bu kadar iyi tanımlanmadığı daha çeşitli durumlar varsa veya yaklaşım onu ​​diğer şekillerle karıştırırsa - bu durumlar çeşitli nedenlerden dolayı olabilir, ancak yaygın bir neden kötü görüntü elde etmektir - sonra ön -Pencereli bir Hough Dönüşümüne Dayalı Dikdörtgen Algılama "makalesinde açıklanan çalışma ile işlem adımları.


1
sizin ve yukarıdaki uygulamaların uygulanmasında büyük bir fark var mı (yani @karlphilip'in cevabı)? Üzgünüm (3 kanal-1 kanalı ve Mathematica-OpenCV hariç) hızlı bir görünüm bulamadım.
Abid Rahman K

2
@AbidRahmanK evet, var .. Başlamak için canny ne "birkaç eşik" kullanmıyorum. Başka farklılıklar da var, ancak yorumunuzun tonuna göre kendi yorumuma çaba harcamak anlamsız görünüyor.
mmgp

1
Her ikinizi de önce kenarları bulduğunuzu görüyorum ve hangi kenarın kare olduğunu belirledim. Kenarları bulmak için farklı yöntemler kullanırsınız. Canny kullanıyor, biraz dilatasyon erozyonu kullanıyorsunuz. Ve "birkaç eşik", kareleri bulmak için kullanılan OpenCV örneklerinden alınmış olabilir. Önemli olan, genel kavramın aynı olduğunu hissettim. Msgstr "Kenarları bul ve kareyi algıla". Ve içtenlikle sordum, yorumumdan ne "ton" aldığını veya ne anladığını (anladım / yanlış anladım) bilmiyorum. Bu sorunun samimi olduğunu düşünüyorsanız, diğer farklılıkları bilmek isterim. Aksi takdirde yorumlarımı sil.
Abid Rahman K

1
@AbidRahmanK elbette konsept aynı, görev aynı. Medyan filtreleme kullanılıyor, inceltme kullanılıyor, birkaç eşik fikrini nereden aldığını umursamıyorum - sadece burada kullanılmıyor (bu yüzden nasıl bir fark olamaz?), Görüntü burada yeniden boyutlandırılıyor, bileşen ölçümleri farklıdır. "Bazı dilatasyon-erozyon" ikili kenarlar vermez, bunun için otsu kullanılır. Bundan bahsetmek anlamsız, kod orada.
mmgp

1
K. Teşekkür ederim. Cevabı aldım. Concept is the same. (Mathematica'yı hiç kullanmadım, bu yüzden kodu anlayamıyorum.) Ve bahsettiğiniz farklılıklar farklılıklar, ancak farklı bir yaklaşım veya ana yaklaşımlar değil. Hâlâ yapmadıysanız Örneğin, şunu kontrol edin:
Abid Rahman K

14

Geç kaldım.


Resminizde kağıt, whitearka plan ise colored. Bu nedenle, kağıdın Saturation(饱和度)kanalda olduğunu tespit etmek daha iyidir HSV color space. Önce wiki HSL_and_HSV adresine bakın . Ardından , bir görüntüdeki bu Renkli Segmenti Al bölümündeki cevabımdan çoğu fikri kopyalayacağım .


Ana adımlar:

  1. İcerigi okumak BGR
  2. Görüntüyü dönüştürme bgriçin hsvboşluk
  3. S kanalını eşikle
  4. Daha sonra , köşeleri elde etmek için maksimum dış konturu bulun (veya istediğiniz Cannyveya HoughLinesistediğiniz gibi seçin findContours).

Bu benim sonucum:

resim açıklamasını buraya girin


Python kodu (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)
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

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)

İlgili cevaplar:

  1. OpenCV kullanarak bir görüntüdeki renkli yamaları nasıl tespit edebilirim?
  2. OpenCV kullanarak renkli arka plan üzerinde kenar tespiti
  3. OpenCV C ++ / Obj-C: Bir sayfayı algılama / Kare Algılama
  4. Farklı OpenCV sürümlerinde `cv2.findContours` nasıl kullanılır?

S alanı kullanmayı denedim ama yine de başarılı olamadım.
Şuna

3

Senin ihtiyacın olan bir dörtgen yerine döndürülmüş dikdörtgenin. RotatedRectyanlış sonuçlar verir. Ayrıca bir perspektif projeksiyonuna ihtiyacınız olacak.

Temel olarak yapılması gereken şey:

  • Tüm çokgen parçalarından geçirin ve neredeyse eşit olanları bağlayın.
  • Bunları en büyük 4 çizgi parçasına sahip olacak şekilde sıralayın.
  • Bu çizgileri kesiştirin ve en olası 4 köşe noktasına sahipsiniz.
  • Matrisi, köşe noktalarından toplanan perspektif ve bilinen nesnenin en boy oranına dönüştürün.

QuadrangleDönüşümü dörtlü hale getirmek için kontur ile ilgilenen ve aynı zamanda doğru perspektif üzerinde dönüştürecek bir sınıf uyguladım .

Burada çalışan bir uygulamaya bakın: Java OpenCV bir konturu düzeltme


1

Belgenin sınırlayıcı kutusunu belirledikten sonra, görüntünün yukarıdan aşağıya kuş bakışı görünümünü elde etmek için dört noktalı bir perspektif dönüşümü gerçekleştirebilirsiniz . Bu, eğriltmeyi düzeltir ve yalnızca istenen nesneyi yalıtır.


Giriş resmi:

Algılanan metin nesnesi

Metin belgesinin yukarıdan aşağı görünümü

kod

from imutils.perspective import four_point_transform
import cv2
import numpy

# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread("1.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find contours and sort for largest contour
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None

for c in cnts:
    # Perform contour approximation
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    if len(approx) == 4:
        displayCnt = approx
        break

# Obtain birds' eye view of image
warped = four_point_transform(image, displayCnt.reshape(4, 2))

cv2.imshow("thresh", thresh)
cv2.imshow("warped", warped)
cv2.imshow("image", image)
cv2.waitKey()

-1

Kağıdın algılanması biraz eski okuldur. Eğiklik algılamasıyla uğraşmak istiyorsanız, metin satırı algılamayı hemen hedeflemeniz daha iyidir. Bununla, uçları sola, sağa, üste ve alta getireceksiniz. İstemiyorsanız, görüntüdeki tüm grafikleri atın ve ardından en çok oluşan açı aralığını veya daha doğrusu açıyı bulmak için metin satırı segmentlerinde bazı istatistikler yapın. İyi bir eğim açısına bu şekilde daralmış olacaksınız. Şimdi bundan sonra bu parametreleri eğri açısını ve uçları yerleştirerek görüntüyü gereken şeye doğrayın.

Mevcut görüntü gereksinimi gelince, CV_RETR_LIST yerine CV_RETR_EXTERNAL'i denerseniz daha iyi olur.

Kenarları algılamanın başka bir yöntemi de, kağıt kenarlarında rastgele bir orman sınıflandırıcısı yetiştirmek ve daha sonra kenar Haritasını almak için sınıflandırıcıyı kullanmaktır. Bu çok sağlam bir yöntemdir ancak eğitim ve zaman gerektirir.

Rastgele ormanlar, kabaca beyaz zemin üzerine beyaz kağıt gibi düşük kontrast farkı senaryolarıyla çalışır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.