前一阵在班讯通上边加了一个小的功能:根据拼音提示写出汉字,提交之后软件会打出分数,其界面如下:
下面简单介绍一下第一个版本识别算法的实现:
iOS中UIView视图继承了UIResponder类,该类中的四个方法是我们需要调用的:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
- 在前三个方法中,我们记录一个笔画的起始点、过程点和结束点,将点添加到保存点的数组中,在touchesEnded方法里,将保存点的数组添加到保存笔画的数组。这样,写完一个字之后,保存笔画的数组就是我们要的整个字的点集。
- 将该点集保存在数据库模板表中,用做模板。
- 模板完成之后,开始记录用户输入的点集,并将数据记录在用户表。
// 采集点集 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { NSMutableArray *newStackOfPoints = [NSMutableArray array]; [newStackOfPoints addObject:[NSValue valueWithCGPoint:[touch locationInView:self]]]; [self.strokes addObject:newStackOfPoints]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { [[self.strokes lastObject] addObject:[NSValue valueWithCGPoint:[touch locationInView:self]]]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch* touch in touches) { [[self.strokes lastObject] addObject:[NSValue valueWithCGPoint:[touch locationInView:self]]]; } }
// 插入数据库 - (int)insertChar:(CharacterModel *)model { int charId = 0; [self.hanziDb executeUpdate:@"insert into characters(chinese, pinyin, pointsString) values(?,?,?)", model.chinese, model.pinyin, model.pointsString]; NSString *queryString = [NSString stringWithFormat:@"select id from characters where chinese = '%@'", model.chinese]; FMResultSet* set = [self.hanziDb executeQuery:queryString]; if([set next]) { charId = [set intForColumn:@"id"]; } for (int i=0; i<model.inputPointGrids.count; i++) { NSArray *aStroke = model.inputPointGrids[i]; for (NSValue *aPointValue in aStroke) { CGPoint aPoint = [aPointValue CGPointValue]; [self.hanziDb executeUpdate:@"insert into points(id, pointX, pointY, strokeid) values(?,?,?,?)", [NSNumber numberWithInt:charId],[NSNumber numberWithInt:(int)aPoint.x],[NSNumber numberWithInt:(int)aPoint.y], [NSNumber numberWithInt:i+1]]; } } return charId; }
- 有了数据,剩下的就是利用数据库的select语句来对比了。但是在实际对比时候会发现~~根本没几个点能匹配的上,原因很简单:在第一步中,记录的是点的绝对坐标。比如我们写个“一”,录入模板的时候,记录的坐标集是{(50, 200), (100, 200), (150, 200), (200, 200), (250, 200)},用户输入的时候记录的是{(60, 220), (90, 220), (130, 220), (160, 220), (200, 220), (230, 220)}, 结果就是零匹配。另外一点很重要,就是屏幕的分辨率......
- 鉴于这种情况, 将输入区域划分为50个格子,每经过一个格子,便将其记录下来作为相对坐标,这样上边的两条数据就成了{(10, 40), (20, 40 ), (30, 40), (40, 40), (50, 40)}和{(10, 40), (20, 40 ), (30, 40), (40, 40), (50, 40), (60, 40)}, 匹配程度就提高了(当然,这只是举例数据,并不准确)。
- 改为相对坐标存储之后,虽然匹配程度提高不少,但是当笔画上下或者左右位置偏差比较大的时候仍然识别不准确, 比如一个横的模板数据是{(10, 40), (20, 40 ), (30, 40), (40, 40), (50, 40)}, 用户输入的数据是{(10, 41), (20, 41 ), (30, 41), (40, 41), (50, 41)},这样在查询的时候就匹配不到。所以在查询的时候需要将用户输入的笔画做一个偏移,将{(10, 40), (20, 40 ), (30, 40), (40, 40), (50, 40)}和{(10, 41), (20, 41 ), (30, 41), (40, 41), (50, 41)}匹配起来。
// 取相对坐标 - (void)turnToGrids { self.strokeCount = self.inputStrokes.count; // 格子宽度 float gridWidth = kScreenWidth/10; for (int k=0; k<self.inputStrokes.count; k++) { // 存储一个笔画的所有点到一个数组 NSMutableArray *strokPointGrids = [NSMutableArray array]; NSArray *inputStrokesArray = self.inputStrokes[k]; for (int l = 0; l<inputStrokesArray.count; l++) { NSValue *value = inputStrokesArray[l]; if (l == 0) { [self.strokeStartPoints addObject:value]; } if (l == self.inputStrokes.count-1) { [self.strokeEndPoints addObject:value]; } CGPoint point = [value CGPointValue]; CGPoint grid = CGPointMake(ceil(point.x/gridWidth), ceil(point.y/gridWidth)); BOOL shouldAdd = NO; if (strokPointGrids.count == 0) { shouldAdd = YES; } else { NSValue *lastValue = strokPointGrids.lastObject; CGPoint lastGrid = [lastValue CGPointValue]; if (lastGrid.x != grid.x || lastGrid.y != grid.y) { shouldAdd = YES; } } if (shouldAdd) { [strokPointGrids addObject:[NSValue valueWithCGPoint:grid]]; if (![self.pointsString isEqualToString: @""] && ![self.pointsString hasSuffix:@"*"]) { [self.pointsString appendString:[NSString stringWithFormat:@"|"]]; } [self.pointsString appendString:[NSString stringWithFormat:@"%d,%d", (int)grid.x, (int)grid.y]]; } } if (k != self.inputStrokes.count-1) { [self.pointsString appendString:@"*"]; } [self.inputPointGrids addObject:strokPointGrids]; } }
// 最终的查询语句 NSString *queryString4 = [NSString stringWithFormat:@"select a.strokeid as strokeid,a.samecount as samecount,a.ucount as ucount,a.pcount as pcount from ( select strokeid, count(*) as samecount, (select count(*) from input_points where id=p.id and strokeid= %d ) as ucount, (select count(*) from points where id=p.id and strokeid=p.strokeid) as pcount from points p where exists(select * from input_points u where u.id=p.id and u.strokeid=%d and (abs(u.pointX-p.pointX) + abs(u.pointY-p.pointY))<2 ) and p.strokeid not in (%@) and p.id=%d group by strokeid ) a order by abs(a.pcount - a.samecount) asc", j, j, hasStrokeid, model.charID];
至此, 该功能模块就可以简单识别从手机屏幕写入的汉字了,只不过这样很鸡肋,因为如果模板录入和用户输入风格相差比较大或者用户写的偏上(偏下,左右都有可能)的话,识别率就直线下降,所以,我们在第二版本中彻底改变了识别算法,极大程度地提高了识别率。
关于手写识别的第二个版本,请看下一篇博文
原文地址:http://blog.csdn.net/zwf_apple/article/details/46482585