Dengan pengenalan tinta digital ML Kit, Anda dapat mengenali teks yang ditulis tangan pada tampilan digital dalam ratusan bahasa, serta mengklasifikasikan sketsa.
Cobalah
- Cobalah aplikasi contoh untuk melihat contoh penggunaan API ini.
Sebelum memulai
Sertakan library ML Kit berikut di Podfile Anda:
pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
Setelah menginstal atau mengupdate Pod project, buka project Xcode Anda menggunakan
.xcworkspace
-nya. ML Kit didukung dalam versi Xcode 13.2.1 atau yang lebih baru.
Sekarang Anda siap untuk mulai mengenali teks dalam objek Ink
.
Membuat objek Ink
Cara utama untuk membuat objek Ink
adalah dengan menggambarnya di layar sentuh. Di iOS,
Anda dapat menggunakan UIImageView bersama dengan
peristiwa sentuh
pengendali
yang menggambar {i>stroke<i} di layar dan
juga menyimpan {i>stroke<i} itu poin untuk membangun
objek Ink
. Pola umum ini ditunjukkan dalam kode berikut
cuplikan kode. Lihat panduan memulai
aplikasi untuk
contoh yang lebih lengkap, yang memisahkan
penanganan peristiwa sentuh, gambar layar,
dan melakukan{i> stroke<i} pada manajemen data.
Swift
@IBOutlet weak var mainImageView: UIImageView! var kMillisecondsPerTimeInterval = 1000.0 var lastPoint = CGPoint.zero private var strokes: [Stroke] = [] private var points: [StrokePoint] = [] func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) { UIGraphicsBeginImageContext(view.frame.size) guard let context = UIGraphicsGetCurrentContext() else { return } mainImageView.image?.draw(in: view.bounds) context.move(to: fromPoint) context.addLine(to: toPoint) context.setLineCap(.round) context.setBlendMode(.normal) context.setLineWidth(10.0) context.setStrokeColor(UIColor.white.cgColor) context.strokePath() mainImageView.image = UIGraphicsGetImageFromCurrentImageContext() mainImageView.alpha = 1.0 UIGraphicsEndImageContext() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } lastPoint = touch.location(in: mainImageView) let t = touch.timestamp points = [StrokePoint.init(x: Float(lastPoint.x), y: Float(lastPoint.y), t: Int(t * kMillisecondsPerTimeInterval))] drawLine(from:lastPoint, to:lastPoint) } override func touchesMoved(_ touches: Set , with event: UIEvent?) { guard let touch = touches.first else { return } let currentPoint = touch.location(in: mainImageView) let t = touch.timestamp points.append(StrokePoint.init(x: Float(currentPoint.x), y: Float(currentPoint.y), t: Int(t * kMillisecondsPerTimeInterval))) drawLine(from: lastPoint, to: currentPoint) lastPoint = currentPoint } override func touchesEnded(_ touches: Set , with event: UIEvent?) { guard let touch = touches.first else { return } let currentPoint = touch.location(in: mainImageView) let t = touch.timestamp points.append(StrokePoint.init(x: Float(currentPoint.x), y: Float(currentPoint.y), t: Int(t * kMillisecondsPerTimeInterval))) drawLine(from: lastPoint, to: currentPoint) lastPoint = currentPoint strokes.append(Stroke.init(points: points)) self.points = [] doRecognition() }
Objective-C
// Interface @property (weak, nonatomic) IBOutlet UIImageView *mainImageView; @property(nonatomic) CGPoint lastPoint; @property(nonatomic) NSMutableArray*strokes; @property(nonatomic) NSMutableArray *points; // Implementations static const double kMillisecondsPerTimeInterval = 1000.0; - (void)drawLineFrom:(CGPoint)fromPoint to:(CGPoint)toPoint { UIGraphicsBeginImageContext(self.mainImageView.frame.size); [self.mainImageView.image drawInRect:CGRectMake(0, 0, self.mainImageView.frame.size.width, self.mainImageView.frame.size.height)]; CGContextMoveToPoint(UIGraphicsGetCurrentContext(), fromPoint.x, fromPoint.y); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), toPoint.x, toPoint.y); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 10.0); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1, 1, 1, 1); CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal); CGContextStrokePath(UIGraphicsGetCurrentContext()); CGContextFlush(UIGraphicsGetCurrentContext()); self.mainImageView.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } - (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event { UITouch *touch = [touches anyObject]; self.lastPoint = [touch locationInView:self.mainImageView]; NSTimeInterval time = [touch timestamp]; self.points = [NSMutableArray array]; [self.points addObject:[[MLKStrokePoint alloc] initWithX:self.lastPoint.x y:self.lastPoint.y t:time * kMillisecondsPerTimeInterval]]; [self drawLineFrom:self.lastPoint to:self.lastPoint]; } - (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint currentPoint = [touch locationInView:self.mainImageView]; NSTimeInterval time = [touch timestamp]; [self.points addObject:[[MLKStrokePoint alloc] initWithX:currentPoint.x y:currentPoint.y t:time * kMillisecondsPerTimeInterval]]; [self drawLineFrom:self.lastPoint to:currentPoint]; self.lastPoint = currentPoint; } - (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint currentPoint = [touch locationInView:self.mainImageView]; NSTimeInterval time = [touch timestamp]; [self.points addObject:[[MLKStrokePoint alloc] initWithX:currentPoint.x y:currentPoint.y t:time * kMillisecondsPerTimeInterval]]; [self drawLineFrom:self.lastPoint to:currentPoint]; self.lastPoint = currentPoint; if (self.strokes == nil) { self.strokes = [NSMutableArray array]; } [self.strokes addObject:[[MLKStroke alloc] initWithPoints:self.points]]; self.points = nil; [self doRecognition]; }
Perhatikan bahwa cuplikan kode menyertakan fungsi contoh untuk menggambar {i>stroke<i} ke dalam
UIImageView,
yang harus disesuaikan seperlunya untuk aplikasi Anda. Sebaiknya gunakan
{i>roundcaps<i} ketika menggambar segmen garis sehingga
segmen panjang nol akan
digambar sebagai titik (seperti titik pada huruf kecil i). doRecognition()
dipanggil setelah setiap {i>stroke<i} ditulis dan akan didefinisikan di bawah ini.
Mendapatkan instance DigitalInkRecognizer
Untuk melakukan pengenalan, kita harus meneruskan objek Ink
ke objek
Instance DigitalInkRecognizer
. Untuk mendapatkan instance DigitalInkRecognizer
,
kita harus terlebih dahulu mengunduh model pengenal untuk bahasa yang diinginkan, dan
memuat model ke dalam RAM. Hal ini dapat dilakukan dengan menggunakan kode berikut
cuplikan, yang agar lebih mudah ditempatkan dalam metode viewDidLoad()
dan menggunakan
nama bahasa yang di-hardcode. Lihat panduan memulai
aplikasi untuk
contoh cara menampilkan daftar bahasa yang tersedia untuk pengguna dan mengunduh
bahasa yang dipilih.
Swift
override func viewDidLoad() { super.viewDidLoad() let languageTag = "en-US" let identifier = DigitalInkRecognitionModelIdentifier(forLanguageTag: languageTag) if identifier == nil { // no model was found or the language tag couldn't be parsed, handle error. } let model = DigitalInkRecognitionModel.init(modelIdentifier: identifier!) let modelManager = ModelManager.modelManager() let conditions = ModelDownloadConditions.init(allowsCellularAccess: true, allowsBackgroundDownloading: true) modelManager.download(model, conditions: conditions) // Get a recognizer for the language let options: DigitalInkRecognizerOptions = DigitalInkRecognizerOptions.init(model: model) recognizer = DigitalInkRecognizer.digitalInkRecognizer(options: options) }
Objective-C
- (void)viewDidLoad { [super viewDidLoad]; NSString *languagetag = @"en-US"; MLKDigitalInkRecognitionModelIdentifier *identifier = [MLKDigitalInkRecognitionModelIdentifier modelIdentifierForLanguageTag:languagetag]; if (identifier == nil) { // no model was found or the language tag couldn't be parsed, handle error. } MLKDigitalInkRecognitionModel *model = [[MLKDigitalInkRecognitionModel alloc] initWithModelIdentifier:identifier]; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager downloadModel:model conditions:[[MLKModelDownloadConditions alloc] initWithAllowsCellularAccess:YES allowsBackgroundDownloading:YES]]; MLKDigitalInkRecognizerOptions *options = [[MLKDigitalInkRecognizerOptions alloc] initWithModel:model]; self.recognizer = [MLKDigitalInkRecognizer digitalInkRecognizerWithOptions:options]; }
Aplikasi panduan memulai menyertakan kode tambahan yang menunjukkan cara menangani beberapa unduhan secara bersamaan, dan cara menentukan download mana yang berhasil dengan menangani notifikasi penyelesaian.
Mengenali objek Ink
Selanjutnya, kita sampai ke fungsi doRecognition()
, yang untuk kesederhanaan disebut
dari touchesEnded()
. Di aplikasi lain yang
mungkin ingin dipanggil
pengenalan hanya setelah waktu tunggu, atau saat pengguna menekan tombol untuk memicu
pengenalan objek.
Swift
func doRecognition() { let ink = Ink.init(strokes: strokes) recognizer.recognize( ink: ink, completion: { [unowned self] (result: DigitalInkRecognitionResult?, error: Error?) in var alertTitle = "" var alertText = "" if let result = result, let candidate = result.candidates.first { alertTitle = "I recognized this:" alertText = candidate.text } else { alertTitle = "I hit an error:" alertText = error!.localizedDescription } let alert = UIAlertController(title: alertTitle, message: alertText, preferredStyle: UIAlertController.Style.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) self.present(alert, animated: true, completion: nil) } ) }
Objective-C
- (void)doRecognition { MLKInk *ink = [[MLKInk alloc] initWithStrokes:self.strokes]; __weak typeof(self) weakSelf = self; [self.recognizer recognizeInk:ink completion:^(MLKDigitalInkRecognitionResult *_Nullable result, NSError *_Nullable error) { typeof(weakSelf) strongSelf = weakSelf; if (strongSelf == nil) { return; } NSString *alertTitle = nil; NSString *alertText = nil; if (result.candidates.count > 0) { alertTitle = @"I recognized this:"; alertText = result.candidates[0].text; } else { alertTitle = @"I hit an error:"; alertText = [error localizedDescription]; } UIAlertController *alert = [UIAlertController alertControllerWithTitle:alertTitle message:alertText preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; [strongSelf presentViewController:alert animated:YES completion:nil]; }]; }
Mengelola download model
Kita telah melihat cara mendownload model pengenalan. Kode berikut cuplikan kode yang menggambarkan cara memeriksa apakah model sudah didownload, atau menghapus model jika tidak lagi diperlukan untuk memulihkan ruang penyimpanan.
Memeriksa apakah model sudah didownload
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() modelManager.isModelDownloaded(model)
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager isModelDownloaded:model];
Menghapus model yang didownload
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() if modelManager.isModelDownloaded(model) { modelManager.deleteDownloadedModel( model!, completion: { error in if error != nil { // Handle error return } NSLog(@"Model deleted."); }) }
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; if ([self.modelManager isModelDownloaded:model]) { [self.modelManager deleteDownloadedModel:model completion:^(NSError *_Nullable error) { if (error) { // Handle error. return; } NSLog(@"Model deleted."); }]; }
Tips untuk meningkatkan akurasi pengenalan teks
Keakuratan pengenalan teks dapat bervariasi di berbagai bahasa. Akurasi juga bergantung tentang gaya penulisan. Pengenalan Tinta Digital dilatih untuk menangani berbagai jenis gaya penulisan, hasilnya dapat bervariasi dari satu pengguna ke pengguna lainnya.
Berikut beberapa cara untuk meningkatkan akurasi pengenal teks. Perhatikan bahwa teknik-teknik ini melakukan tidak berlaku untuk pengklasifikasi gambar untuk emoji, gambar otomatis, dan bentuk.
Area menulis
Banyak aplikasi memiliki area penulisan yang didefinisikan dengan baik untuk input pengguna. Arti dari suatu simbol adalah sebagian ditentukan oleh ukurannya yang relatif terhadap ukuran area tulisan yang memuatnya. Misalnya, perbedaan antara huruf "o" kecil atau besar atau "c", dan koma versus garis miring.
Memberi tahu pengenal tentang lebar dan tinggi area tulisan dapat meningkatkan akurasi. Namun, pengenal mengasumsikan bahwa area penulisan hanya berisi satu baris teks. Jika fisik area tulisannya cukup besar sehingga pengguna dapat menulis dua baris atau lebih, Anda mungkin akan lebih hasil dengan meneruskan WritingArea dengan ketinggian yang merupakan perkiraan terbaik Anda mengenai tinggi satu baris teks. Objek WritingArea yang Anda teruskan ke pengenal tidak harus sesuai persis dengan area tulisan fisik di layar. Mengubah tinggi WritingArea dengan cara ini berfungsi lebih baik di beberapa bahasa dibandingkan bahasa lainnya.
Saat menetapkan area penulisan, tentukan lebar dan tingginya dalam unit yang sama dengan goresan. pada koordinat tertentu. Argumen koordinat x,y tidak memiliki persyaratan satuan - API menormalisasi semua satu-satunya hal yang penting adalah ukuran relatif dan posisi goresan. Anda bebas untuk meneruskan koordinat dalam skala apa pun yang masuk akal untuk sistem Anda.
Pra-konteks
Pra-konteks adalah teks yang tepat mendahului goresan dalam Ink
yang Anda
oleh orang lain. Anda dapat membantu pengenal dengan memberi tahu tentang prakonteks.
Misalnya, huruf tulis tangan "n" dan "u" sering disalahartikan. Jika pengguna memiliki sudah memasukkan sebagian kata "arg", mereka mungkin melanjutkan dengan goresan yang dapat dikenali sebagai "umen" atau "nment". Menentukan "arg" pra-konteks menyelesaikan ambiguitas ini, karena kata "argumen" kemungkinannya lebih besar daripada "argnment".
Pra-konteks juga dapat membantu pengenal untuk mengidentifikasi jeda kata, spasi antar-kata. Anda dapat mengetik karakter spasi, tetapi Anda tidak dapat menggambarnya, jadi bagaimana pengenal dapat menentukan kapan satu kata berakhir yang berikutnya dimulai? Jika pengguna sudah menulis "halo" dan dilanjutkan dengan kata "world", tanpa pra-konteks, pengenal akan menampilkan string "world". Namun, jika Anda menentukan pra-konteks "hello", model akan menampilkan string " dunia", dengan spasi di awal, karena dunia" lebih masuk akal daripada kata "helloword".
Anda harus menyediakan string pra-konteks sepanjang mungkin, hingga 20 karakter, termasuk spasi. Jika string lebih panjang, pengenal hanya menggunakan 20 karakter terakhir.
Contoh kode di bawah ini memperlihatkan cara menentukan area penulisan dan menggunakan
RecognitionContext
untuk menentukan prakonteks.
Swift
let ink: Ink = ...; let recognizer: DigitalInkRecognizer = ...; let preContext: String = ...; let writingArea = WritingArea.init(width: ..., height: ...); let context: DigitalInkRecognitionContext.init( preContext: preContext, writingArea: writingArea); recognizer.recognizeHandwriting( from: ink, context: context, completion: { (result: DigitalInkRecognitionResult?, error: Error?) in if let result = result, let candidate = result.candidates.first { NSLog("Recognized \(candidate.text)") } else { NSLog("Recognition error \(error)") } })
Objective-C
MLKInk *ink = ...; MLKDigitalInkRecognizer *recognizer = ...; NSString *preContext = ...; MLKWritingArea *writingArea = [MLKWritingArea initWithWidth:... height:...]; MLKDigitalInkRecognitionContext *context = [MLKDigitalInkRecognitionContext initWithPreContext:preContext writingArea:writingArea]; [recognizer recognizeHandwritingFromInk:ink context:context completion:^(MLKDigitalInkRecognitionResult *_Nullable result, NSError *_Nullable error) { NSLog(@"Recognition result %@", result.candidates[0].text); }];
Pengurutan goresan
Akurasi pengenalan sensitif terhadap urutan goresan. Pengenal mengharapkan {i>stroke<i} untuk terjadi dalam urutan yang secara alami akan ditulis orang; misalnya kiri-ke-kanan untuk bahasa Inggris. Semua kasus yang menyimpang dari pola ini, seperti menulis kalimat bahasa Inggris yang dimulai dengan kata terakhir, memberikan hasil yang kurang akurat.
Contoh lain adalah saat sebuah kata di tengah Ink
dihapus dan diganti dengan
kata lain. Revisi mungkin ada di tengah kalimat, tetapi {i>stroke<i} untuk revisi
berada di akhir rangkaian {i>stroke<i}.
Dalam hal ini, sebaiknya kirimkan kata yang baru ditulis secara terpisah ke API dan gabungkan
hasil dengan pengenalan sebelumnya menggunakan logika Anda sendiri.
Menangani bentuk yang ambigu
Ada kasus saat arti bentuk yang diberikan kepada pengenal tidak jelas. Sebagai contoh, persegi panjang dengan tepi yang sangat membulat dapat dilihat sebagai persegi panjang atau elips.
Kasus yang tidak jelas ini dapat ditangani menggunakan skor pengenalan jika tersedia. Hanya
pengklasifikasi bentuk akan memberikan skor. Jika model sangat yakin, skor hasil teratas akan
jauh lebih baik dari yang terbaik kedua. Jika ada ketidakpastian, skor untuk dua hasil teratas akan
menjadi dekat. Selain itu, perlu diingat bahwa pengklasifikasi bentuk menafsirkan seluruh Ink
sebagai
satu bentuk. Misalnya, jika Ink
berisi persegi panjang dan elips di samping masing-masing
lainnya, pengenal dapat mengembalikan salah satunya (atau sesuatu yang sama sekali berbeda) sebagai
karena satu kandidat pengenalan tidak dapat merepresentasikan dua bentuk.