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 ...
Gambar asli:
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;
}
Jawaban:
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:
Setelah prosedur ini dieksekusi, lembaran kertas akan menjadi persegi terbesar di
vector<vector<Point> >
:Saya membiarkan Anda menulis fungsi untuk menemukan kotak terbesar. ;)
sumber
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.angle()
adalah fungsi pembantu . Seperti yang dinyatakan dalam jawaban, kode ini didasarkan pada sampel / cpp / squares.cpp yang ada di OpenCV.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:
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:
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:
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".
sumber
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:Yah, saya terlambat.
Dalam gambar Anda, kertasnya
white
, sedangkan latar belakangnyacolored
. Jadi, lebih baik mendeteksi kertas masukSaturation(饱和度)
saluranHSV 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:
BGR
bgr
kehsv
angkasaCanny
, atauHoughLines
sesuka Anda, saya pilihfindContours
), kira-kira untuk mendapatkan sudut.Ini hasil saya:
Kode Python (Python 3.5 + OpenCV 3.3):
Jawaban terkait:
sumber
Yang Anda butuhkan adalah segi empat bukan persegi panjang yang diputar.
RotatedRect
akan memberi Anda hasil yang salah. Anda juga akan memerlukan proyeksi perspektif.Pada dasarnya yang harus dilakukan adalah:
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
sumber
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
sumber
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.
sumber