java快照分析(OCR识别护照一)
java快照分析(OCR识别护照一)package org.xy.passportScanner.data; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; public class PassportBean { //文件序号 private StringProperty id = new SimpleStringProperty(); //文件名 private StringProperty file = new SimpleStringProperty(); //护照号 private StringProperty number = new SimpleStringProperty(); //姓名 privat
本系列文章将介绍一个使用OCR技术批量识别护照的桌面小工具的编写过程。使用到的主要技术为java、JavaFx、pdfbox等。最终代码将上传Github和Gitee。
OCR识别护照(二):护照识别及数据导出
需求描述一个在国际学校做HR的朋友跟我说总是需要在OA系统或excel中录入外籍人员的护照信息,每次都需要打开pdf文件或图片对照填写,一旦遇到大批外籍员工入职或更换护照、签证信息时,工作量较大且容易出错。于是我利用业余时间给她写了一个小工具,可以批量识别护照。
新建javafx项目在Idea中新建JavaFxApplication,取名passportScanner。
界面布局使用Idea自带的javafx编辑工具,简单地实现程序的界面布局。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<AnchorPane prefHeight="500.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.xy.passportScanner.MainController">
<children>
<VBox layoutX="14.0" layoutY="6.0" prefHeight="500.0" prefWidth="1024.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<GridPane hgap="8.0" prefHeight="80.0" prefWidth="1024.0" vgap="12.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="300.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="160.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="fileButton" mnemonicParsing="false" text="选择文件" GridPane.columnIndex="1">
<font>
<Font size="14.0" />
</font></Button>
<Label text="(png jpg bmp pdf)" GridPane.columnIndex="1">
<GridPane.margin>
<Insets left="90.0" />
</GridPane.margin>
<font>
<Font size="14.0" />
</font>
</Label>
<TextArea fx:id="filePath" editable="false" prefHeight="200.0" prefWidth="200.0" />
<HBox prefHeight="100.0" prefWidth="200.0" spacing="20.0" GridPane.columnSpan="2" GridPane.rowIndex="1">
<children>
<Button fx:id="scanButton" graphicTextGap="8.0" layoutX="10.0" layoutY="10.0" minWidth="108.0" mnemonicParsing="false" text="开始识别">
<font>
<Font size="20.0" />
</font>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets>
</Button>
<Label fx:id="tipLabel" prefHeight="40.0" wrapText="true">
<font>
<Font size="18.0" />
</font>
</Label>
</children>
</HBox>
</children>
<VBox.margin>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</VBox.margin>
</GridPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<children>
<TableView fx:id="resultTable" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="420.0" prefWidth="1024.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</children>
</VBox>
</children>
</AnchorPane>
设置界面大小、图标、标题
在resources/images文件夹中添加passport.png。
在Main.java的start方法中添加以下代码。
package org.xy.passportScanner;
import javafx.scene.image.Image;
import org.xy.passportScanner.config.GlobalData;
import org.xy.passportScanner.config.Settings;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.Constructor.Constructor;
import java.io.InputStream;
public class Main extends Application {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
@Override
public void start(Stage primaryStage) throws Exception {
//加载界面文件
Parent load = FXMLLoader.load(getClass().getClassLoader().getResource("main.fxml"));
//设置窗口大小
Scene scene = new Scene(load 1024 500);
primaryStage.setScene(scene);
//设置标题和logo
primaryStage.setTitle("PassportScanner");
primaryStage.getIcons().add(new Image("images/passport.png"));
//显示页面
primaryStage.show();
//关闭事件处理
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.exit(0);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
tableview绑定数据
新建PassportBean.java类,与不同javabean不同,所有属性均使用StringProperty,以便与javafx组件绑定数据。
package org.xy.passportScanner.data;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class PassportBean {
//文件序号
private StringProperty id = new SimpleStringProperty();
//文件名
private StringProperty file = new SimpleStringProperty();
//护照号
private StringProperty number = new SimpleStringProperty();
//姓名
private StringProperty name = new SimpleStringProperty();
//性别
private StringProperty gender = new SimpleStringProperty();
//出生日期
private StringProperty birthday = new SimpleStringProperty();
//有效期
private StringProperty expireDate = new SimpleStringProperty();
//签发国
private StringProperty issuingCountry = new SimpleStringProperty();
//国籍
private StringProperty nationality = new SimpleStringProperty();
//备注
private StringProperty comments = new SimpleStringProperty();
public PassportBean(String id String file String number String name String gender String birthday String expireDate String issuingCountry String nationality String comments){
this.id.set(id);
this.file.set(file);
this.number.set(number);
this.name.set(name);
this.gender.set(gender);
this.birthday.set(birthday);
this.expireDate.set(expireDate);
this.issuingCountry.set(issuingCountry);
this.nationality.set(nationality);
this.comments.set(comments);
}
public PassportBean(){}
public String getId() {
return id.get();
}
public StringProperty idProperty() {
return id;
}
public void setId(String id) {
this.id.set(id);
}
public String getFile() {
return file.get();
}
public StringProperty fileProperty() {
return file;
}
public void setFile(String file) {
this.file.set(file);
}
public String getNumber() {
return number.get();
}
public StringProperty numberProperty() {
return number;
}
public void setNumber(String number) {
this.number.set(number);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getGender() {
return gender.get();
}
public StringProperty genderProperty() {
return gender;
}
public void setGender(String gender) {
this.gender.set(gender);
}
public String getBirthday() {
return birthday.get();
}
public StringProperty birthdayProperty() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday.set(birthday);
}
public String getExpireDate() {
return expireDate.get();
}
public StringProperty expireDateProperty() {
return expireDate;
}
public void setExpireDate(String expireDate) {
this.expireDate.set(expireDate);
}
public String getIssuingCountry() {
return issuingCountry.get();
}
public StringProperty issuingCountryProperty() {
return issuingCountry;
}
public void setIssuingCountry(String issuingCountry) {
this.issuingCountry.set(issuingCountry);
}
public String getNationality() {
return nationality.get();
}
public StringProperty nationalityProperty() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality.set(nationality);
}
public String getComments() {
return comments.get();
}
public StringProperty commentsProperty() {
return comments;
}
public void setComments(String comments) {
this.comments.set(comments);
}
}
在MainController的initialize方法中为tableview绑定数据
//初始化结果表
resultTable.setItems(data);
TableColumn<PassportBean String> id = new TableColumn<PassportBean String>("序号");
id.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.05));
id.setCellFactory(TextFieldTableCell.forTableColumn());
id.setCellValueFactory(new PropertyValueFactory<PassportBean String>("id"));
id.getStyleClass().add("alarm-table");
resultTable.getColumns().add(id);
TableColumn<PassportBean String> file = new TableColumn<PassportBean String>("文件");
file.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.15));
file.setCellValueFactory(new PropertyValueFactory<PassportBean String>("file"));
file.getStyleClass().add("alarm-table");
resultTable.getColumns().add(file);
TableColumn<PassportBean String> number = new TableColumn<PassportBean String>("护照号");
number.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.1));
number.setCellValueFactory(new PropertyValueFactory<PassportBean String>("number"));
number.getStyleClass().add("alarm-table");
resultTable.getColumns().add(number);
TableColumn<PassportBean String> name = new TableColumn<PassportBean String>("姓名");
name.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.25));
name.setCellValueFactory(new PropertyValueFactory<PassportBean String>("name"));
name.getStyleClass().add("alarm-table");
resultTable.getColumns().add(name);
TableColumn<PassportBean String> gender = new TableColumn<PassportBean String>("性别");
gender.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.05));
gender.setCellValueFactory(new PropertyValueFactory<PassportBean String>("gender"));
gender.getStyleClass().add("alarm-table");
resultTable.getColumns().add(gender);
TableColumn<PassportBean String> birthday = new TableColumn<PassportBean String>("出生日期");
birthday.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.1));
birthday.setCellValueFactory(new PropertyValueFactory<PassportBean String>("birthday"));
birthday.getStyleClass().add("alarm-table");
resultTable.getColumns().add(birthday);
TableColumn<PassportBean String> expireDate = new TableColumn<PassportBean String>("有效期");
expireDate.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.1));
expireDate.setCellValueFactory(new PropertyValueFactory<PassportBean String>("expireDate"));
expireDate.getStyleClass().add("alarm-table");
resultTable.getColumns().add(expireDate);
TableColumn<PassportBean String> issuingCountry = new TableColumn<PassportBean String>("签发国");
issuingCountry.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.05));
issuingCountry.setCellValueFactory(new PropertyValueFactory<PassportBean String>("issuingCountry"));
issuingCountry.getStyleClass().add("alarm-table");
resultTable.getColumns().add(issuingCountry);
TableColumn<PassportBean String> nationality = new TableColumn<PassportBean String>("国籍");
nationality.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.05));
nationality.setCellValueFactory(new PropertyValueFactory<PassportBean String>("nationality"));
nationality.getStyleClass().add("alarm-table");
resultTable.getColumns().add(nationality);
TableColumn<PassportBean String> comments = new TableColumn<PassportBean String>("备注");
comments.prefWidthProperty().bind(resultTable.widthProperty().multiply(0.1));
comments.setCellValueFactory(new PropertyValueFactory<PassportBean String>("comments"));
comments.getStyleClass().add("alarm-table");
resultTable.getColumns().add(comments);
//模拟数据
data.add(new PassportBean("1" "c:/123" "12345" "LI SI" "M" "20010302" "20250909" "CHN" "CHN" ""));
data.add(new PassportBean("2" "c:/123" "12345" "LI SI" "M" "20010302" "20250909" "CHN" "CHN" ""));
运行Main看看效果
至此,界面已经可以运行了。运行Main.main后,界面如下图。
javafx打包为exe程序javafx打包使用的plugin为com.zenjava,详细配置如下:
如果需要修改默认的exe图标,则需要制作相应的ico图片,并放到指定目录下,如resources/images/passport.ico。
<plugin>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>8.8.3</version>
<configuration>
<!--运行类-->
<mainClass>org.xy.passportScanner.Main</mainClass>
<vendor>pegasus</vendor>
<bundleArguments>
<!-- 指示用户是否可以选择应用程序安装目录 -->
<installdirChooser>true</installdirChooser>
<!--exe图标-->
<icon>${project.basedir}/src/main/resources/images/passport.ico</icon>
</bundleArguments>
<allPermissions>true</allPermissions>
<!--添加到程序列表-->
<needMenu>true</needMenu>
<!--桌面快捷方式-->
<needShortcut>true</needShortcut>
</configuration>
</plugin>
配置好以后,执行maven clean package和jfx:build-native即可将javafx程序打包生成exe文件,同时将java运行环境一起打包。