以前用樹莓派開發一套簡易的視頻監控平臺,正好週日有時間,此次用樹莓派實現了人臉打卡機。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實現的界面模塊也能夠在公衆號上下載!!!
關注公衆號,閱讀更多文章。