Algoritma untuk mendeteksi sudut-sudut lembar kertas di foto

97

Apa cara terbaik untuk mendeteksi sudut-sudut faktur / kwitansi / lembar kertas di foto? Ini akan digunakan untuk koreksi perspektif selanjutnya, sebelum OCR.

Pendekatan saya saat ini adalah:

RGB> Gray> Canny Edge Detection dengan thresholding> Dilate (1)> Hapus objek kecil (6)> hapus objek boarder> pilih blog besar berdasarkan Convex Area. > [deteksi sudut - Tidak diterapkan]

Saya tidak bisa tidak berpikir pasti ada pendekatan 'cerdas' / statistik yang lebih kuat untuk menangani jenis segmentasi ini. Saya tidak memiliki banyak contoh pelatihan, tetapi saya mungkin bisa mengumpulkan 100 gambar.

Konteks yang lebih luas:

Saya menggunakan matlab untuk prototipe, dan berencana untuk mengimplementasikan sistem di OpenCV dan Tesserect-OCR. Ini adalah yang pertama dari sejumlah masalah pemrosesan gambar yang perlu saya selesaikan untuk aplikasi khusus ini. Jadi saya mencari solusi saya sendiri dan membiasakan kembali diri saya dengan algoritma pemrosesan gambar.

Berikut beberapa contoh gambar yang ingin saya tangani oleh algoritme: Jika Anda ingin menerima tantangan, gambar besar ada di http://madteckhead.com/tmp

kasus 1
(sumber: madteckhead.com )

kasus 2
(sumber: madteckhead.com )

kasus 3
(sumber: madteckhead.com )

kasus 4
(sumber: madteckhead.com )

Dalam kasus terbaik ini memberi:

kasus 1 - cerdik
(sumber: madteckhead.com )

kasus 1 - posting cerdik
(sumber: madteckhead.com )

kasus 1 - blog terbesar
(sumber: madteckhead.com )

Namun gagal dengan mudah pada kasus lain:

kasus 2 - cerdik
(sumber: madteckhead.com )

kasus 2 - posting cerdik
(sumber: madteckhead.com )

kasus 2 - blog terbesar
(sumber: madteckhead.com )

Terima kasih sebelumnya untuk semua ide hebatnya! Saya suka BEGITU!

EDIT: Kemajuan Transformasi Hough

T: Algoritme apa yang akan mengelompokkan garis kasar untuk menemukan sudut? Mengikuti saran dari jawaban saya dapat menggunakan Transformasi Hough, memilih garis, dan memfilternya. Pendekatan saya saat ini agak kasar. Saya telah membuat asumsi faktur akan selalu kurang dari 15 derajat tidak sesuai dengan gambar. Saya berakhir dengan hasil yang masuk akal untuk garis jika ini masalahnya (lihat di bawah). Tetapi saya tidak sepenuhnya yakin tentang algoritme yang cocok untuk mengelompokkan garis (atau memilih) untuk memperkirakan sudut. Garis Hough tidak kontinu. Dan pada gambar yang berisik, bisa ada garis paralel sehingga diperlukan beberapa bentuk atau jarak dari metrik asal garis. Ada ide?

kasus 1 kasus 2 kasus 3 kasus 4
(sumber: madteckhead.com )

Nathan Keller
sumber
1
Ya, saya membuatnya berfungsi di sekitar 95% kasus. Saya sejak itu harus menunda kode karena kekurangan waktu. Saya akan memposting tindak lanjut pada tahap tertentu, jangan ragu untuk menugaskan saya jika Anda memerlukan bantuan segera. Maaf atas kurangnya tindak lanjut yang baik. Saya ingin kembali mengerjakan fitur ini.
Nathan Keller
Nathan, bisakah Anda memposting tindak lanjut tentang bagaimana Anda akhirnya melakukannya? Saya terjebak pada titik yang sama mengenali sudut / kontur luar lembaran kertas. Saya mengalami masalah yang sama persis seperti yang Anda alami, jadi saya akan sangat tertarik dengan solusinya.
Tim
6
Semua gambar di posting ini sekarang 404.
ChrisF

Jawaban:

28

Saya teman Martin yang mengerjakan ini awal tahun ini. Ini adalah proyek pengkodean pertama saya, dan agak berakhir dengan terburu-buru, jadi kodenya perlu beberapa kesalahan ... decoding ... Saya akan memberikan beberapa tip dari apa yang saya lihat sudah Anda lakukan, dan kemudian urutkan kode saya pada hari libur saya besok.

Tip pertama, OpenCVdan pythonluar biasa, pindah ke mereka sesegera mungkin. : D

Alih-alih menghapus objek kecil dan atau kebisingan, turunkan batasan cerdik, sehingga menerima lebih banyak tepi, dan kemudian menemukan kontur tertutup terbesar (dalam penggunaan OpenCV findcontour()dengan beberapa parameter sederhana, saya pikir saya menggunakan CV_RETR_LIST). mungkin masih kesulitan saat berada di atas selembar kertas putih, tetapi jelas memberikan hasil terbaik.

Untuk Houghline2()Transformasi, coba dengan CV_HOUGH_STANDARDkebalikan dari CV_HOUGH_PROBABILISTIC, itu akan memberikan nilai rho dan theta , mendefinisikan garis dalam koordinat kutub, dan kemudian Anda dapat mengelompokkan garis dalam toleransi tertentu.

Pengelompokan saya bekerja sebagai tabel pencarian, untuk setiap baris yang dihasilkan dari transformasi hough akan menghasilkan pasangan rho dan theta. Jika nilai-nilai ini ada di dalam, katakanlah 5% dari sepasang nilai dalam tabel, mereka dibuang, jika mereka berada di luar 5% itu, entri baru ditambahkan ke tabel.

Anda kemudian dapat melakukan analisis garis paralel atau jarak antar garis dengan lebih mudah.

Semoga ini membantu.

Daniel Crowley
sumber
Hai Daniel, terima kasih sudah terlibat. Saya suka Anda mendekat. Ini sebenarnya rute yang membuat saya mendapatkan hasil bagus saat ini. Bahkan ada contoh OpenCV yang mendeteksi persegi panjang. Hanya harus melakukan beberapa penyaringan pada hasil. seperti yang Anda katakan putih di atas putih sulit dideteksi dengan metode ini. Tapi itu adalah pendekatan yang sederhana dan lebih murah daripada yang kasar. Saya telah benar-benar meninggalkan pendekatan kasar dari algo saya dan telah melakukan pendekatan poli, lihatlah contoh kotak di opencv. Saya ingin melihat penerapan pemungutan suara kasar Anda. Terima kasih sebelumnya, Nathan
Nathan Keller
Saya mengalami masalah dengan pendekatan ini, saya akan memposting solusi jika saya dapat merancang sesuatu yang lebih baik untuk referensi di masa mendatang
Anshuman Kumar
@AnshumanKumar Saya benar-benar membutuhkan bantuan dengan pertanyaan ini, dapatkah Anda membantu saya? stackoverflow.com/questions/61216402/…
Carlos Diego
19

Sekelompok mahasiswa di universitas saya baru-baru ini mendemonstrasikan aplikasi iPhone (dan aplikasi OpenCV python) yang mereka tulis untuk melakukan hal ini. Seingat saya, langkah-langkahnya adalah seperti ini:

  • Filter median untuk sepenuhnya menghapus teks di atas kertas (ini adalah teks tulisan tangan di atas kertas putih dengan pencahayaan yang cukup baik dan mungkin tidak berfungsi dengan teks yang dicetak, ini berfungsi dengan sangat baik). Alasannya adalah itu membuat deteksi sudut lebih mudah.
  • Transformasi Hough untuk garis
  • Temukan puncak di ruang akumulator Hough Transform dan gambar setiap garis di seluruh gambar.
  • Analisis garis dan hapus garis yang sangat dekat satu sama lain dan berada pada sudut yang sama (kelompokkan garis menjadi satu). Ini diperlukan karena Transformasi Hough tidak sempurna karena bekerja di ruang sampel yang terpisah.
  • Temukan pasangan garis yang kira-kira sejajar dan yang memotong pasangan lainnya untuk melihat garis mana yang membentuk paha depan.

Ini tampaknya bekerja dengan cukup baik dan mereka dapat mengambil foto selembar kertas atau buku, melakukan deteksi sudut dan kemudian memetakan dokumen dalam gambar ke bidang datar hampir secara realtime (ada satu fungsi OpenCV yang harus dilakukan pemetaan). Tidak ada OCR saat saya melihatnya berfungsi.

Martin Foot
sumber
Terima kasih atas ide-ide hebatnya, Martin. Saya telah menerima saran Anda dan menerapkan pendekatan transformasi Hough. (Lihat hasil di atas). Saya berjuang untuk menentukan algoritma yang kuat yang akan mengekstrapolasi garis untuk menemukan perpotongan. Tidak banyak garis, dan beberapa positif palsu. Apakah Anda memiliki saran tentang cara terbaik untuk menggabungkan dan membuang garis? Jika siswa Anda tertarik, mohon dorong mereka untuk menghubungi. Saya ingin mendengar pengalaman mereka dalam menjalankan algoritme pada platform seluler. (Itulah tujuan saya selanjutnya). Terima kasih banyak atas ide Anda.
Nathan Keller
1
Sepertinya HT untuk garis telah bekerja dengan baik di semua kecuali gambar kedua Anda, tetapi apakah Anda menentukan toleransi ambang batas untuk nilai awal dan akhir Anda di akumulator? HT tidak benar-benar menentukan posisi awal dan akhir, melainkan nilai m dan c di y = mx + c. Lihat di sini - perhatikan bahwa ini menggunakan koordinat kutub di akumulator, bukan kartesius. Dengan cara ini Anda dapat mengelompokkan garis dengan c dan kemudian dengan m untuk menipiskannya dan dengan membayangkan garis melebar di seluruh gambar, Anda akan menemukan perpotongan yang lebih berguna.
Martin Foot
@MartinFoot Saya benar-benar membutuhkan bantuan dengan pertanyaan ini, dapatkah Anda membantu saya? stackoverflow.com/questions/61216402/…
Carlos Diego
16

Inilah yang saya dapatkan setelah sedikit eksperimen:

import cv, cv2, numpy as np
import sys

def get_new(old):
    new = np.ones(old.shape, np.uint8)
    cv2.bitwise_not(new,new)
    return new

if __name__ == '__main__':
    orig = cv2.imread(sys.argv[1])

    # these constants are carefully picked
    MORPH = 9
    CANNY = 84
    HOUGH = 25

    img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(img, (3,3), 0, img)


    # this is to recognize white on white
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
    dilated = cv2.dilate(img, kernel)

    edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)

    lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
    for line in lines[0]:
         cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                         (255,0,0), 2, 8)

    # finding contours
    contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                   cv.CV_CHAIN_APPROX_TC89_KCOS)
    contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
    contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)

    # simplify contours down to polygons
    rects = []
    for cont in contours:
        rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
        rects.append(rect)

    # that's basically it
    cv2.drawContours(orig, rects,-1,(0,255,0),1)

    # show only contours
    new = get_new(img)
    cv2.drawContours(new, rects,-1,(0,255,0),1)
    cv2.GaussianBlur(new, (9,9), 0, new)
    new = cv2.Canny(new, 0, CANNY, apertureSize=3)

    cv2.namedWindow('result', cv2.WINDOW_NORMAL)
    cv2.imshow('result', orig)
    cv2.waitKey(0)
    cv2.imshow('result', dilated)
    cv2.waitKey(0)
    cv2.imshow('result', edges)
    cv2.waitKey(0)
    cv2.imshow('result', new)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

Tidak sempurna, tetapi setidaknya berfungsi untuk semua sampel:

1 2 3 4

Vanuan
sumber
4
Saya sedang mengerjakan proyek serupa. Saya menjalankan di atas kode dan itu memberi saya kesalahan "Tidak ada modul bernama cv". Saya menginstal versi Open CV 2.4 dan mengimpor cv2 bekerja dengan sempurna untuk saya.
Navneet Singh
Apakah Anda berbaik hati memperbarui kode ini agar berhasil? pastebin.com/PMH5Y0M8 itu hanya memberi saya halaman hitam.
the7erm
Apakah Anda tahu tentang cara mengubah kode berikut ke java: for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
aurelianr
Vanuan Saya benar-benar membutuhkan bantuan untuk pertanyaan ini, dapatkah Anda membantu saya? stackoverflow.com/questions/61216402/…
Carlos Diego
9

Alih-alih memulai dari deteksi tepi, Anda bisa menggunakan deteksi sudut.

Marvin Framework menyediakan implementasi algoritma Moravec untuk tujuan ini. Anda bisa menemukan sudut-sudut kertas sebagai titik awal. Di bawah keluaran algoritma Moravec:

masukkan deskripsi gambar di sini

Gabriel Ambrósio Archanjo
sumber
4

Anda juga dapat menggunakan MSER (Wilayah ekstrem yang stabil maksimum ) melalui hasil operator Sobel untuk menemukan wilayah gambar yang stabil. Untuk setiap wilayah yang dikembalikan oleh MSER Anda dapat menerapkan convex hull dan poly aproksimasi untuk mendapatkan beberapa seperti ini:

Tetapi jenis deteksi ini berguna untuk deteksi langsung lebih dari satu gambar yang tidak selalu memberikan hasil terbaik.

hasil

Flayn
sumber
1
Dapatkah Anda membagikan beberapa detail lebih lanjut untuk ini, mungkin beberapa kode, terima kasih banyak sebelumnya
Monty
Saya menerima pesan kesalahan di cv2.CHAIN_APPROX_SIMPLE yang mengatakan terlalu banyak nilai untuk dibuka. Ada ide? Saya menggunakan gambar 1024 * 1024 sebagai sampel saya
Praveen
1
Terima kasih semua, hanya tahu perubahan sintaks di cabang OpenCV saat answers.opencv.org/question/40329/...
Praveen
Bukankah MSER dimaksudkan untuk mengekstrak gumpalan? Saya mencobanya dan hanya mendeteksi sebagian besar teks
Anshuman Kumar
3

Setelah deteksi tepi, gunakan Hough Transform. Kemudian, letakkan titik-titik tersebut di SVM (mesin vektor pendukung) dengan labelnya, jika contoh memiliki garis halus, SVM tidak akan mengalami kesulitan untuk membagi bagian yang diperlukan dari contoh dan bagian lain. Saran saya tentang SVM, letakkan parameter seperti konektivitas dan panjang. Artinya, jika poin terhubung dan panjang, kemungkinan besar itu adalah garis tanda terima. Kemudian, Anda dapat menghilangkan semua poin lainnya.

Hephaestus
sumber
Hai Ares, terima kasih atas ide Anda! Saya telah menerapkan transformasi Hough (lihat di atas). Saya tidak bisa menemukan cara yang kuat untuk menemukan sudut yang diberikan positif palsu, dan garis tidak kontinu. Apakah Anda punya ide lebih lanjut? Sudah lama sejak saya melihat teknik SVM. Apakah ini pendekatan yang diawasi? Saya tidak memiliki data pelatihan apa pun, tetapi saya dapat menghasilkan beberapa. Saya akan tertarik untuk mengeksplorasi pendekatan ini karena saya tertarik untuk mempelajari lebih lanjut tentang SVM. Dapatkah Anda merekomendasikan sumber daya apa pun. Salam. Nathan
Nathan Keller
3

Di sini Anda memiliki kode @Vanuan menggunakan C ++:

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);
GBF_Gabriel
sumber
Dimana definisi variabel garis? Harus std :: vector <cv :: Vec4i> lines;
Dapat Ürek
@ CanÜrek Anda benar. std::vector<cv::Vec4i> lines;dideklarasikan dalam lingkup global dalam proyek saya.
GBF_Gabriel
1
  1. Ubah ke ruang lab

  2. Gunakan cluster kmeans segment 2

  3. Kemudian gunakan kontur atau hough pada salah satu cluster (intenral)
pengguna3452134
sumber