Bagaimana cara meratakan gambar label pada wadah makanan?

40

Saya ingin mengambil gambar label pada toples makanan, dan dapat mengubahnya sehingga labelnya rata, dengan sisi kanan dan kiri diubah ukurannya menjadi rata dengan bagian tengah gambar.

Idealnya, saya ingin menggunakan kontras antara label dan latar belakang untuk menemukan tepi dan menerapkan koreksi. Kalau tidak, saya bisa meminta pengguna untuk mengidentifikasi sudut dan sisi gambar.


Saya sedang mencari teknik dan algoritma umum untuk mengambil gambar yang miring secara sphere (dalam kasus saya berbentuk silindris) dan dapat meratakan gambar. Saat ini gambar label yang dililitkan di sekitar toples atau botol, akan memiliki fitur dan teks yang menyusut ketika surut ke kanan atau kiri gambar. Juga garis-garis yang menunjukkan tepi label, hanya akan sejajar di tengah gambar, dan akan condong ke arah satu sama lain di sisi kanan dan kiri label.

Setelah memanipulasi gambar, saya ingin dibiarkan dengan kotak hampir sempurna di mana teks dan fitur berukuran seragam, seolah-olah saya mengambil gambar label ketika itu tidak ada di botol atau botol.

Saya juga ingin jika teknik ini dapat secara otomatis mendeteksi tepi label, untuk menerapkan koreksi yang sesuai. Kalau tidak, saya harus meminta pengguna saya untuk menunjukkan batas label.

Saya sudah mencari di Google dan menemukan artikel seperti ini: meratakan dokumen melengkung , tapi saya mencari sesuatu yang sedikit lebih sederhana, karena kebutuhan saya adalah label dengan kurva sederhana.

mahboudz
sumber
Nikie memiliki apa yang tampaknya menjadi solusi menyeluruh. Akan lebih mudah, jika Anda tahu bahwa kamera selalu "persegi" untuk tabung, tanpa latar belakang yang membingungkan. Kemudian Anda menemukan tepi toples dan menerapkan transformasi trigonometri (arcsine?) Sederhana, tanpa banyak mengutak-atik. Setelah gambar diratakan, Anda dapat mengisolasi label itu sendiri.
Daniel R Hicks
@Aniel Itulah yang saya lakukan di sini . Idealnya orang akan memperhitungkan proyeksi tidak paralel sempurna juga, tetapi saya tidak melakukannya.
Szabolcs
pekerjaannya sangat bagus. tetapi kode menunjukkan kesalahan di sistem saya. Saya menggunakan matlab 2017a apakah itu kompatibel dengannya. terima kasih,
Satish Kumar

Jawaban:

60

Sebuah pertanyaan serupa diminta di Mathematica.Stackexchange . Jawaban saya di sana berkembang dan cukup panjang pada akhirnya, jadi saya akan meringkas algoritme di sini.

Abstrak

Ide dasarnya adalah:

  1. Temukan labelnya.
  2. Temukan batas label
  3. Temukan pemetaan yang memetakan koordinat gambar ke koordinat silinder sehingga memetakan piksel di sepanjang batas atas label ke ([apa saja] / 0), piksel di sepanjang batas kanan ke (1 / [apa saja]), dan seterusnya.
  4. Ubah gambar menggunakan pemetaan ini

Algoritme hanya berfungsi untuk gambar yang:

  1. label lebih cerah dari latar belakang (ini diperlukan untuk deteksi label)
  2. label berbentuk segi empat (ini digunakan untuk mengukur kualitas pemetaan)
  3. tabung (hampir) vertikal (ini digunakan untuk menjaga fungsi pemetaan tetap sederhana)
  4. tabung itu berbentuk silinder (ini digunakan untuk menjaga fungsi pemetaan tetap sederhana)

Namun, algoritma ini bersifat modular. Setidaknya pada prinsipnya, Anda dapat menulis deteksi label Anda sendiri yang tidak memerlukan latar belakang gelap, atau Anda dapat menulis fungsi pengukuran kualitas Anda sendiri yang dapat mengatasi label elips atau oktagonal.

Hasil

Gambar-gambar ini diproses sepenuhnya secara otomatis, yaitu algoritma mengambil gambar sumber, bekerja selama beberapa detik, kemudian menunjukkan pemetaan (kiri) dan gambar tidak terdistorsi (kanan):

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Gambar berikutnya diproses dengan versi algoritma yang dimodifikasi, jika pengguna memilih batas kiri dan kanan tabung (bukan label), karena kelengkungan label tidak dapat diperkirakan dari gambar dalam bidikan frontal (yaitu algoritma sepenuhnya otomatis akan mengembalikan gambar yang sedikit terdistorsi):

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Pelaksanaan:

1. Temukan label

Labelnya cerah di depan latar belakang yang gelap, jadi saya bisa menemukannya dengan mudah menggunakan binarisasi:

src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]

gambar binarized

Saya cukup memilih komponen terhubung terbesar dan menganggap itu label:

labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]

komponen terbesar

2. Temukan batas label

Langkah selanjutnya: temukan batas atas / bawah / kiri / kanan menggunakan masker konvolusi derivatif sederhana:

topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];

masukkan deskripsi gambar di sini

Ini adalah fungsi pembantu kecil yang menemukan semua piksel putih di salah satu dari empat gambar ini dan mengonversi indeks menjadi koordinat ( Positionmengembalikan indeks, dan indeks berbasis 1 {y, x} -tupel, di mana y = 1 berada di atas Tetapi semua fungsi pemrosesan gambar mengharapkan koordinat, yang merupakan 0-{{x, y} -tuples, di mana y = 0 adalah bagian bawah gambar):

{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];

3. Temukan pemetaan dari koordinat gambar ke silinder

Sekarang saya memiliki empat daftar koordinat terpisah dari batas atas, bawah, kiri, kanan label. Saya mendefinisikan pemetaan dari koordinat gambar ke koordinat silinder:

arcSinSeries = Normal[Series[ArcSin[\[Alpha]], {\[Alpha], 0, 10}]]
Clear[mapping];
mapping[{x_, y_}] := 
   {
    c1 + c2*(arcSinSeries /. \[Alpha] -> (x - cx)/r) + c3*y + c4*x*y, 
    top + y*height + tilt1*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]] + tilt2*y*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]]
   }

Ini adalah pemetaan silindris, yang memetakan koordinat X / Y pada gambar sumber ke koordinat silindris. Pemetaan memiliki 10 derajat kebebasan untuk ketinggian / radius / pusat / perspektif / kemiringan. Saya menggunakan seri Taylor untuk memperkirakan arc arc, karena saya tidak bisa mendapatkan optimasi bekerja dengan ArcSin secara langsung. ItuClippanggilan adalah upaya ad-hoc saya untuk mencegah angka-angka kompleks selama optimasi. Ada trade-off di sini: Di ​​satu sisi, fungsinya harus sedekat mungkin dengan pemetaan silinder yang tepat, untuk memberikan distorsi serendah mungkin. Di sisi lain, jika terlalu rumit, akan semakin sulit untuk menemukan nilai optimal untuk derajat kebebasan secara otomatis. (Yang menyenangkan tentang melakukan pemrosesan gambar dengan Mathematica adalah bahwa Anda dapat bermain-main dengan model matematika seperti ini dengan sangat mudah, memperkenalkan istilah tambahan untuk distorsi yang berbeda dan menggunakan fungsi optimisasi yang sama untuk mendapatkan hasil akhir. Saya tidak pernah bisa melakukan apa pun. seperti itu menggunakan OpenCV atau Matlab. Tapi saya tidak pernah mencoba kotak alat simbolis untuk Matlab, mungkin itu membuatnya lebih berguna.)

Selanjutnya saya mendefinisikan "fungsi kesalahan" yang mengukur kualitas gambar -> pemetaan koordinat silinder. Ini hanya jumlah kesalahan kuadrat untuk piksel perbatasan:

errorFunction =
  Flatten[{
    (mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
    (mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
    (mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
    (mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
    }];

Fungsi kesalahan ini mengukur "kualitas" pemetaan: Paling rendah jika titik-titik di perbatasan kiri dipetakan ke (0 / [apa saja]), piksel pada batas atas dipetakan ke ([apa saja] / 0) dan seterusnya .

Sekarang saya dapat memberitahu Mathematica untuk menemukan koefisien yang meminimalkan fungsi kesalahan ini. Saya dapat membuat "tebakan-tebakan" tentang beberapa koefisien (mis. Jari-jari dan pusat tabung pada gambar). Saya menggunakan ini sebagai titik awal optimasi:

leftMean = Mean[maskToPoints[leftBorder]][[1]];
rightMean = Mean[maskToPoints[rightBorder]][[1]];
topMean = Mean[maskToPoints[topBorder]][[2]];
bottomMean = Mean[maskToPoints[bottomBorder]][[2]];
solution = 
 FindMinimum[
   Total[errorFunction], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {cx, (leftMean + rightMean)/2}, 
     {top, topMean}, 
     {r, rightMean - leftMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]

FindMinimummenemukan nilai untuk 10 derajat kebebasan fungsi pemetaan saya yang meminimalkan fungsi kesalahan. Gabungkan pemetaan generik dan solusi ini dan saya mendapatkan pemetaan dari koordinat gambar X / Y, yang sesuai dengan area label. Saya dapat memvisualisasikan pemetaan ini menggunakan ContourPlotfungsi Mathematica :

Show[src,
 ContourPlot[mapping[{x, y}][[1]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.1], 
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[2]] /. solution) <= 1]],
 ContourPlot[mapping[{x, y}][[2]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.2],
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[1]] /. solution) <= 1]]]

masukkan deskripsi gambar di sini

4. Ubah gambar

Akhirnya, saya menggunakan ImageForwardTransformfungsi Mathematica untuk mengubah gambar sesuai dengan pemetaan ini:

ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]

Itu memberikan hasil seperti yang ditunjukkan di atas.

Versi yang dibantu secara manual

Algoritma di atas adalah otomatis penuh. Tidak diperlukan penyesuaian. Ini bekerja dengan baik selama gambar diambil dari atas atau bawah. Tetapi jika itu adalah pukulan frontal, jari-jari jar tidak dapat diperkirakan dari bentuk label. Dalam kasus ini, saya mendapatkan hasil yang lebih baik jika saya membiarkan pengguna memasukkan batas kiri / kanan jar secara manual, dan mengatur derajat kebebasan yang sesuai dalam pemetaan secara eksplisit.

Kode ini memungkinkan pengguna memilih batas kiri / kanan:

LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}], 
 Dynamic[Show[src, 
   Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}], 
     Line[{{xRight, 0}, {xRight, h}}]}]]]]

LocatorPane

Ini adalah kode optimalisasi alternatif, di mana pusat & radius diberikan secara eksplisit.

manualAdjustments = {cx -> (xLeft + xRight)/2, r -> (xRight - xLeft)/2};
solution = 
  FindMinimum[
   Total[minimize /. manualAdjustments], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {top, topMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]
solution = Join[solution, manualAdjustments]
Niki Estner
sumber
11
Menghapus kacamata hitam ... ibu dewa ...
Spacey
Apakah Anda memiliki referensi untuk pemetaan silindris? Dan mungkin persamaan untuk pemetaan terbalik? @ niki-estner
Ita