OpenCV C ++ / Obj-C: Mendeteksi selembar kertas / Deteksi Kotak

178

Saya berhasil menerapkan contoh deteksi persegi OpenCV dalam aplikasi pengujian saya, tetapi sekarang perlu memfilter output, karena cukup berantakan - atau apakah kode saya salah?

Saya tertarik pada empat titik sudut kertas untuk reduksi miring (seperti itu ) dan pemrosesan lebih lanjut ...

Input output: Input output

Gambar asli:

klik

Kode:

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:

Untuk menggambar kotak yang terdeteksi pada gambar, gunakan kode ini:

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;
}
dom
sumber
1
Saya pikir Anda dapat menyesuaikan judul pertanyaan untuk sesuatu seperti Mendeteksi selembar kertas , jika Anda pikir itu lebih tepat.
karlphillip
1
@moosgummi Saya mencari untuk memiliki fungsi yang sama yang telah Anda terapkan yaitu "Mendeteksi Sudut gambar / dokumen yang diambil". Bagaimana Anda mencapai ini? Apakah saya dapat menggunakan OpenCV dalam aplikasi iPhone saya? Tolong sarankan saya cara yang lebih baik untuk memiliki ini ..
Ajay Sharma
1
Apakah Anda pernah melakukan sesuatu dengan OpenCV? Aplikasi apa saja?
karlphillip
6
Perlu dicatat bahwa bendera CV_RETR_EXTERNAL dapat digunakan ketika menemukan kontur untuk menolak semua kontur di dalam bentuk tertutup.
mehfoos yacoob

Jawaban:

162

Ini adalah topik yang berulang di Stackoverflow dan karena saya tidak dapat menemukan implementasi yang relevan saya memutuskan untuk menerima tantangan.

Saya membuat beberapa modifikasi pada demo kotak yang ada di OpenCV dan kode C ++ yang dihasilkan di bawah ini mampu mendeteksi selembar kertas pada gambar:

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

Setelah prosedur ini dieksekusi, lembaran kertas akan menjadi persegi terbesar di vector<vector<Point> >:

deteksi lembar kertas opencv

Saya membiarkan Anda menulis fungsi untuk menemukan kotak terbesar. ;)

karlphillip
sumber
4
Itu sebabnya saya menggunakan kontrol sumber. Modifikasi kode terkecil yang tidak disengaja dapat dengan mudah ditemukan. Jika Anda tidak mengubah apa pun, coba pengujian dengan gambar lain dan akhirnya kompilasi ulang / instal ulang OpenCV.
karlphillip
2
OpenCV hampir sama untuk semua platform (Win / Linux / Mac / iPhone / ...). Perbedaannya adalah beberapa tidak mendukung modul GPU OpenCV. Sudahkah Anda membangun OpenCV untuk iOS ? Apakah Anda dapat mengujinya? Saya pikir ini adalah pertanyaan yang perlu Anda jawab sebelum mencoba sesuatu yang lebih maju. Langkah kecil!
karlphillip
1
@karlphillip Saya menguji kode ini dan saya dapat mendeteksi kertas dengan jelas, tetapi butuh banyak waktu. Apakah kodenya sangat berat? ada aplikasi bernama SayText di mana deteksi ini terjadi secara real-time dari aliran video. Kode ini tidak praktis untuk waktu-nyata, benarkan?
alandalusi
1
Mungkin. Ini adalah jawaban akademis, tidak terlalu praktis untuk industri. Ada semua jenis optimasi yang dapat Anda coba, dimulai dengan definisi penghitung yang terletak di for (int c = 0; c < 3; c++), yang bertanggung jawab untuk beralih di setiap saluran gambar. Misalnya, Anda dapat mengaturnya untuk beralih hanya pada satu saluran :) Jangan lupa untuk memilih.
karlphillip
3
@ SilentPro angle()adalah fungsi pembantu . Seperti yang dinyatakan dalam jawaban, kode ini didasarkan pada sampel / cpp / squares.cpp yang ada di OpenCV.
karlphillip
40

Kecuali ada persyaratan lain yang tidak ditentukan, saya hanya akan mengubah gambar warna Anda menjadi skala abu-abu dan hanya bekerja dengan itu (tidak perlu bekerja pada 3 saluran, kontras sekarang sudah terlalu tinggi). Selain itu, kecuali ada beberapa masalah khusus tentang mengubah ukuran, saya akan bekerja dengan versi downscaled gambar Anda, karena mereka relatif besar dan ukurannya tidak menambah masalah yang sedang dipecahkan. Kemudian, akhirnya, masalah Anda diselesaikan dengan filter median, beberapa alat morfologi dasar, dan statistik (kebanyakan untuk ambang batas Otsu, yang sudah dilakukan untuk Anda).

Inilah yang saya dapatkan dengan sampel gambar Anda dan beberapa gambar lain dengan selembar kertas yang saya temukan di sekitar:

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Filter median digunakan untuk menghapus detail kecil dari gambar, sekarang skala abu-abu. Mungkin akan menghilangkan garis tipis di dalam kertas keputihan, yang bagus karena Anda akan berakhir dengan komponen kecil yang terhubung yang mudah dibuang. Setelah median, terapkan gradien morfologis (hanya dilation- erosion) dan binariasikan hasilnya dengan Otsu. Gradien morfologis adalah metode yang baik untuk menjaga tepi yang kuat, itu harus digunakan lebih banyak. Kemudian, karena gradien ini akan meningkatkan lebar kontur, terapkan penipisan morfologis. Sekarang Anda dapat membuang komponen kecil.

Pada titik ini, inilah yang kita miliki dengan gambar kanan di atas (sebelum menggambar poligon biru), yang kiri tidak ditampilkan karena satu-satunya komponen yang tersisa adalah yang menggambarkan kertas:

masukkan deskripsi gambar di sini

Mengingat contoh-contohnya, sekarang satu-satunya masalah yang tersisa adalah membedakan antara komponen yang terlihat seperti persegi panjang dan yang lainnya tidak. Ini adalah masalah menentukan rasio antara luas lambung cembung yang berisi bentuk dan luas kotak pembatasnya; rasio 0,7 berfungsi dengan baik untuk contoh-contoh ini. Mungkin Anda harus membuang komponen yang ada di dalam kertas, tetapi tidak dalam contoh ini dengan menggunakan metode ini (namun, melakukan langkah ini harus sangat mudah terutama karena dapat dilakukan melalui OpenCV secara langsung).

Untuk referensi, berikut ini contoh kode dalam 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}]]

Jika ada situasi yang lebih bervariasi di mana persegi panjang kertas tidak terdefinisi dengan baik, atau pendekatan membingungkan dengan bentuk lain - situasi ini bisa terjadi karena berbagai alasan, tetapi penyebab umum adalah akuisisi gambar yang buruk - kemudian coba kombinasikan pra langkah-langkah pemrosesan dengan karya yang dijelaskan dalam makalah "Deteksi Rectangle berdasarkan Windough Hough Transform".

mmgp
sumber
1
apakah ada perbedaan besar dalam implementasi Anda dan yang di atas (yaitu jawaban @karlphilip)? Maaf saya tidak dapat menemukan tampilan yang cepat (kecuali 3 channel-1 channel dan Mathematica-OpenCV).
Abid Rahman K
2
@AbidRahmanK ya, ada .. Saya tidak menggunakan canny baik "ambang beberapa" untuk memulai. Ada perbedaan-perbedaan lain, tetapi dengan nada komentar Anda, sepertinya tidak ada gunanya untuk mengusahakan komentar saya sendiri.
mmgp
1
Saya melihat Anda berdua pertama-tama menemukan tepi, dan menentukan tepi mana yang persegi. Untuk menemukan tepi, Anda orang menggunakan metode yang berbeda. Dia menggunakan cerdik, Anda menggunakan beberapa pelebaran-erosi. Dan "beberapa ambang batas", mungkin dia dapatkan dari sampel OpenCV, yang digunakan untuk mencari kotak. Yang utama adalah, saya merasa konsep keseluruhannya sama. "Temukan tepi dan deteksi kotak". Dan saya bertanya dengan tulus, saya tidak tahu "nada" apa yang Anda dapatkan dari komentar saya, atau apa yang Anda (mengerti / salah pahami). Jadi jika Anda merasa pertanyaan ini tulus, saya ingin tahu perbedaan lainnya. Kalau tidak, buang komentar saya.
Abid Rahman K
1
@AbidRahmanK tentu saja konsepnya sama, tugasnya sama. Penyaringan median sedang digunakan, penjarangan sedang digunakan, saya tidak peduli dari mana ia mengambil beberapa ide ambang - itu tidak digunakan di sini (jadi bagaimana mungkin tidak ada perbedaan?), Gambar diubah ukurannya di sini, pengukuran komponen berbeda. "Some dilation-erosion" tidak memberikan tepi biner, otsu digunakan untuk itu. Tidak ada gunanya untuk menyebutkan ini, kode itu ada di sana.
mmgp
1
K. Terima kasih. Mendapat jawabannya. Concept is the same. (Saya tidak pernah menggunakan Mathematica, jadi saya tidak dapat memahami kode.) Dan perbedaan yang Anda sebutkan adalah perbedaan, tetapi bukan pendekatan yang berbeda atau yang utama. Jika Anda masih tidak Misalnya, periksa ini:
Abid Rahman K
14

Yah, saya terlambat.


Dalam gambar Anda, kertasnya white, sedangkan latar belakangnya colored. Jadi, lebih baik mendeteksi kertas masuk Saturation(饱和度)saluran HSV color space. Lihatlah dulu wiki HSL_and_HSV terlebih dahulu. Lalu saya akan menyalin sebagian besar ide dari jawaban saya di Deteksi Berwarna Segmen ini dalam gambar .


Langkah utama:

  1. Membaca kedalam BGR
  2. Konversi gambar dari bgrke hsvangkasa
  3. Ambang saluran S
  4. Kemudian cari kontur eksternal maks (atau lakukan Canny, atau HoughLinessesuka Anda, saya pilih findContours), kira-kira untuk mendapatkan sudut.

Ini hasil saya:

masukkan deskripsi gambar di sini


Kode 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)
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)

Jawaban terkait:

  1. Bagaimana cara mendeteksi tambalan berwarna pada gambar menggunakan OpenCV?
  2. Deteksi tepi pada latar belakang berwarna menggunakan OpenCV
  3. OpenCV C ++ / Obj-C: Mendeteksi selembar kertas / Deteksi Kotak
  4. Bagaimana cara menggunakan `cv2.findContours` dalam berbagai versi OpenCV?
Kinght 金
sumber
Saya mencoba menggunakan ruang S tetapi tetap tidak berhasil. Lihat ini: stackoverflow.com/questions/50699893/…
hchouhan02
3

Yang Anda butuhkan adalah segi empat bukan persegi panjang yang diputar. RotatedRectakan memberi Anda hasil yang salah. Anda juga akan memerlukan proyeksi perspektif.

Pada dasarnya yang harus dilakukan adalah:

  • Lingkari semua segmen poligon dan sambungkan yang hampir equel.
  • Urutkan mereka sehingga Anda memiliki 4 segmen garis terbesar.
  • Berpotongan garis-garis itu dan Anda memiliki 4 titik sudut yang paling mungkin.
  • Ubah matriks dari perspektif yang dikumpulkan dari titik sudut dan rasio aspek dari objek yang diketahui.

Saya menerapkan kelas Quadrangle yang menangani konversi kontur ke segi empat dan juga akan mentransformasikannya ke perspektif yang benar.

Lihat implementasi yang bekerja di sini: Java OpenCV deskewing kontur

Tim
sumber
1

Setelah Anda mendeteksi kotak pembatas dokumen, Anda dapat melakukan transformasi perspektif empat titik untuk mendapatkan tampilan mata burung dari atas ke bawah pada gambar. Ini akan memperbaiki kemiringan dan hanya mengisolasi objek yang diinginkan.


Input gambar:

Objek teks yang terdeteksi

Tampilan atas-bawah dokumen teks

Kode

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()
nathancy
sumber
-1

Mendeteksi selembar kertas agak kuno. Jika Anda ingin mengatasi deteksi miring maka lebih baik jika Anda langsung bertujuan untuk deteksi garis teks. Dengan ini, Anda akan mendapatkan ujung kiri, kanan, atas dan bawah. Buang gambar apa pun dalam gambar jika Anda tidak mau dan lakukan beberapa statistik pada segmen garis teks untuk menemukan rentang sudut yang paling banyak terjadi atau lebih tepatnya sudut. Ini adalah bagaimana Anda akan mempersempit ke sudut kemiringan yang baik. Sekarang setelah ini Anda meletakkan parameter ini sudut miring dan ekstrem ke meja dan memotong gambar untuk apa yang diperlukan.

Adapun persyaratan gambar saat ini, lebih baik jika Anda mencoba CV_RETR_EXTERNAL daripada CV_RETR_LIST.

Metode lain mendeteksi tepi adalah untuk melatih classifier hutan acak di tepi kertas dan kemudian menggunakan classifier untuk mendapatkan Peta tepi. Ini sejauh ini merupakan metode yang kuat tetapi membutuhkan pelatihan dan waktu.

Hutan acak akan bekerja dengan skenario perbedaan kontras rendah misalnya kertas putih dengan latar belakang putih kasar.

Anubhav Rohatgi
sumber