采用紅外熱成像輔助的人臉識別對活體甄別的算法
浙江某學校的一群小學生,在一次課外實驗中驚發(fā)現(xiàn),只要用一張打印照片就能代替真人刷臉横缔、騙過小區(qū)里的豐巢智能柜镜遣,取出父母們的貨件己肮,該視頻一出就引起了圍觀。
視頻中,一群小學生正圍著一個豐巢快遞柜谎僻,其中一名小男孩用手里的打印照片對著快遞柜一掃娄柳,就完成了人臉識別。聽上去高精尖的人臉識別艘绍,居然被一群小學生給破解了
其實,我們目前的人臉識別系統(tǒng),大多數(shù)都是采用:圖像采集-->人臉檢測-->人臉識別,這幾個步驟,送到識別系統(tǒng)的最終是裁剪好的人臉圖片,對于活體的檢測,并沒有太多算法. 百度的人臉識別中有卡通臉的識別,主要是對臉部的圖片的層次進行分析,照片和圖片的層次和實際人員的圖像會有些不同,但是這些檢測算法對于,稍作處理的圖片或動態(tài)的圖像就會很遜色.能不能找到一種更加精確的方法來識別活體和假體人像的方法呢?
大家知道,絕對零度的物體都會對外輻射發(fā)光,在我們常溫范圍(0-80攝氏度),這個范圍輻射的光正處在紅外波段,采用紅外人成像技術就可以把圖像中的活體人像采集出來,通過可見光的采集的圖像(攝像頭)和紅外線采集的圖像(紅外熱成像傳感器)進行比對,用于判斷我們采集的圖像是否為活體.
1. 紅外熱成像
我們需要對采集的人臉特征做判斷,一張紅外熱成像的圖片,既有大致的形狀特征,也有基于顏色的溫標(一般越藍溫度越低,越紅溫度越高).
這個就是我使用AMG8833的紅外熱釋傳感器做的一套紅外熱釋成像的小玩具.這個傳感器是8x8的像素,雖然分辨率低,但是對大致的形狀和特征還是可以識別的
對于溫度的識別還是不錯的,關鍵是這個組件成本很低,某寶上我用了不到300人民幣就可以買到.顯示部分正好那基于esp32的M5小盒子做的.
這張照片,對著一杯茶水,看著屏幕上一大團紅色,沒錯,我喜歡喝熱茶.
代碼也比較簡單
#include <M5Stack.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_AMG88xx.h>
//low range of the sensor (this will be blue on the screen)
byte MINTEMP = 20;
//high range of the sensor (this will be red on the screen)
byte MAXTEMP = 32;
//the colors we will be using
const uint16_t camColors[] = {0x480F,
0x400F, 0x400F, 0x400F, 0x4010, 0x3810, 0x3810, 0x3810, 0x3810, 0x3010, 0x3010,
0x3010, 0x2810, 0x2810, 0x2810, 0x2810, 0x2010, 0x2010, 0x2010, 0x1810, 0x1810,
0x1811, 0x1811, 0x1011, 0x1011, 0x1011, 0x0811, 0x0811, 0x0811, 0x0011, 0x0011,
0x0011, 0x0011, 0x0011, 0x0031, 0x0031, 0x0051, 0x0072, 0x0072, 0x0092, 0x00B2,
0x00B2, 0x00D2, 0x00F2, 0x00F2, 0x0112, 0x0132, 0x0152, 0x0152, 0x0172, 0x0192,
0x0192, 0x01B2, 0x01D2, 0x01F3, 0x01F3, 0x0213, 0x0233, 0x0253, 0x0253, 0x0273,
0x0293, 0x02B3, 0x02D3, 0x02D3, 0x02F3, 0x0313, 0x0333, 0x0333, 0x0353, 0x0373,
0x0394, 0x03B4, 0x03D4, 0x03D4, 0x03F4, 0x0414, 0x0434, 0x0454, 0x0474, 0x0474,
0x0494, 0x04B4, 0x04D4, 0x04F4, 0x0514, 0x0534, 0x0534, 0x0554, 0x0554, 0x0574,
0x0574, 0x0573, 0x0573, 0x0573, 0x0572, 0x0572, 0x0572, 0x0571, 0x0591, 0x0591,
0x0590, 0x0590, 0x058F, 0x058F, 0x058F, 0x058E, 0x05AE, 0x05AE, 0x05AD, 0x05AD,
0x05AD, 0x05AC, 0x05AC, 0x05AB, 0x05CB, 0x05CB, 0x05CA, 0x05CA, 0x05CA, 0x05C9,
0x05C9, 0x05C8, 0x05E8, 0x05E8, 0x05E7, 0x05E7, 0x05E6, 0x05E6, 0x05E6, 0x05E5,
0x05E5, 0x0604, 0x0604, 0x0604, 0x0603, 0x0603, 0x0602, 0x0602, 0x0601, 0x0621,
0x0621, 0x0620, 0x0620, 0x0620, 0x0620, 0x0E20, 0x0E20, 0x0E40, 0x1640, 0x1640,
0x1E40, 0x1E40, 0x2640, 0x2640, 0x2E40, 0x2E60, 0x3660, 0x3660, 0x3E60, 0x3E60,
0x3E60, 0x4660, 0x4660, 0x4E60, 0x4E80, 0x5680, 0x5680, 0x5E80, 0x5E80, 0x6680,
0x6680, 0x6E80, 0x6EA0, 0x76A0, 0x76A0, 0x7EA0, 0x7EA0, 0x86A0, 0x86A0, 0x8EA0,
0x8EC0, 0x96C0, 0x96C0, 0x9EC0, 0x9EC0, 0xA6C0, 0xAEC0, 0xAEC0, 0xB6E0, 0xB6E0,
0xBEE0, 0xBEE0, 0xC6E0, 0xC6E0, 0xCEE0, 0xCEE0, 0xD6E0, 0xD700, 0xDF00, 0xDEE0,
0xDEC0, 0xDEA0, 0xDE80, 0xDE80, 0xE660, 0xE640, 0xE620, 0xE600, 0xE5E0, 0xE5C0,
0xE5A0, 0xE580, 0xE560, 0xE540, 0xE520, 0xE500, 0xE4E0, 0xE4C0, 0xE4A0, 0xE480,
0xE460, 0xEC40, 0xEC20, 0xEC00, 0xEBE0, 0xEBC0, 0xEBA0, 0xEB80, 0xEB60, 0xEB40,
0xEB20, 0xEB00, 0xEAE0, 0xEAC0, 0xEAA0, 0xEA80, 0xEA60, 0xEA40, 0xF220, 0xF200,
0xF1E0, 0xF1C0, 0xF1A0, 0xF180, 0xF160, 0xF140, 0xF100, 0xF0E0, 0xF0C0, 0xF0A0,
0xF080, 0xF060, 0xF040, 0xF020, 0xF800,
};
Adafruit_AMG88xx amg;
#define AMG_COLS 8
#define AMG_ROWS 8
float pixels[AMG_COLS * AMG_ROWS];
#define INTERPOLATED_COLS 24
#define INTERPOLATED_ROWS 24
int max_v = 0;
int min_v = 80;
float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols, float *dest, uint8_t dest_rows, uint8_t dest_cols);
void setup()
{
M5.begin();
M5.setWakeupButton(BUTTON_B_PIN);
M5.Lcd.begin();
M5.Lcd.setRotation(0);
M5.Lcd.fillScreen(TFT_BLACK);
Serial.begin(115200);
int icolor = 255;
for (int irow = 16; irow <= 223; irow++)
{
M5.Lcd.drawRect(0, 0, 35, irow, camColors[icolor]);
icolor--;
}
infodisplay();
if (!amg.begin())
{
while (1)
{
delay(1);
}
}
}
void loop() {
if (M5.BtnA.pressedFor(1000)) {
MINTEMP = min_v;
min_v = 80;
infodisplay();
}
if (M5.BtnA.wasPressed()) {
if (MINTEMP <= 0)
{
MINTEMP = MAXTEMP - 1;
infodisplay();
}
else
{
MINTEMP--;
infodisplay();
}
}
if (M5.BtnB.pressedFor(1000)) {
M5.powerOFF();
}
if (M5.BtnC.pressedFor(1000)) {
MAXTEMP = max_v;
max_v = 0;
infodisplay();
}
if (M5.BtnC.wasPressed()) {
if (MAXTEMP >= 80)
{
MAXTEMP = MINTEMP + 1;
infodisplay();
}
else
{
MAXTEMP++;
infodisplay();
}
}
M5.update();
//read all the pixels
amg.readPixels(pixels);
for (int i = 1; i <= AMG88xx_PIXEL_ARRAY_SIZE; i++)
{
}
float dest_2d[INTERPOLATED_ROWS * INTERPOLATED_COLS];
interpolate_image(pixels, AMG_ROWS, AMG_COLS, dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);
uint16_t boxsize = min(M5.Lcd.width() / INTERPOLATED_COLS, M5.Lcd.height() / INTERPOLATED_COLS);
drawpixels(dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS, boxsize, boxsize, false);
max_v = INT_MIN;
// int max_i = 0;
int spot_v = pixels[28];
for ( int itemp = 0; itemp < sizeof(pixels) / sizeof(pixels[0]); itemp++ )
{
if ( pixels[itemp] > max_v )
{
max_v = pixels[itemp];
//max_i = itemp;
}
if ( pixels[itemp] < min_v )
{
min_v = pixels[itemp];
//max_i = itemp;
}
}
M5.Lcd.setTextSize(2);
M5.Lcd.fillRect(284, 18, 36, 16, TFT_BLACK);
M5.Lcd.fillRect(284, 130, 36, 16, TFT_BLACK);
M5.Lcd.setCursor(284, 18);
M5.Lcd.setTextColor(TFT_WHITE);
if (max_v > 80 | max_v < 0) {
M5.Lcd.setTextColor(TFT_RED);
M5.Lcd.printf("Err", 1);
}
else
{ M5.Lcd.print(max_v, 1);
M5.Lcd.printf("C" , 1);
M5.Lcd.setCursor(284, 130);
M5.Lcd.print(spot_v, 1);
M5.Lcd.printf("C" , 1);
M5.Lcd.drawCircle(160, 120, 6, TFT_WHITE);
M5.Lcd.drawLine(160, 110, 160, 130, TFT_WHITE);
M5.Lcd.drawLine(150, 120, 170, 120, TFT_WHITE);
}
}
/***infodisplay()*****/
void infodisplay(void) {
M5.Lcd.setTextColor(TFT_WHITE);
// M5.Lcd.setCursor(288, 230);
// M5.Lcd.printf("Power" , 1);
M5.Lcd.fillRect(0, 0, 36, 16, TFT_BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 1);
M5.Lcd.print(MAXTEMP , 1);
M5.Lcd.printf("C" , 1);
M5.Lcd.setCursor(0, 225);
M5.Lcd.fillRect(0, 225, 36, 16, TFT_BLACK);
M5.Lcd.print(MINTEMP , 1);
M5.Lcd.printf("C" , 1);
M5.Lcd.setCursor(284, 0);
M5.Lcd.printf("Max", 1);
M5.Lcd.setCursor(284, 100);
// M5.Lcd.printf("Spot", 1);
M5.Lcd.drawCircle(300, 120, 6, TFT_WHITE);
M5.Lcd.drawLine(300, 110, 300, 130, TFT_WHITE);
M5.Lcd.drawLine(290, 120, 310, 120, TFT_WHITE);
}
void drawpixels(float *p, uint8_t rows, uint8_t cols, uint8_t boxWidth, uint8_t boxHeight, boolean showVal) {
int colorTemp;
Serial.println('{');
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
float val = get_point(p, rows, cols, x, y);
if (val >= MAXTEMP) colorTemp = MAXTEMP;
else if (val <= MINTEMP) colorTemp = MINTEMP;
else colorTemp = val;
uint8_t colorIndex = map(colorTemp, MINTEMP, MAXTEMP, 0, 255);
colorIndex = constrain(colorIndex, 0, 255);
//draw the pixels!
uint16_t color;
color = val * 2;
Serial.print(camColors[colorIndex],DEC);
Serial.print(',');
M5.Lcd.fillRect(40 + boxWidth * x, boxHeight * y, boxWidth, boxHeight, camColors[colorIndex]);
}
}
Serial.println('}');
}
2.活體識別算法
我們這次需要判斷可見光的圖片的特征和紅外光的圖片特征,如果簡單的判斷溫度,不能很好的處理一些作假和偽造的行為.我們采用深度學習的算法,采集可見光的人像,同時采集紅外熱成像的圖片
,將圖片分類后訓練模型,通過訓練模型,以后再接受2張圖片:一張可見光的,一張紅外的,模型可以很好地預測出結果,有效判斷采集的圖像中人像是否為活體.
這個就是我的初步的思路.
應為主要做特征判斷,所以可見光圖像(人臉)可以使用128X128或者64X64,紅外光受限硬件只能是8x8的圖片.
深度學習模型這邊,因為是圖片,采用CNN(Convolutional Neural Networks)是個不錯的選擇, 圖片也不到cnn的層數(shù)也可以不用太多.
但是這個模型比較特殊,一般的模型都是一個INPUT和一個OUTPUT,這個模型需要2個INPUT.
這個需要我們搭建一個比較特殊的模型,有2個INPUT的CNN深度學習模型.
代碼先簡單描述一下:
# 定義2個輸入
inputA = Input(shape=(8,8,3))
inputB = Input(shape=(128,128,3))
# 第一個輸入
x = Dense((8,8,3), activation="relu")(inputA)
x = Dense(4, activation="relu")(x)
x = Model(inputs=inputA, outputs=x)
# 第二個輸入
y = Dense(((64,64,3), activation="relu")(inputB)
y = Dense(32, activation="relu")(y)
y = Dense(4, activation="relu")(y)
y = Model(inputs=inputB, outputs=y)
# 合并2個輸入
combined = concatenate([x.output, y.output])
z = Dense(2, activation="relu")(combined)
z = Dense(1, activation="linear")(z)
# 整合到一個模型
model = Model(inputs=[x.input, y.input], outputs=z)
3.運行
模型訓練完成之后,系統(tǒng)同時采集可見光人像和紅外熱成像的圖片,把圖片送到模型中判斷,模型通過訓練可以有效的結合2各渠道的圖片推理出采集的圖像是否為活體.
理論上這個算法比單純依靠可見光的活算法,或者僅僅依靠紅外人成像溫度判斷要可靠地多.
當然這個還要靠實踐檢驗.