之前用樹莓派開發一套簡易的視頻監控平台,正好周日有時間,這次用樹莓派實現了人臉打卡機。
樹莓派相關文章:
- 樹莓派搭建nexus2.x私服
- 樹莓派搭建視頻監控平台
- 樹莓派視頻監控平台實現錄制歸檔
- 樹莓派實現人臉打卡機 (本文)
1. 功能設計
樹莓派人臉打卡機,主要包括兩個大方向的功能要求:
a. 人臉采集存檔
b. 人臉識別簽到
這兩個功能配合使用就能實現人臉打卡了, 通過人臉采集將人臉信息預存檔在系統中,簽到的時候,當人靠近攝像頭時實時采集人臉,然后比對現有人臉,如果信息匹配則認為簽到成功。
2. 開發人臉采集模塊
人臉采集模塊主要的工作就是從攝像頭采集視頻幀,然后交給界面回顯,這里使用的是JavaCV中的opencv模塊。
頻繁采集視頻幀是一個很耗CPU的過程,我在這里做了一些優化處理,即:當檢測到沒有人臉的時候,程序休眠更長的時間(1秒),而當檢測到人臉時,采集間隔調整為180毫秒。
下面是完整的代碼:
/**
* @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,來消費采集到的人臉圖片,以及人臉區域。
3. 開發人臉識別模塊
人臉識別這里直接采用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;
}
這里也可以將圖片灰度化處理再對比。
Imgproc.cvtColor(dst, hsv, Imgproc.COLOR_BGR2GRAY);
4. 開發界面控制層
界面使用JavaFX來開發,功能比較單一,只要程序啟動的時候,啟動視頻采集線程即可。
這里需要注意的是,當長時間沒有識別到人臉的時候,界面不應該顯示之前的人臉信息, 所以需要另起一個線程來監控是否有人臉識別信息,如果沒有,則顯示默認的圖片。
人臉采集回顯部分
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>
=========================================================
項目源碼可關注公眾號 “HiIT青年” 發送 “raspi-face” 獲取。(如果沒有收到回復,可能是你之前取消過關注。)
!!!基於Swing實現的界面模塊也可以在公眾號上下載!!!
關注公眾號,閱讀更多文章。