เทคโนโลยีการจดจำหมึกดิจิทัลของ ML Kit ให้คุณจดจำข้อความที่เขียนด้วยลายมือบน ดิจิทัลในหลายร้อยภาษา และจำแนกภาพร่างได้
ลองเลย
- ลองใช้แอปตัวอย่างเพื่อ ดูตัวอย่างการใช้ API นี้
ก่อนเริ่มต้น
ใส่ไลบรารี ML Kit ต่อไปนี้ใน Podfile
pod 'GoogleMLKit/DigitalInkRecognition', '3.2.0'
หลังจากติดตั้งหรืออัปเดตพ็อดของโปรเจ็กต์แล้ว ให้เปิดโปรเจ็กต์ Xcode โดยใช้
.xcworkspace
เวอร์ชัน Xcode รองรับ ML Kit 13.2.1 ขึ้นไป
คุณพร้อมที่จะเริ่มจดจำข้อความในออบเจ็กต์ Ink
แล้ว
สร้างออบเจ็กต์ Ink
วิธีหลักในการสร้างวัตถุ Ink
คือการวาดวัตถุบนหน้าจอสัมผัส ใน iOS
คุณสามารถใช้ UIImageView พร้อมด้วย
เหตุการณ์การแตะ
เครื่องจัดการ
ซึ่งวาดเส้นบนหน้าจอและจัดเก็บเส้นโครงร่าง คะแนนในการสร้าง
ออบเจ็กต์ Ink
รูปแบบทั่วไปนี้แสดงให้เห็นในโค้ดต่อไปนี้
ข้อมูลเพิ่มเติม ดูคู่มือเริ่มต้นฉบับย่อ
แอปสำหรับ
ตัวอย่างที่สมบูรณ์มากขึ้น ซึ่งแยกการจัดการเหตุการณ์การแตะ การวาดหน้าจอ
และการจัดการข้อมูลโรคหลอดเลือดสมอง
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]; }
โปรดทราบว่าข้อมูลโค้ดมีฟังก์ชันตัวอย่างที่ใช้วาดเส้นโครงร่างเป็น
UIImageView
ซึ่งควรปรับเปลี่ยนตามความจำเป็นสำหรับการสมัครของคุณ เราขอแนะนำให้ใช้
จุดกลมเมื่อวาดส่วนของเส้นตรงเพื่อให้ส่วนของเส้นตรงที่ความยาวเป็นศูนย์
วาดเป็นจุด (ลองนึกถึงจุดบนตัวอักษรพิมพ์เล็ก i) doRecognition()
จะถูกเรียกหลังจากที่เขียนเส้นโครงร่างแล้ว และจะกำหนดไว้ด้านล่าง
รับอินสแตนซ์ของ DigitalInkRecognizer
ในการจดจำ เราต้องส่งผ่านออบเจ็กต์ Ink
ไปยัง
DigitalInkRecognizer
ในการรับอินสแตนซ์ DigitalInkRecognizer
ก่อนอื่นเราจะต้องดาวน์โหลดโมเดลเครื่องมือจดจำสำหรับภาษาที่ต้องการก่อน
โหลดโมเดลลงใน RAM ซึ่งทำได้โดยใช้โค้ดต่อไปนี้
ระบบจะวางข้อมูลโค้ดไว้ในเมธอด viewDidLoad()
และใช้
ชื่อภาษาแบบฮาร์ดโค้ด ดูคู่มือเริ่มต้นฉบับย่อ
แอปสำหรับ
ตัวอย่างวิธีแสดงรายการภาษาที่พร้อมให้บริการแก่ผู้ใช้และดาวน์โหลด
ภาษาที่เลือก
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]; }
แอปการเริ่มต้นอย่างรวดเร็วจะมีโค้ดเพิ่มเติมที่แสดงวิธีจัดการกับ ในเวลาเดียวกัน และวิธีดูว่าการดาวน์โหลดใดสำเร็จด้วยการจัดการ การแจ้งเตือนการเสร็จสมบูรณ์
จดจำออบเจ็กต์ Ink
ต่อไป เราจะไปที่ฟังก์ชัน doRecognition()
ซึ่งเรียกง่ายๆ ว่า
จาก touchesEnded()
ในแอปพลิเคชันอื่นๆ อาจมีการเรียกใช้
การจดจำเสียงหลังจากหมดเวลาเท่านั้น หรือเมื่อผู้ใช้กดปุ่มเพื่อทริกเกอร์
การจดจำ
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]; }]; }
การจัดการการดาวน์โหลดโมเดล
เราได้เห็นวิธีดาวน์โหลดโมเดลการจดจำไปแล้ว รหัสต่อไปนี้ แสดงตัวอย่างวิธีการตรวจสอบว่าโมเดลได้รับการดาวน์โหลดแล้วหรือไม่ หรือ เพื่อลบโมเดลเมื่อไม่จำเป็นต้องกู้คืนพื้นที่เก็บข้อมูลอีกต่อไป
ตรวจสอบว่าดาวน์โหลดโมเดลแล้วหรือไม่
Swift
let model : DigitalInkRecognitionModel = ... let modelManager = ModelManager.modelManager() modelManager.isModelDownloaded(model)
Objective-C
MLKDigitalInkRecognitionModel *model = ...; MLKModelManager *modelManager = [MLKModelManager modelManager]; [modelManager isModelDownloaded:model];
ลบโมเดลที่ดาวน์โหลด
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."); }]; }
เคล็ดลับในการปรับปรุงความแม่นยำในการจดจำข้อความ
ความถูกต้องของการจดจำข้อความอาจแตกต่างกันไปตามภาษาต่างๆ ความแม่นยำก็ขึ้นอยู่กับ เกี่ยวกับสไตล์การเขียน แม้ว่า Digital Ink Recognition จะได้รับการฝึกให้จัดการกับรูปแบบการเขียนที่หลากหลาย ผลลัพธ์อาจแตกต่างกันไปตามผู้ใช้แต่ละคน
วิธีปรับปรุงความแม่นยำของโปรแกรมจดจำข้อความมีดังนี้ โปรดทราบว่าเทคนิคเหล่านี้จะ ไม่ใช้กับตัวแยกประเภทภาพวาดสำหรับอีโมจิ การวาดอัตโนมัติ และรูปร่าง
พื้นที่สำหรับเขียน
แอปพลิเคชันจำนวนมากมีพื้นที่การเขียนที่กำหนดไว้อย่างชัดเจนสำหรับป้อนข้อมูลของผู้ใช้ ความหมายของสัญลักษณ์คือ กำหนดบางส่วนโดยขนาดที่สัมพันธ์กับขนาดพื้นที่การเขียนที่มีข้อมูลอยู่ เช่น ความแตกต่างระหว่างอักษรตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่ "o" หรือ "c" และเครื่องหมายคอมมาเทียบกับ เครื่องหมายทับ
การบอกโปรแกรมจดจำว่าความกว้างและความสูงของพื้นที่การเขียนจะช่วยเพิ่มความแม่นยำได้ อย่างไรก็ตาม เครื่องมือจดจำจะถือว่าพื้นที่สำหรับการเขียนมีเพียงบรรทัดเดียว หากรูปภาพ พื้นที่การเขียนมีขนาดใหญ่พอที่จะให้ผู้ใช้เขียนได้ตั้งแต่ 2 บรรทัดขึ้นไป คุณอาจเขียนได้ดีกว่า โดยการส่งผ่านในบริเวณการเขียนที่มีความสูงซึ่งเป็นค่าประมาณที่ดีที่สุดของความสูง บรรทัดเดียวก็ได้ ออบเจ็กต์ WritingArea ที่คุณส่งไปยังเครื่องมือจดจำไม่จำเป็นต้องสอดคล้องกัน ตรงตามพื้นที่เขียนจริงบนหน้าจอ การเปลี่ยนความสูงของพื้นที่การเขียนในลักษณะนี้ ทำงานในบางภาษาได้ดีกว่าภาษาอื่นๆ
เมื่อคุณระบุพื้นที่การเขียน ให้ระบุความกว้างและความสูงเป็นหน่วยเดียวกับเส้นโครงร่าง พิกัด อาร์กิวเมนต์พิกัด x,y ไม่มีข้อกำหนดหน่วย - API จะปรับทั้งหมดให้เป็นค่ามาตรฐาน หน่วย ดังนั้นสิ่งเดียวที่สำคัญคือขนาดและตำแหน่งของเส้นโครงร่าง คุณมีอิสระที่จะ ส่งพิกัดในขนาดใดก็ได้ที่เหมาะสมกับระบบของคุณ
ก่อนบริบท
ก่อนบริบทคือข้อความที่อยู่ก่อนเส้นโครงร่างใน Ink
ที่คุณ
กำลังพยายามจดจำ คุณช่วยเครื่องมือจดจำได้โดยบอกเกี่ยวกับบริบทเบื้องต้น
เช่น ตัวอักษรแบบคัดลายมือ "n" และ "u" มักจะเข้าใจผิดว่าเป็นกัน หากผู้ใช้มี ป้อนคำว่า "อาร์กิวเมนต์" บางส่วนไปแล้ว พวกเขาอาจใช้คำนี้ต่อไปอีก เช่น "ument" หรือ "nment" การระบุ "อาร์กิวเมนต์" ก่อนบริบท แก้ไขความคลุมเครือ เนื่องจากคำว่า "อาร์กิวเมนต์" จะเป็นไปได้มากกว่า "อาร์กิวเมนต์"
นอกจากนี้ บริบทเบื้องต้นยังช่วยให้ระบบจดจำระบุตัวแบ่งคำหรือการเว้นวรรคระหว่างคำได้ด้วย คุณสามารถ พิมพ์อักขระเว้นวรรคแต่คุณไม่สามารถวาดได้ 1 คำ ดังนั้น โปรแกรมรู้จำจะระบุได้อย่างไรว่า 1 คำสิ้นสุดเมื่อใด แล้วเพลงถัดไปก็เริ่มล่ะ หากผู้ใช้เคยเขียนคำว่า "สวัสดี" ไว้แล้ว และต่อด้วยคำว่า "world" โดยไม่มีบริบทล่วงหน้า เครื่องมือจดจำจะแสดงผลสตริง "world" แต่ถ้าคุณระบุ บริบท "สวัสดี" ล่วงหน้า โมเดลจะส่งกลับสตริง " โลก" โดยเว้นวรรคนำหน้า โลก" เหมาะสมกว่าคำว่า "Heyword"
คุณควรระบุสตริงก่อนบริบทที่ยาวที่สุดเท่าที่จะเป็นไปได้ โดยไม่เกิน 20 อักขระโดยรวมถึง พื้นที่ทำงาน หากสตริงยาวกว่า เครื่องมือจดจำจะใช้เพียง 20 อักขระสุดท้าย
ตัวอย่างโค้ดด้านล่างแสดงวิธีกำหนดพื้นที่การเขียนและใช้
RecognitionContext
ที่จะระบุบริบทล่วงหน้า
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); }];
การจัดเรียงสโตรก
ความถูกต้องในการจดจำจะคำนึงถึงลำดับของเส้น โปรแกรมรู้จำคาดหวังให้สโตรก เกิดขึ้นตามลำดับที่โดยปกติแล้วผู้คนจะเขียน เช่น อ่านซ้ายไปขวาสำหรับภาษาอังกฤษ ทุกกรณี ที่ออกจากรูปแบบนี้ เช่น การเขียนประโยคภาษาอังกฤษโดยเริ่มต้นด้วยคำสุดท้าย จะให้ผลลัพธ์ที่แม่นยำน้อยลง
อีกตัวอย่างหนึ่งคือ เมื่อนำคำที่อยู่ตรงกลาง Ink
ออกและแทนที่ด้วย
คำอื่น การแก้ไขอาจอยู่ระหว่างกลางประโยค แต่เส้นแบ่งการแก้ไข
จะอยู่ท้ายสุดของลำดับเส้นโครงร่าง
ในกรณีนี้ เราขอแนะนำให้ส่งคำที่เขียนใหม่แยกต่างหากไปยัง API และรวม
ผลลัพธ์ด้วยการจดจำก่อนหน้าโดยใช้ตรรกะของคุณเอง
การจัดการกับรูปร่างที่ไม่ชัดเจน
มีบางกรณีที่ความหมายของรูปร่างที่ให้ไว้กับเครื่องมือจดจำนั้นไม่ชัดเจน สำหรับ เช่น สี่เหลี่ยมผืนผ้าที่มีขอบมนมากอาจมองว่าเป็นสี่เหลี่ยมผืนผ้าหรือวงรี
คุณใช้คะแนนการจดจำเสียงเมื่อข้อมูลมีการจัดการเคสที่ไม่ชัดเจนเหล่านี้ได้ เฉพาะ
ตัวแยกประเภทรูปร่างจะให้คะแนน หากโมเดลมั่นใจมาก คะแนนของผลลัพธ์อันดับต้นๆ จะเป็น
ดีกว่าโซลูชันที่ 2 อย่างมาก หากไม่แน่นอน คะแนนสำหรับผลลัพธ์ 2 อันดับแรกจะ
ใกล้ๆ นอกจากนี้ โปรดทราบว่าตัวแยกประเภทรูปร่างจะตีความ Ink
ทั้งหมดว่าเป็น
รูปร่างเดียว เช่น หาก Ink
มีสี่เหลี่ยมผืนผ้าและวงรีอยู่ข้างแต่ละจุด
เครื่องมือจดจำอาจแสดงผลอย่างใดอย่างหนึ่ง (หรือบางอย่างที่แตกต่างออกไปโดยสิ้นเชิง) เป็น
เนื่องจากตัวเลือกการจดจำเดี่ยวไม่สามารถแสดงรูปร่างสองรูป