用高拍仪实现二代身份证识别
按计划,该做二代身份证识别功能了。
自己站在用户角度,该怎么用呢?
当然一键式的最好,用户操作一下,什么都妥妥的了。
从实现的角度出发,再从通用的角度出发,需要先做模块,通过配置,用户可以直接选择该配置文件,即可完成他所要的功能。
界面
还是先设计界面,加上几个按钮:
定制模板
另存模板,是将目前的分区保存为一个模板配置,即载入一个标准证件照片图片,然后手动设置各种分区,如文字、图片等,可以通过属性浏览器设置各分区对象的名称等属性:
然后保存为模板配置:
保存后,配置文件为:
这些工作,基本上调用一下之前的代码就可以实现:
void __fastcall TMainForm::Button_SaveTemplateClick(TObject *Sender) { //
if (InputQuery("模板名称", "请输入模板名称", FTemplateName)) {
UnicodeString templatePath = THelper::FormatString("%sres\\template\\", THelper::GetApplicationPath());
THelper::CreateDirectory(templatePath, true);
UnicodeString templateFileName = THelper::FormatString("%s\\%s.xml", templatePath, FTemplateName);
CbwXML * templateXml = new CbwXML(templateFileName, "template");
CbwXmlNode * borderNode = templateXml->RootNode->AddElement("Border");
borderNode->AddAttribute("left", FImageDisplayBounds.Left);
borderNode->AddAttribute("top", FImageDisplayBounds.Top);
borderNode->AddAttribute("right", FImageDisplayBounds.Right);
borderNode->AddAttribute("bottom", FImageDisplayBounds.Bottom);
if (Ratio != 1)
borderNode->AddAttribute("ratio", Ratio);
bool hasRecognizePartFlag = false;
CBW_ITERATOR(CbwObjects, FObjects)
if (RecognizePartType(*it) != rptNone) { // 是分区图形对象
(*it)->AddToXmlNode(borderNode);
hasRecognizePartFlag = true;
}
if (hasRecognizePartFlag) {
templateXml->Save();
Button_ApplyTemplate->Visible = ivAlways;
}
delete templateXml;
}
}
// ---------------------------------------------------------------------------
应用模板
下来就是应用这些模板,当然,在界面中的工作就是,查找配置文件,如果存在,则动态构造按钮,效果如下:
这实现起来就太简单了,无需多言。
应用模板,就是读入配置,然后呢?
第一要务是识别出目前身份证图片中的边框,然后通过坐标映射将模板中的分区对象自动对应到该图片中的相应位置。
void __fastcall TMainForm::OnApplyTemplate(TObject* Sender) {
// 应用模板
cSelectedObjects->Selected = false;
FTemplateName = GetPropValue(Sender, "Caption");
int tag = THelper::GetTag(Sender);
UnicodeString fileName = THelper::FormatString("%sres\\template\\%s.xml", THelper::GetApplicationPath(), FTemplateName);
if (FileExists(fileName)) { // 取得目标模板的配置文件
CbwXML * templateXml = new CbwXML(fileName, "template");
templateXml->Read();
CbwXmlNode * borderNode = templateXml->RootNode->NodeByName("Border");
if (borderNode) {
for (int i = 0; i < borderNode->ElementNumber; ++i) {
CbwXmlNode * currentNode = borderNode->Elements(i);
UnicodeString metaTypeName = currentNode->Name;
TCbwObject * subObject = BuildCbwObjectByMetaName(metaTypeName);
AssignUItoObject(subObject);
subObject->XMLFile = templateXml;
subObject->AllowDraw = false;
subObject->GetFromXmlNode(currentNode);
FTemplateObjects.push_back(subObject);
AddThisObject(subObject);
subObject->Selected = true;
cSelectedObjects->AddObject(subObject);
subObject->AllowDraw = true;
subObject->IsAllObjectsConstructed = true;
subObject->XMLFile = FXmlFile;
}
double ratio = borderNode->AttributeValueByName("ratio", "1").ToDouble();
int left = borderNode->AttributeValueByName("left", "0").ToInt() / ratio;
int top = borderNode->AttributeValueByName("top", "0").ToInt() / ratio;
int right = borderNode->AttributeValueByName("right", "0").ToInt() / ratio;
int bottom = borderNode->AttributeValueByName("bottom", "0").ToInt() / ratio;
double dx = - left; //(left + right) / 2; //((FImageDisplayBounds.Left + FImageDisplayBounds.Right) / Ratio - (left + right)) / 2;
double dy = - top; //(top + bottom) / 2; //((FImageDisplayBounds.Top + FImageDisplayBounds.Bottom) / Ratio - (top + bottom)) / 2;
if(tag) {
dx = ((FImageDisplayBounds.Left + FImageDisplayBounds.Right) / Ratio - (left + right)) / 2;
dy = ((FImageDisplayBounds.Top + FImageDisplayBounds.Bottom) / Ratio - (top + bottom)) / 2;
}
cSelectedObjects->DragBy(dx, dy, true);
cSelectedObjects->Selected = false;
FTemplateBorderRect = TRect(0, 0, right - left, bottom - top);
if(FImage) {
GetImageBorder(FImage, false);
TPoint p[4] = {
TPoint(FImageDisplayBounds.Left / Ratio + FImageBorder->BorderPoints[0].x, FImageDisplayBounds.Top / Ratio + FImageBorder->BorderPoints[0].y),
TPoint(FImageDisplayBounds.Left / Ratio + FImageBorder->BorderPoints[1].x, FImageDisplayBounds.Top / Ratio + FImageBorder->BorderPoints[1].y),
TPoint(FImageDisplayBounds.Left / Ratio + FImageBorder->BorderPoints[2].x, FImageDisplayBounds.Top / Ratio + FImageBorder->BorderPoints[2].y),
TPoint(FImageDisplayBounds.Left / Ratio + FImageBorder->BorderPoints[3].x, FImageDisplayBounds.Top / Ratio + FImageBorder->BorderPoints[3].y)
};
ConvertTemplateObjectsToDisplayObjects(p);
}
}
delete templateXml;
Button_EditTemplate->Visible = ivAlways;
}
RefreshCurrentScreen();
}
void __fastcall TMainForm::ConvertTemplateObjectsToDisplayObjects(TPoint * destModalPoints, int pointNumber) {
TCbwFloatPoint centerPoint = TCbwFloatPoint( (FTemplateBorderRect.Left + FTemplateBorderRect.Right) / 2.0, (FTemplateBorderRect.Top + FTemplateBorderRect.Bottom) / 2.0);
double dx = 0, dy = 0;
for(int i = 0; i < pointNumber; ++i) {
dx += destModalPoints[i].x;
dy += destModalPoints[i].y;
}
dx /= pointNumber;
dy /= pointNumber;
dx -= centerPoint.x;
dy -= centerPoint.y;
for(int i = 0; i < pointNumber; ++i) {
destModalPoints[i].x -= dx;;
destModalPoints[i].y -= dy;
}
CBW_ITERATOR(CbwObjects, FTemplateObjects) {
TRectangle * rect = dynamic_cast<TRectangle*>(*it);
TCbwObject * destObject = Clone(*it);
if (destObject) {
double params[12];
THelper::BuildLineParamsByTwoPoint(destModalPoints[0], destModalPoints[1], params[0], params[1], params[2]);
THelper::BuildLineParamsByTwoPoint(destModalPoints[1], destModalPoints[2], params[3], params[4], params[5]);
THelper::BuildLineParamsByTwoPoint(destModalPoints[2], destModalPoints[3], params[6], params[7], params[8]);
THelper::BuildLineParamsByTwoPoint(destModalPoints[3], destModalPoints[0], params[9], params[10], params[11]);
double theta0 = centerPoint.ThetaToPoint(TCbwFloatPoint(FTemplateBorderRect.Left, FTemplateBorderRect.Bottom));
double theta1 = centerPoint.ThetaToPoint(TCbwFloatPoint(FTemplateBorderRect.Left, FTemplateBorderRect.Top));
double theta2 = centerPoint.ThetaToPoint(TCbwFloatPoint(FTemplateBorderRect.Right, FTemplateBorderRect.Top));
double theta3 = centerPoint.ThetaToPoint(TCbwFloatPoint(FTemplateBorderRect.Right, FTemplateBorderRect.Bottom));
for(int i = 0; i < destObject->PointNumber; ++i) {
TCbwFloatPoint p = destObject->Points[i];
double dis1 = centerPoint.DistanceToPoint(p);
TCbwFloatPoint destPoint = p;
if(p != centerPoint) {
double positionRatio = 1, lineRatio = 1;
double a, b, c;
THelper::BuildLineParamsByTwoPoint(centerPoint, p, a, b, c);
double theta = centerPoint.ThetaToPoint(p);
int index = 2;
if((theta1 - theta) * (theta - theta0) >= 0)
index = 0;
else if((theta2 - theta) * (theta - theta1) >= 0)
index = 1;
else if((theta0 - theta) * (theta - theta3) >= 0)
index = 3;
TCbwFloatPoint point;
if(index == 0) {
point = THelper::GetCrossPoint(a, b, c, 0, 1, 0);
positionRatio = 1 - point.y / FTemplateBorderRect.Height();
lineRatio = p.x / centerPoint.x;
}
if(index == 1) {
point = THelper::GetCrossPoint(a, b, c, 1, 0, 0);
positionRatio = point.x / FTemplateBorderRect.Width();
lineRatio = p.y / centerPoint.y;
}
if(index == 2) {
point = THelper::GetCrossPoint(a, b, c, 0, 1, FTemplateBorderRect.Right);
positionRatio = point.y / FTemplateBorderRect.Height();
lineRatio = (FTemplateBorderRect.Width() - p.x) / centerPoint.x;
}
if(index == 3) {
point = THelper::GetCrossPoint(a, b, c, 1, 0, FTemplateBorderRect.Bottom);
positionRatio = 1 - point.x / FTemplateBorderRect.Width();
lineRatio = (FTemplateBorderRect.Height() - p.y) / centerPoint.y;
}
destPoint.x = destModalPoints[index].x + (destModalPoints[(index + 1) % 4].x - destModalPoints[index].x) * positionRatio;
destPoint.y = destModalPoints[index].y + (destModalPoints[(index + 1) % 4].y - destModalPoints[index].y) * positionRatio;
destPoint.x = destPoint.x + (centerPoint.x - destPoint.x) * lineRatio;
destPoint.y = destPoint.y + (centerPoint.y - destPoint.y) * lineRatio;
}
destObject->SetPoint(i, destPoint);
}
destObject->Tag = (*it)->Tag;
AssignUItoObject(destObject);
AddThisObject(destObject);
cSelectedObjects->AddObject(destObject);
}
}
cSelectedObjects->DragBy(dx, dy, true);
cSelectedObjects->Selected = false;
}
这样,用户无论如何放置身份证照片,都能自动将分区对象摆放到目标位置。如:
识别生成
这项功能就算不上什么新鲜玩意了,以前早实现了。什么WORD、PDF之流的。
不过常做常新,现在倒可以尝试一下生成图片,也就是在一张空白的身份证照片上,填上识别的文字、照片;另外可以把照片再优化一下,看起来水润一些、漂亮一些。这项功能以后没什么事的时候,或有项目需求的时候再做。
后续展望
老子发发狠,把各种证件都识别一下。上网一查:
23种证件可乘火车:
二代居民身份证、临时身份证、户口簿、中国人民解放军军人保障卡、军官证、武警警官证、士兵证、军队学员证、军队文职干部证、军队离退休干部证、按规定可使用的有效护照、港澳居民来往内地通行证、中华人民共和国来往港澳通行证、台湾居民来往大陆通行证、大陆居民往来台湾通行证、外国人居留证、外国人出入境证、外交官证、领事馆证、海员证、外交部开具的外国人身份证明、地方公安机关出入境管理部门开具的护照报失证明、铁路公安部门填发的乘坐旅客列车临时身份证明。
17种证件可乘飞机:
二代居民身份证、公安部门发放的临时身份证明、军官证、武警警官证、士兵证、军队学员证、军队文职干部证、军队离退休干部证、军队职工证、港澳地区居民旅行证件、台湾同胞旅行证件、外籍旅客护照、旅行证、外交官证、学生证、户口簿、户口所在地公安机关出具的身份证明
哈,这也算一个小功能吧。
这些事情交给一个中专生就可以完成了,只需要定制模板,然后…就没有然后了。