以前用树莓派开发一套简易的视频监控平台,正好周日有时间,此次用树莓派实现了人脸打卡机。html
树莓派相关文章:java
树莓派人脸打卡机,主要包括两个大方向的功能要求:
a. 人脸采集存档
b. 人脸识别签到
这两个功能配合使用就能实现人脸打卡了, 经过人脸采集将人脸信息预存档在系统中,签到的时候,当人靠近摄像头时实时采集人脸,而后比对现有人脸,若是信息匹配则认为签到成功。shell
下面是签到的效果:
当人脸签到成功后,程序界面底部会显示签到时间和签到人的工号。架构
人脸采集模块主要的工做就是从摄像头采集视频帧,而后交给界面回显,这里使用的是JavaCV中的opencv模块。
频繁采集视频帧是一个很耗CPU的过程,我在这里作了一些优化处理,即:当检测到没有人脸的时候,程序休眠更长的时间(1秒),而当检测到人脸时,采集间隔调整为180毫秒。ide
下面是完整的代码:布局
/** * @author itqn */ public class FaceCapture implements Runnable { private VideoCapture capture; private CascadeClassifier classifier; private OpenCVFrameConverter.ToMat matConvert; private JavaFXFrameConverter converter; private BiConsumer<Image, Rect> videoConsumer; public FaceCapture(BiConsumer<Image, Rect> videoConsumer) { this.videoConsumer = videoConsumer; init(); } private void init() { capture = new VideoCapture(); classifier = new CascadeClassifier("samples//haarcascade_frontalface_alt.xml"); matConvert = new OpenCVFrameConverter.ToMat(); converter = new JavaFXFrameConverter(); capture.open(0); } private void destroy() { capture.close(); } @Override public void run() { boolean find; Mat image = new Mat(); RectVector vector = new RectVector(); while (capture.isOpened()) { find = false; capture.read(image); classifier.detectMultiScale(image, vector); for (Rect rect : vector.get()) { find = true; Image video = converter.convert(matConvert.convert(image)); videoConsumer.accept(video, rect); break; } // if no face sleep 1 second try { if (find) { TimeUnit.MILLISECONDS.sleep(180); } else { TimeUnit.SECONDS.sleep(1); } } catch (InterruptedException ignore) { } } } }
这里调用者经过注册videoConsumer,来消费采集到的人脸图片,以及人脸区域。测试
人脸识别这里直接采用opencv的native API,采用直方图对比的方式对比,这里采用相关性数据做为人脸识别成功的基准,若是相关度高于0.7则认为人脸匹配。
程序经过将采集到的人脸信息跟已经存档的人脸信息注意对比,到达基准0.7以上则返回工号(图片是以工号命名的)。优化
private static final double EXPECT_SCORE = 0.7d; public static String parser(String tmp, String dir) { Mat tmpImg = Imgcodecs.imread(tmp, 1); File imgDir = new File(dir); String[] fList = imgDir.list((d, n) -> n.endsWith(".png")); if (fList == null) { return null; } for (String f : fList) { Mat dstImg = Imgcodecs.imread(dir + File.separator + f, 1); Mat h1 = new Mat(); Mat h2 = new Mat(); Imgproc.calcHist(Collections.singletonList(tmpImg), channels, new Mat(), h1, histSize, ranges); Imgproc.calcHist(Collections.singletonList(dstImg), channels, new Mat(), h2, histSize, ranges); Core.normalize(h1, h1, 0d, 1d, Core.NORM_MINMAX, -1, new Mat()); Core.normalize(h2, h2, 0d, 1d, Core.NORM_MINMAX, -1, new Mat()); double score = Imgproc.compareHist(h1, h2, Imgproc.HISTCMP_CORREL); if (score > EXPECT_SCORE) { return f.substring(0, f.length() - 4); } } return null; }
这里也能够将图片灰度化处理再对比。ui
Imgproc.cvtColor(dst, hsv, Imgproc.COLOR_BGR2GRAY);
界面使用JavaFX来开发,功能比较单一,只要程序启动的时候,启动视频采集线程便可。
这里须要注意的是,当长时间没有识别到人脸的时候,界面不该该显示以前的人脸信息, 因此须要另起一个线程来监控是否有人脸识别信息,若是没有,则显示默认的图片。this
人脸采集回显部分
private void startVideoCapture() { new Thread(new FaceCapture((v, r) -> { Image tmp = FaceUtils.sub(v, r.x(), r.y(), r.width(), r.height()); try { FaceUtils.store(tmp, tmpPath); String id = FaceParser.parser(tmpPath, dir); if (id != null) { Platform.runLater(() -> { message.setText(sdf.format(new Date()) + ", 工号:" + id + "签到成功。"); // for sign service }); } } catch (IOException e) { alert.setContentText(e.getMessage()); alert.show(); } Platform.runLater(() -> { video.setImage(v); timestamp.set(System.currentTimeMillis()); if (!find.get()) { avatar.setImage(tmp); find.set(true); } }); })).start(); }
空闲监控,显示默认图部分
这里认为2秒内没有人脸识别信息则认为是空闲。
private void startVideoListener() { new Thread(() -> { while (true) { if (System.currentTimeMillis() - timestamp.get() > 2 * 1000) { Platform.runLater(() -> { video.setImage(DEF_VIDEO_IMAGE); avatar.setImage(DEF_AVA_TAR); uid.setText(""); message.setText(DEF_MESSAGE); }); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ignore) { } } }).start(); }
界面布局
布局采用JavaFX的fxml来设计。
<BorderPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.itqn.raspi.video.VideoController"> <right> <VBox alignment="CENTER" prefWidth="120.0" spacing="20.0" BorderPane.alignment="CENTER"> <children> <ImageView fx:id="avatar" fitHeight="100.0" fitWidth="100.0"/> <HBox alignment="CENTER" prefHeight="40.0"> <Label text="工号 "/> <TextField fx:id="uid" prefWidth="60"/> </HBox> <HBox alignment="CENTER" prefHeight="40.0" spacing="5.0"> <Button onAction="#store" text="存档"/> <Button onAction="#reset" text="采集"/> </HBox> </children> <padding> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> </padding> </VBox> </right> <bottom> <HBox alignment="CENTER_LEFT" prefHeight="40.0" spacing="20.0" BorderPane.alignment="CENTER"> <Label text="打开信息:"/> <Label fx:id="message"/> <padding> <Insets bottom="10.0" left="50.0" right="10.0" top="10.0"/> </padding> </HBox> </bottom> <center> <ImageView fx:id="video" fitWidth="320.0" fitHeight="180.0"/> </center> </BorderPane>
当没有检测到人脸的时候,程序会休眠更长的时间(1秒)以下降CPU的使用率,下面是空闲时的界面。
用户开始使用的时候,能够经过采集人脸进行工号绑定,下面是采集存档成功的界面。
因为程序是在Windows环境下开发的,程序开发完成,测试完美经过,然而树莓派是armv7架构的,默认安装的jdk8并不支持JavaFX。
从新开发了一套基于swing的UI,本来的UI应该是这样的:
不支持JavaFX,有解决办法,不过测试了一下,效果不行,下面是解决方案:
https://gluonhq.com/products/mobile/javafxports/get/
而后每次启动的时候指定ext模块
java -Djava.ext.dirs=/home/pi/armv6hf-sdk/rt/lib/ext -jar raspi-video.jar
https://docs.gluonhq.com/javafxports/
解决这个问题比较简单,只须要将SwingFXUtils这个类的源码复制一份便可。
=========================================================
项目源码可关注公众号 “HiIT青年” 发送 “raspi-face” 获取。
!!!基于Swing实现的界面模块也能够在公众号上下载!!!
关注公众号,阅读更多文章。