Dengan pengenalan tinta digital ML Kit, Anda dapat mengenali teks yang ditulis tangan di platform 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 Anda menginstal atau mengupdate Pod project, buka project Xcode menggunakan
.xcworkspace
-nya. ML Kit didukung di Xcode versi 13.2.1 atau yang lebih baru.
Sekarang Anda siap untuk mulai mengenali teks dalam objek Ink
.
Membuat objek Ink
Cara utama untuk mem-build objek Ink
adalah dengan menggambarnya di layar sentuh. Di iOS,
Anda dapat menggunakan UIImageView beserta
pengendali peristiwa
sentuh
yang menggambar goresan di layar dan juga menyimpan titik goresan untuk mem-build
objek Ink
. Pola umum ini ditunjukkan dalam cuplikan
kode berikut. Lihat aplikasi
panduan memulai untuk
contoh yang lebih lengkap, yang memisahkan penanganan peristiwa sentuh, gambar layar,
dan pengelolaan data goresan.
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]; }
Perlu diperhatikan bahwa cuplikan kode menyertakan fungsi contoh untuk menggambar goresan ke dalam
UIImageView,
yang harus disesuaikan seperlunya untuk aplikasi Anda. Sebaiknya gunakan huruf bulat saat menggambar segmen garis sehingga segmen dengan panjang nol akan digambar sebagai titik (misalnya titik pada huruf kecil i). Fungsi doRecognition()
dipanggil setelah setiap goresan ditulis dan akan ditentukan di bawah ini.
Dapatkan instance DigitalInkRecognizer
Untuk melakukan pengenalan, kita perlu meneruskan objek Ink
ke instance DigitalInkRecognizer
. Untuk mendapatkan instance DigitalInkRecognizer
,
pertama-tama kita harus mendownload model pengenal untuk bahasa yang diinginkan, lalu
memuat model ke RAM. Hal ini dapat dilakukan menggunakan cuplikan
kode berikut, yang untuk mempermudah ditempatkan dalam metode viewDidLoad()
dan menggunakan
nama bahasa hardcode. Lihat aplikasi
panduan memulai untuk
contoh cara menampilkan daftar bahasa yang tersedia kepada pengguna dan mendownload
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 download secara bersamaan, dan cara menentukan download mana yang berhasil dengan menangani notifikasi penyelesaian.
Mengenali objek Ink
Selanjutnya, kita sampai ke fungsi doRecognition()
, yang agar lebih praktis dipanggil
dari touchesEnded()
. Di aplikasi lain, seseorang mungkin ingin memanggil
pengenalan hanya setelah waktu tunggu habis, atau saat pengguna menekan tombol untuk memicu
pengenalan.
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. Cuplikan kode berikut mengilustrasikan cara memeriksa apakah model telah didownload, atau menghapus model saat 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 antarbahasa. Akurasi juga tergantung pada gaya penulisan. Meskipun Pengenalan Tinta Digital dilatih untuk menangani berbagai jenis gaya penulisan, hasilnya dapat bervariasi dari satu pengguna ke pengguna lainnya.
Berikut ini beberapa cara untuk meningkatkan akurasi pengenal teks. Perhatikan bahwa teknik ini tidak berlaku bagi pengklasifikasi gambar untuk emoji, gambar otomatis, dan bentuk.
Area penulisan
Banyak aplikasi yang memiliki area penulisan yang didefinisikan dengan baik untuk input pengguna. Arti simbol sebagian ditentukan oleh ukurannya relatif terhadap ukuran area tulisan yang memuatnya. Misalnya, perbedaan antara huruf kecil atau besar "o" atau "c", dan koma versus garis miring ke depan.
Memberi tahu pengenal lebar dan tinggi area tulisan dapat meningkatkan akurasi. Namun, pengenal mengasumsikan bahwa area penulisan hanya berisi satu baris teks. Jika area penulisan fisik cukup besar untuk memungkinkan pengguna menulis dua baris atau lebih, Anda bisa mendapatkan hasil yang lebih baik dengan meneruskan WritingArea dengan tinggi yang merupakan perkiraan terbaik untuk tinggi satu baris teks. Objek WritingArea yang Anda teruskan ke pengenal tidak harus sama persis dengan area penulisan fisik di layar. Mengubah tinggi WritingArea dengan cara ini bekerja lebih baik dalam beberapa bahasa dibandingkan bahasa lainnya.
Saat Anda menentukan area tulisan, tentukan lebar dan tingginya dalam unit yang sama dengan koordinat goresan. Argumen koordinat x,y tidak memiliki persyaratan satuan - API menormalisasi semua unit, sehingga satu-satunya hal yang penting adalah ukuran relatif dan posisi goresan. Anda bebas meneruskan koordinat dalam skala apa pun yang sesuai untuk sistem Anda.
Pra-konteks
Pra-konteks adalah teks yang berada tepat sebelum goresan dalam Ink
yang
ingin Anda kenali. Anda dapat membantu pengenal dengan memberitahukan pra-konteksnya.
Misalnya, huruf kursif "n" dan "u" sering disalahartikan satu sama lain. Jika pengguna telah memasukkan sebagian kata "arg", mereka dapat melanjutkan dengan goresan yang dapat dikenali sebagai "ument" atau "nment". Menentukan "arg" pra-konteks akan menyelesaikan ambiguitas, karena kata "argument" lebih mungkin daripada "argnment".
Pra-konteks juga dapat membantu pengenal untuk mengidentifikasi jeda kata dan spasi di antara kata. Anda dapat mengetik karakter spasi, tetapi tidak dapat menggambarnya. Jadi, bagaimana cara pengenal dapat menentukan kapan satu kata berakhir dan kata berikutnya dimulai? Jika pengguna telah menulis "halo" dan melanjutkan dengan kata tertulis "world", tanpa konteks pra-konteks, pengenal akan menampilkan string "world". Namun, jika Anda menentukan pra-konteks "hello", model akan menampilkan string "world", dengan spasi di awal, karena "hello world" lebih masuk akal daripada "helloword".
Anda harus menyediakan string pra-konteks terpanjang yang memungkinkan, hingga 20 karakter, termasuk spasi. Jika string lebih panjang, pengenal hanya akan menggunakan 20 karakter terakhir.
Contoh kode di bawah menunjukkan cara menentukan area penulisan dan menggunakan
objek RecognitionContext
untuk menentukan pra-konteks.
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 goresan terjadi sesuai urutan yang akan ditulis orang secara alami; misalnya kiri-ke-kanan untuk bahasa Inggris. Kasus apa pun yang menyimpang dari pola ini, seperti menulis kalimat bahasa Inggris yang dimulai dengan kata terakhir, akan memberikan hasil yang kurang akurat.
Contoh lainnya adalah saat kata di tengah Ink
dihapus dan diganti dengan
kata lain. Revisi mungkin berada di tengah kalimat, tetapi goresan untuk revisi berada di akhir urutan goresan.
Dalam hal ini, sebaiknya kirim kata yang baru ditulis secara terpisah ke API dan gabungkan
hasilnya dengan pengenalan sebelumnya menggunakan logika Anda sendiri.
Menangani bentuk yang ambigu
Ada kasus saat arti bentuk yang diberikan kepada pengenal ambigu. Misalnya, persegi panjang dengan tepi yang sangat membulat dapat dilihat sebagai persegi panjang atau elips.
Kasus yang tidak jelas ini dapat ditangani dengan menggunakan skor pengenalan jika tersedia. Hanya pengklasifikasi bentuk yang memberikan skor. Jika modelnya sangat yakin, skor hasil teratas akan jauh lebih baik daripada skor terbaik kedua. Jika ada ketidakpastian, skor untuk dua hasil teratas akan
mendekati. Selain itu, perlu diingat bahwa pengklasifikasi bentuk menafsirkan seluruh Ink
sebagai
satu bentuk. Misalnya, jika Ink
berisi persegi panjang dan elips di samping satu sama lain, pengenal dapat menampilkan salah satunya (atau sesuatu yang sama sekali berbeda) sebagai hasilnya, karena satu kandidat pengenalan tidak dapat merepresentasikan dua bentuk.