采用红外热成像辅助的人脸识别对活体甄别的算法

采用红外热成像辅助的人脸识别对活体甄别的算法

浙江某学校的一群小学生,在一次课外实验中惊发现,只要用一张打印照片就能代替真人刷脸、骗过小区里的丰巢智能柜,取出父母们的货件,该视频一出就引起了围观。


破解人脸

视频中,一群小学生正围着一个丰巢快递柜,其中一名小男孩用手里的打印照片对着快递柜一扫,就完成了人脸识别。听上去高精尖的人脸识别,居然被一群小学生给破解了


破解人脸

其实,我们目前的人脸识别系统,大多数都是采用:图像采集-->人脸检测-->人脸识别,这几个步骤,送到识别系统的最终是裁剪好的人脸图片,对于活体的检测,并没有太多算法. 百度的人脸识别中有卡通脸的识别,主要是对脸部的图片的层次进行分析,照片和图片的层次和实际人员的图像会有些不同,但是这些检测算法对于,稍作处理的图片或动态的图像就会很逊色.能不能找到一种更加精确的方法来识别活体和假体人像的方法呢?

大家知道,绝对零度的物体都会对外辐射发光,在我们常温范围(0-80摄氏度),这个范围辐射的光正处在红外波段,采用红外人成像技术就可以把图像中的活体人像采集出来,通过可见光的采集的图像(摄像头)和红外线采集的图像(红外热成像传感器)进行比对,用于判断我们采集的图像是否为活体.

红外热成像

1. 红外热成像

我们需要对采集的人脸特征做判断,一张红外热成像的图片,既有大致的形状特征,也有基于颜色的温标(一般越蓝温度越低,越红温度越高).
这个就是我使用AMG8833的红外热释传感器做的一套红外热释成像的小玩具.这个传感器是8x8的像素,虽然分辨率低,但是对大致的形状和特征还是可以识别的
对于温度的识别还是不错的,关键是这个组件成本很低,某宝上我用了不到300人民币就可以买到.显示部分正好那基于esp32的M5小盒子做的.

这张照片,对着一杯茶水,看着屏幕上一大团红色,没错,我喜欢喝热茶.

代码也比较简单

#include 
#include 
#include 
#include 
//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的层数也可以不用太多.
但是这个模型比较特殊,一般的模型都是一个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.运行

模型训练完成之后,系统同时采集可见光人像和红外热成像的图片,把图片送到模型中判断,模型通过训练可以有效的结合2各渠道的图片推理出采集的图像是否为活体.
理论上这个算法比单纯依靠可见光的活算法,或者仅仅依靠红外人成像温度判断要可靠地多.
当然这个还要靠实践检验.

你可能感兴趣的:(采用红外热成像辅助的人脸识别对活体甄别的算法)