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:
- Temukan labelnya.
- Temukan batas label
- 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.
- Ubah gambar menggunakan pemetaan ini
Algoritme hanya berfungsi untuk gambar yang:
- label lebih cerah dari latar belakang (ini diperlukan untuk deteksi label)
- label berbentuk segi empat (ini digunakan untuk mengukur kualitas pemetaan)
- tabung (hampir) vertikal (ini digunakan untuk menjaga fungsi pemetaan tetap sederhana)
- 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):
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):
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]]]
Saya cukup memilih komponen terhubung terbesar dan menganggap itu label:
labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]
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}}]];
Ini adalah fungsi pembantu kecil yang menemukan semua piksel putih di salah satu dari empat gambar ini dan mengonversi indeks menjadi koordinat ( Position
mengembalikan 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. ItuClip
panggilan 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]]
FindMinimum
menemukan 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 ContourPlot
fungsi 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]]]
4. Ubah gambar
Akhirnya, saya menggunakan ImageForwardTransform
fungsi 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}}]}]]]]
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]