Menggunakan perpustakaan OpenCV untuk mengenali busur elips di bagian 2D awan titik 3D

Sehubungan dengan penyebaran yang lebih luas dari pemindai laser yang terjangkau (kapten) yang mampu menerima awan titik 3D ( 3DOT ) dan aplikasi yang lebih luas dari teknologi ini di berbagai bidang (dari teknik mesin hingga keamanan, dari industri minyak hingga arsitektur), minat dalam pemrosesan algoritma telah dihidupkan kembali. awan poin.

Salah satu aplikasi populer 3d dalam industri adalah pembuatan dokumentasi desain hanya untuk peralatan yang dibangun, tua atau yang dikonversi, yang biasanya terdiri dari jaringan pipa dan struktur lain dari geometri silinder.

Untuk mendeteksi primitif geometri dalam 3DOT , perpustakaan 3D khusus, misalnya, Microsoft PCL , biasanya digunakan. Pendekatan dengan penggunaan perpustakaan siap pakai, bersama dengan kelebihannya, memiliki kelemahan. Misalnya, sulit untuk memasukkannya ke dalam skema pemrosesan Kadov yang ada, yang biasanya memiliki dimensi 2D.

Mari kita pertimbangkan bagaimana mungkin untuk memproses 3dOT , misalnya, stasiun pemompaan, dimulai dengan bagian 2D dan menggunakan seluruh gudang pemrosesan 2D, yang tersedia di perpustakaan pemrosesan gambar yang andal dan dioptimalkan, misalnya OpenCV .


Gambar 1. Model PL 3D stasiun pompa

Elemen utama dari bagian yang diperoleh dengan memindai berbagai struktur pipa adalah busur elips .


Gambar 2. Penampang horizontal model 3D stasiun pemompaan pada tingkat rata-rata

Untuk artikel ini, kami membatasi pertimbangan kami pada satu algoritma kunci yang memungkinkan kami mendeteksi busur elips yang sewenang-wenang - ini adalah algoritma berulang untuk pertumbuhan segmen busur dan pengikatan wilayah ( pertumbuhan wilayah dan penghubungan tepi ).

Algoritma pertumbuhan adalah yang paling jelas dan mudah diverifikasi, meskipun memakan waktu dibandingkan dengan algoritma statistik, yang lebih cocok untuk kasus ketika adegan berisi objek yang digabungkan longgar, jauh yang dimiliki oleh satu elips. Algoritma ini akan dibahas dalam artikel mendatang.


Untuk saat ini, untuk kesederhanaan, kami menghilangkan prosedur untuk mendapatkan bagian dari file sumber 3DOT , preprocessing bagian, mengelompokkannya untuk mengisolasi primitif geometris, serta mengikat berikutnya, perbaikan, dan operasi fotogrametrik lainnya yang diperlukan untuk mendapatkan parameter model. Kami tidak akan membahas parameterisasi algoritma pencarian heuristik dengan cara yang sama. Mari kita jelaskan semua operasi dasar dari mana algoritma dibangun.

Kami berasumsi bahwa kami perlu mendeteksi (mengenali, mengklasifikasikan) busur elips (yaitu, menghitung parameter elips, serta sudut awal dan akhir dari busur elips) pada gambar ini, memotong dari bagian horizontal awan titik.


Gambar 3. Salah satu busur elips dari penampang model 3D (setelah dihaluskan)

Untuk meminimalkan pekerjaan dengan raster secara membabi buta, kami akan melakukan semua operasi dengan raster melalui garis besar .

The OpenCV findContours prosedur penemuan pada raster tikar semua eksternal (tanpa bentuk internal) kontur dalam bentuk vektor dari titik vektor bilangan bulat (dalam koordinat raster):
 Mat mat(size);
 vector<vector<Point>> contours;
 findContours(mat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

Ini adalah operasi utama kami, yang dalam beberapa kasus sederhana sepenuhnya menyelesaikan tugas . Tetapi karena kasus-kasus degenerasi tidak selalu ditemukan, mari kita pertimbangkan secara lebih rinci teknologi pemrosesan melalui pembentukan kontur.

Operasi sebaliknya, menghasilkan raster sesuai dengan sirkuit eksternal yang ada menggunakan fungsi OpenCV , juga terlihat sederhana:
 drawContours(mat, contours, -1, Scalar(255), -1);

Itu juga sangat sering digunakan untuk menutupi kontur, menggambar, atau untuk menghitung luas.

Jadi, pada tahap awal, kami memiliki satu set tambalan (potongan kurva tertentu) yang perlu dihubungkan dalam busur elips, menghilangkan bagian-bagian komponen lain dari struktur (misalnya, pengencang) atau noise optik yang muncul akibat membayangi selama pemindaian dan lainnya. alasan.

Mari kita buat fungsi diskriminan yang akan mengembalikan jenis kontur (elips, segmen linier, penetasan atau yang lainnya), serta titik akhir kontur dan rotari garis besar yang diputar:
 contourTypeSearch(
   const vector<Point> &contour, Vec4i &def, RotatedRect &rc);

Rasio panjang dan lebar persegi panjang membantu untuk dengan cepat membedakan kontur dekat dengan segmen linier , serta kontur kebisingan kecil .

Kotak yang diputar di OpenCV memiliki sistem koordinat yang kompleks . Jika bukan sudut itu sendiri yang diperlukan, tetapi fungsi trigonometriknya , semakin tidak jelas dari konteksnya. Jika nilai absolut sudut digunakan , harus diperhitungkan bahwa sudut dihitung dari horizontal ke tepi pertama persegi panjang berlawanan arah jarum jam dan memiliki nilai negatif .

Titik akhir dari kontur elips ditemukan menggunakan prosedur kami, yang menerima raster matdengan garis diskriminasi yang diekstraksi dari gambar asli dengan menutupi dan mengembalikan cacat maksimum :
 contourConvFeature(mat, &def, … );

Kode utama untuk fungsi ini adalah memanggil dua prosedur OpenCV :

 vector<int> *hull = new vector<int>();
 convexHull(contour, *hull);
 vector<Vec4i> *defs = new vector<Vec4i>();
 convexityDefects(contour, *hull, *defs);

Prosedur pertama menemukan poligon cembung untuk kontur yang diteliti, yang kedua - menghitung semua cacat cembung .

Kami hanya mengambil cacat terbesar dalam hal cembung, mengingat ia menentukan titik akhir dari kontur. Ini mungkin tidak terjadi jika batas eksternal atau internal kontur memiliki fitur . Dalam rangka untuk memperlancar mereka , kami menerapkan smoothing tambahan untuk kontur yang diteliti (dan tidak seluruh gambar agar tidak “blur” yang isthmuses antara kontur dan tidak melanggar topologi asli).


Gambar 4. Perhitungan cacat tonjolan.Opsi

(a) secara salah mendefinisikan titik akhir merah. Opsi (b)mendefinisikan titik akhir dengan benar. Opsi (c) mendefinisikan ulang titik akhir pada bentuk aslinya.

Karena kontur yang dibuat ulang setiap kali dalam teknologi kami mengadopsi, kami harus re-mencari titik-titik korespondensi (atau lebih tepatnya, indeks mereka) dengan pencarian lengkap prosedur :
 nearestContourPtIdx(const vector<Point> &contour, const Point& pt);

Untuk kasus-kasus ketika tidak mungkin untuk sepenuhnya menghilangkan fitur, mode tambahan pemisahan busur juga dilaksanakan (bekerja secara terpisah dengan busur internal / eksternal). Ini penting, misalnya, dalam kasus di mana busur eksternal kontur bersentuhan dengan objek lain atau berisik . Dalam hal ini, Anda bisa bekerja dengan busur internal. Dalam hal ini, tidak perlu untuk memproses busur eksternal dan internal secara terpisah.

Juga, menurut rumus terkenal untuk rasio kerapatan busur , jari - jari lingkaran diperkirakan dan elips yang terlalu besar ditolak:
R = bulge / 2 + SQR(hypot) / (8 * bulge);

Dengan demikian, untuk semua kontur, metrik cacat konveksitasnya ditemukan (atau diklasifikasikan sebagai linier atau kecil dan dikeluarkan dari prosedur). Pada tahap terakhir, parameter tambahan ditambahkan ke metrik asli, seperti parameter dimensi yang diputar, dll., Dan set metrik lengkap yang sedang dipelajari dipesan berdasarkan ukuran .
 typedef tuple<int , // 
   RotatedRect, //  
   Vec4i, //  
   int> // 
   RectDefMetric;


Algoritma untuk menghubungkan segmen busur pada titik akhir


Algoritma pertumbuhan jelas dan jelas: kami mengambil kontur terbesar sebagai benih dan mencoba menumbuhkannya, yaitu, menemukan dan menempelkan tambalan terdekat ke titik akhir yang memenuhi kondisi pertumbuhan. Pada gambar dewasa, kita masukkan busur elips yang diinginkan . Topeng dan kurangi angka dari set aslinya. Kami ulangi prosedur pertumbuhan hingga set awal habis .

Prosedur dasar dari algoritma pertumbuhan terlihat seperti ini:
 vector<Point> *patch =
    growingContours(contour, def, tmp, hull);

di mana kontur adalah kontur yang diteliti, def adalah cacat cembungnya, lambung adalah poligon cembung dari seluruh wilayah, tmp adalah matriks buffer tambahan. Pada output, kita mendapatkan kontur vektor yang tumbuh.

Prosedur ini terdiri dari siklus upaya untuk menumbuhkan benih, diakhiri dengan melelahkan tambalan yang tersedia untuk pertumbuhan, atau dibatasi oleh parameter jumlah iterasi maksimum .


Gambar 5. Banyak tambalan untuk pertumbuhan tanpa benih

Kesulitan utama adalah memilih tambalan terdekat ke titik akhir kontur, sehingga gambar tumbuh hanya maju . Untuk arah tangensialkami mengambil garis rata-rata milik busur di sekitar titik akhir. Pada Gambar 6 menampilkan kandidat untuk koneksi ke seed pada iterasi tertentu.


Gambar 6. Benih dikelilingi oleh sejumlah tambalan kandidat pertumbuhan.

Untuk setiap tambalan kandidat, metrik berikut dihitung:
typedef tuple<
   double, //    2      2   
   bool, bool, //,  4   
   int, // 
   Vec4i> //  
   DistMetric;

Hanya tambalan yang jatuh ke kerucut tangensial yang diperhitungkan . Kemudian tambalan dengan jarak terkecil dipilih dan, dengan mencetak bagian penghubung ke dalam raster, terhubung ke ujung benih yang sesuai. Untuk ujung lain dari seed, sebuah patch yang cocok dengan parameter dicari, dan jika ditemukan, juga terhubung ke seed. Kemudian benih disamarkan dan dikurangi dari banyak tambalan. Prosedur ini diulangi sejak awal.

Pada akhir prosedur pertumbuhan, kami mendapatkan busur elips , yang masih harus diverifikasi.

Untuk memulai, gunakan OpenCV standarprosedur yang diterima patch kami (dalam bentuk jalur, kami ingat bahwa jalur dan raster dapat dipertukarkan dengan kami) dan mengembalikan dimensi yang diputar , yaitu, elips lengkap.
 RotatedRect box = fitEllipse(patch);

Kemudian, kami menolak elips yang terlalu besar dan terlalu kecil, dan kemudian kami menerapkan prosedur asli kami untuk membandingkan area dari busur elips yang dihasilkan dan patch pertumbuhan awal dalam bentuk raster . Prosedur ini mencakup beberapa trik yang disamarkan, jadi kami akan menghilangkan deskripsinya untuk saat ini.

Dan akhirnya, kami menemukan parameter yang tersisa dari elips yang terdeteksi - sudut awal dan akhir (kami sudah tahu setengah-sumbu dari fitEllipse ).

Untuk menentukan sudut awal dan akhir, kami melanjutkan sebagai berikut: kami mengubah elips lengkap kami, kembali ke poligon, dan dengan penghitungan langsung kami menemukan titik-titik yang paling dekat dengan ujung kami. Koordinat sudut mereka(Bahkan, indeks) dan akan menjadi sudut awal dan akhir dari busur elips. Dalam kode, tampilannya seperti ini (sedikit disederhanakan):
 pair<int, int>
   ellipseAngles(const RotatedRect &box,
   vector<Point> &ell, const Point &ps, 
   const Point &pe, const Point &pm) 
 {
    vector<Point> ell0;
    ellipse2Poly(Point(box.center.x, box.center.y), 
      Size(box.size.width / 2, box.size.height / 2),
      box.angle, 0, 355, 5, ell0);
    int i0 = nearestContourPtIdx(ell0, ps);
    int i1 = nearestContourPtIdx(ell0, pe);
    cutSides(ell0, i0, i1, i2, ell, nullptr);
    return pair<int, int>(i0, i1);
}

Prosedur cutSides kami memperhitungkan topologi arc traversal elips. Secara total, delapan kemungkinan kasus melewati indeks i0, i1, i2 harus dipertimbangkan . Apakah kita mengikuti kontur luar atau dalam, dan mana dari indeks yang lebih besar, awal atau akhir?

Lebih mudah melihat kode:
 void cutSides(
   const vector<Point> &ell0, int i0, int i1, int i2, 
   vector<Point> *ell_in, vector<Point> *ell_out)
 {
   if (i0 < i1) {
      if (i2 > i0 && i2 < i1) {
         if (ell_in) {...}
            if (ell_out) {...}
        } else {
            if (ell_in) {...}
            if (ell_out) {...}
        }}
    else {
        if (i2 > i1 && i2 < i0) {
            if (ell_in) {...}
            if (ell_out) {...}
        } else {
            if (ell_in) {...}
            if (ell_out) {...}
        }}}

Beberapa hasil mendeteksi elips dalam kasus kompleks ditunjukkan pada Gambar 7 .



Dalam artikel berikut, metode deteksi statistik akan dipertimbangkan.

All Articles