Prolog
Subjek ini muncul di sini di Stack Overflow dari waktu ke waktu, tetapi biasanya dihapus karena pertanyaan yang ditulis dengan buruk. Saya melihat banyak pertanyaan seperti itu dan kemudian diam dari OP (biasanya repetisi rendah) ketika informasi tambahan diminta. Dari waktu ke waktu, jika masukannya cukup baik bagi saya, saya memutuskan untuk menjawab dengan jawaban dan biasanya mendapat beberapa suara positif per hari saat aktif, tetapi kemudian setelah beberapa minggu, pertanyaan itu dihapus / dihapus dan semua dimulai dari awal. Jadi saya memutuskan untuk menulis T&J ini agar saya dapat merujuk pertanyaan semacam itu secara langsung tanpa menulis ulang jawabannya berulang kali…
Alasan lain juga meta thread ini ditargetkan ke saya jadi jika Anda mendapat masukan tambahan, silakan berkomentar.
Pertanyaan
Bagaimana cara mengonversi gambar bitmap ke seni ASCII menggunakan C ++ ?
Beberapa kendala:
- gambar skala abu-abu
- menggunakan font spasi tunggal
- menjaganya tetap sederhana (tidak menggunakan hal-hal yang terlalu canggih untuk pemrogram tingkat pemula)
Ini adalah halaman Wikipedia terkait seni ASCII (terima kasih kepada @RogerRowland).
Di sini labirin serupa dengan Q&A konversi Seni ASCII .
Jawaban:
Ada lebih banyak pendekatan untuk konversi gambar ke seni ASCII yang sebagian besar didasarkan pada penggunaan font spasi tunggal . Untuk kesederhanaan, saya hanya berpegang pada dasar:
Berbasis piksel / intensitas area (bayangan)
Pendekatan ini menangani setiap piksel dari suatu area piksel sebagai satu titik. Idenya adalah untuk menghitung intensitas skala abu-abu rata-rata dari titik ini dan kemudian menggantinya dengan karakter dengan intensitas yang cukup dekat dengan yang dihitung. Untuk itu kita membutuhkan beberapa daftar karakter yang dapat digunakan, masing-masing dengan intensitas yang telah dihitung sebelumnya. Sebut saja itu karakter
map
. Untuk lebih cepat memilih karakter mana yang terbaik untuk intensitas mana, ada dua cara:Peta karakter intensitas terdistribusi secara linier
Jadi kami hanya menggunakan karakter yang memiliki perbedaan intensitas dengan langkah yang sama. Dengan kata lain, jika diurutkan secara ascending maka:
Juga ketika karakter kita
map
diurutkan maka kita dapat menghitung karakter secara langsung dari intensitas (tidak perlu pencarian)Peta karakter intensitas terdistribusi sewenang-wenang
Jadi kami memiliki berbagai karakter yang dapat digunakan dan intensitasnya. Kita perlu menemukan intensitas yang paling dekat dengan
intensity_of(dot)
Jadi jika kita mengurutkanmap[]
, kita dapat menggunakan pencarian biner, jika tidak kita memerlukanO(n)
pencarian loop jarak minimum atauO(1)
kamus. Kadang-kadang untuk kesederhanaan, karaktermap[]
dapat ditangani sebagai terdistribusi linier, menyebabkan sedikit distorsi gamma, biasanya tidak terlihat dalam hasil kecuali Anda tahu apa yang harus dicari.Konversi berbasis intensitas juga bagus untuk gambar skala abu-abu (tidak hanya hitam dan putih). Jika Anda memilih titik sebagai piksel tunggal, hasilnya menjadi besar (satu piksel -> karakter tunggal), jadi untuk gambar yang lebih besar, sebuah area (perkalian ukuran font) dipilih sebagai gantinya untuk mempertahankan rasio aspek dan tidak memperbesar terlalu banyak.
Bagaimana cara melakukannya:
Sebagai karakter,
map
Anda dapat menggunakan karakter apa saja, tetapi hasilnya akan lebih baik jika karakter memiliki piksel yang tersebar secara merata di sepanjang area karakter. Sebagai permulaan, Anda dapat menggunakan:char map[10]=" .,:;ox%#@";
diurutkan menurun dan berpura-pura terdistribusi linier.
Jadi jika intensitas piksel / area sesuai
i = <0-255>
maka karakter pengganti akan terbentukmap[(255-i)*10/256];
Jika
i==0
kemudian piksel / area berwarna hitam, jikai==127
piksel / area berwarna abu-abu, dan jikai==255
piksel / area berwarna putih. Anda dapat bereksperimen dengan berbagai karakter di dalammap[]
...Berikut adalah contoh kuno saya di C ++ dan VCL:
Anda perlu mengganti / mengabaikan hal-hal VCL kecuali Anda menggunakan lingkungan Borland / Embarcadero .
mm_log
adalah memo tempat teks dikeluarkanbmp
adalah bitmap masukanAnsiString
adalah jenis string VCL yang diindeks dari 1, bukan dari 0 sebagaichar*
!!!Hasilnya: Gambar contoh intensitas NSFW sedikit
Di sebelah kiri adalah keluaran seni ASCII (ukuran font 5 piksel), dan di sebelah kanan gambar masukan diperbesar beberapa kali. Seperti yang Anda lihat, hasilnya adalah piksel yang lebih besar -> karakter. Jika Anda menggunakan area yang lebih besar daripada piksel maka zoomnya lebih kecil, tetapi tentu saja outputnya kurang menyenangkan secara visual.Pendekatan ini sangat mudah dan cepat untuk kode / proses.
Saat Anda menambahkan hal-hal yang lebih canggih seperti:
Kemudian Anda dapat memproses gambar yang lebih kompleks dengan hasil yang lebih baik:
Berikut adalah hasil perbandingan 1: 1 (perbesar untuk melihat karakter):
Tentu saja, untuk pengambilan sampel area Anda kehilangan detail-detail kecil. Ini adalah gambar dengan ukuran yang sama seperti contoh pertama yang diambil sampelnya dengan area:
Gambar contoh tingkat lanjut intensitas NSFW sedikit
Seperti yang Anda lihat, ini lebih cocok untuk gambar yang lebih besar.
Pemasangan karakter (gabungan antara seni ASCII yang teduh dan padat)
Pendekatan ini mencoba untuk mengganti area (tidak ada lagi titik piksel tunggal) dengan karakter dengan intensitas dan bentuk yang serupa. Ini mengarah pada hasil yang lebih baik, bahkan dengan font yang lebih besar yang digunakan dibandingkan dengan pendekatan sebelumnya. Di sisi lain, pendekatan ini tentu saja sedikit lebih lambat. Ada lebih banyak cara untuk melakukan ini, tetapi ide utamanya adalah menghitung perbedaan (jarak) antara area gambar (
dot
) dan karakter yang diberikan. Anda dapat memulai dengan jumlah naif dari perbedaan absolut antar piksel, tetapi itu tidak akan memberikan hasil yang sangat baik karena bahkan pergeseran satu piksel akan membuat jarak menjadi besar. Sebagai gantinya, Anda dapat menggunakan korelasi atau metrik yang berbeda. Algoritme keseluruhan hampir sama dengan pendekatan sebelumnya:Jadi bagi gambar secara merata ke titik area persegi panjang (skala abu-abu) 's
idealnya dengan rasio aspek yang sama seperti karakter font yang dirender (ini akan mempertahankan rasio aspek. Jangan lupa bahwa karakter biasanya sedikit tumpang tindih pada sumbu x)
Hitung intensitas setiap area (
dot
)Gantilah dengan karakter dari karakter
map
dengan intensitas / bentuk terdekatBagaimana kita menghitung jarak antara karakter dan titik? Itu adalah bagian tersulit dari pendekatan ini. Saat bereksperimen, saya mengembangkan kompromi antara kecepatan, kualitas, dan kesederhanaan:
Bagilah area karakter ke zona
map
).i=(i*256)/(xs*ys)
.Proses gambar sumber di area persegi panjang
Ini adalah hasil untuk ukuran font = 7 piksel
Seperti yang Anda lihat, outputnya secara visual menyenangkan, bahkan dengan ukuran font yang lebih besar yang digunakan (contoh pendekatan sebelumnya adalah dengan ukuran font 5 piksel). Outputnya kira-kira berukuran sama dengan gambar input (tanpa zoom). Hasil yang lebih baik dicapai karena karakter lebih mendekati gambar asli, tidak hanya berdasarkan intensitas, tetapi juga bentuk keseluruhan, dan oleh karena itu Anda dapat menggunakan font yang lebih besar dan tetap mempertahankan detail (hingga satu titik tentunya).
Berikut kode lengkap untuk aplikasi konversi berbasis VCL:
Ini adalah aplikasi formulir (
Form1
) sederhana dengan satuTMemo mm_txt
di dalamnya. Ini memuat gambar,"pic.bmp"
dan kemudian sesuai dengan resolusinya, pilih pendekatan mana yang akan digunakan untuk mengubah ke teks yang disimpan"pic.txt"
dan dikirim ke memo untuk divisualisasikan.Bagi mereka yang tidak memiliki VCL, abaikan VCL dan ganti
AnsiString
dengan tipe string apa pun yang Anda miliki, dan jugaGraphics::TBitmap
dengan bitmap atau kelas gambar yang Anda miliki dengan kemampuan akses piksel.Catatan yang sangat penting adalah bahwa ini menggunakan pengaturan
mm_txt->Font
, jadi pastikan Anda mengatur:Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
untuk membuat ini berfungsi dengan baik, jika tidak font tidak akan ditangani sebagai spasi tunggal. Roda mouse hanya mengubah ukuran font naik / turun untuk melihat hasil pada ukuran font yang berbeda.
[Catatan]
3x3
gantinya.Perbandingan
Terakhir, berikut perbandingan antara dua pendekatan pada input yang sama:
Gambar bertanda titik hijau dilakukan dengan pendekatan # 2 dan yang merah dengan # 1 , semuanya dalam ukuran font enam piksel. Seperti yang dapat Anda lihat pada gambar bola lampu, pendekatan peka bentuk jauh lebih baik (meskipun # 1 dilakukan pada gambar sumber yang diperbesar 2x).
Aplikasi keren
Saat membaca pertanyaan baru hari ini, saya mendapat ide tentang aplikasi keren yang mengambil wilayah desktop yang dipilih dan terus-menerus memasukkannya ke konverter ASCIIart dan melihat hasilnya. Setelah satu jam pengkodean, selesai dan saya sangat puas dengan hasilnya sehingga saya harus menambahkannya di sini.
OK aplikasinya hanya terdiri dari dua jendela. Jendela master pertama pada dasarnya adalah jendela konverter lama saya tanpa pemilihan dan pratinjau gambar (semua hal di atas ada di dalamnya). Ini hanya memiliki pratinjau ASCII dan pengaturan konversi. Jendela kedua adalah formulir kosong dengan bagian dalam transparan untuk pemilihan area pengambilan (tidak ada fungsi apa pun).
Sekarang pada pengatur waktu, saya hanya mengambil area yang dipilih dengan formulir pilihan, meneruskannya ke konversi, dan melihat pratinjau ASCIIart .
Jadi Anda menyertakan area yang ingin Anda ubah dengan jendela pemilihan dan melihat hasilnya di jendela master. Ini bisa menjadi permainan, penampil, dll. Tampilannya seperti ini:
Jadi sekarang saya bahkan dapat menonton video di ASCIIart untuk bersenang-senang. Beberapa sangat bagus :).
Jika Anda ingin mencoba menerapkan ini di GLSL , lihat ini:
sumber
3x3
zona dan membandingkan DCT tetapi itu akan banyak menurunkan kinerja menurut saya.