AI 框架

更新时间:
2023-11-23
下载文档

AI 框架

本章主要介绍爱智 AI 框架。

概述

爱智具备强大的边缘 AI 处理能力,提供 AI 组件库和便捷的 JS API,方便开发者接入和使用 AI。同时提供多种灵活的 AI 模型安装机制,让更多的模型运行在爱智上,适配不同的用户场景,EdgerOS AI 包含以下框架:

  • NCNN 是一个针对嵌入式平台优化的高性能神经网络计算框架, 详情请参考 AI Engine/NCNN
  • FaceNN 模块提供人脸检测和识别功能,详情请参考 AI Engine/FaceNN
  • HandNN 模块提供手部检测识别功能,详情请参考 AI Engine/HandNN
  • ThingNN 模块提供物体检测和识别功能,详情请参考 AI Engine/ThingNN
  • LicPlateNN 模块提供车牌检测和识别功能,详情请参考 AI Engine/LicPlateNN

应用示例

爱智提供以下 AI 应用示例,供开发者参考:

功能介绍

人脸识别

用户信息记录与人脸识别等 AI 功能由 faceai (facelock/face.js) 模块提供。

const faceai = require("./face");
  1. 前后端使用 WebSyncTable 技术进行交互,参考以下示例,应用初始化时首先创建 WebSyncTable 对象。

    const SyncTable = require("synctable");
    const WebSyncTable = require("websynctable");
    // ...
    var main = new SyncTable("main");
    var server = new WebSyncTable(app, main);
    
  2. 参考以下示例,在前端引入 @edgeros/web-synctable 模块。

    <script src="static/js/synctable.min.js"></script>
    
  3. 参考以下示例,在前端创建 WebSyncTable 对象。

    var link = null;
    app.security.init(function() {
      if (link == null) {
        var proto = location.protocol === "http:" ? "ws:" : "wss:";
        var server = `${proto}//${window.location.host}`;
        link = new SyncTable(server, "main", app.security);
        // ...
      }
    });
    
  4. 数据采集与解锁通过 ESP32 SDDC 设备实现,ESP32 SDDC 设备与 EdgerOS 之间通过 SDDC 协议传送数据以及收发指令。参考以下示例,在应用初始化时创建一个设备对象。

    const Device = require("device");
    const dev = new Device();
    

人脸解锁

ESP32 SDDC 设备从人体传感器获取一路输入信号,当用户经过人体传感器时,触发一次解锁过程,设备向应用发送 recv 消息,参考以下示例,实现应用中设备对象监听消息(main.js)。

dev.on("message", function(msg) {
  if (msg && msg.cmd === "recv" && msg.size > 0) {
    recv(false, msg.size)
      .then((chunk) => {
        lastpic = chunk;
        var name = undefined;
        var info = faceai.detect(lastpic);
        if (info) {
          for (var face of info.faces) {
            var feature = faceai.feature(face, info.bitmap);
            if (feature) {
              name = faceai.best(feature);
              if (name) {
                dev.send({ cmd: "unlock", timeout: 5000 });
              }
              break;
            }
          }
        } else {
          console.log("No face detect!");
        }
        server.reverse("recv", { file: RECV_FILE, name });
      })
      .catch((error) => console.error(error));
  }
});

人脸解锁过程:

  1. recv() 方法从设备获取图像并更新到缓存。
  2. faceai.detect() 从缓存侦测人脸信息。
  3. faceai.feature() 提取人脸特征值。
  4. faceai.best() 从数据库中比对人脸特征值并返回用户识别结果。
  5. 识别到合法用户时,dev.send() 向设备发起解锁命令,其中选项 timeout 表示开锁持续时间。
  6. server.reverse() 向前端反馈结果,前端更新识别的图像。

设备发现

参考以下示例,在 devreq() 方法中首先获取设备列表,从列表中筛选我们需要的设备,然后将设备对象与有效的设备信息关联起来。

async function devreq() {
  var list = await devlist();
  for (var device of list) {
    var info = await devinfo(device.devid);
    if (info && info.report.name === "IoT Camera") {
      request(device.devid);
      break;
    }
  }
}

设备发现步骤:

  1. devlist() 方法中调用 Device.list() 获取设备列表。
  2. 获取设备列表后,用 devinfo() 方法遍历每个设备,devinfo() 方法调用 Device.info() 获取每个设备信息。
  3. 筛选出所需的 AI 设备。

用户注册

  1. 注册用户后才能通过设备采集用户人脸数据,参考以下示例,由前端发起请求。

    recv: async function() {
      try {
        var reply = await link.fetch('recv', {
          cipher: bindings.data.cipher
        }, true, 4000);
        bindings.data.response = reply.res;
        if (reply.res === 'ok') {
          bindings.data.picture = reply.file + `?t=${Date.now()}`;
        }
        // ...
      }
      // ...
    }
    
  2. 后端接收到请求,调用 recv() 过程获取用户图像,并返回图像信息。

    server.on("recv", (msg, client, reply) => {
      // ...
      recv(msg.cipher).then((chunk) => {
        lastpic = chunk;
        reply({ res: "ok", file: RECV_FILE, size: chunk.length });
      });
      // ...
    });
    
  3. 参考以下示例,配置 recv() 实现。

    async function recv(cipher, size) {
      return new Promise((resolve, rejects) => {
        var chunks = [];
        var connector = new Device.Connector(dev, cipher);
    
        dev.send({ cmd: "recv", connector, size });
        // ...
        connector.on("data", function(data) {
          chunks.push(data);
        });
        // ...
        connector.on("close", function() {
          if (chunks.length) {
            var chunk = Buffer.concat(chunks);
            if (!size || chunk.length === size) {
              return resolve(chunk);
            }
          }
           rejects("Recv error!");
        });
      });
    }
    
  4. 前端获取图像地址后,预览图像,当采集到合适的人脸图像后,参考以下示例,前端发出保存请求。

    save: async function() {
      // ...
      try {
        var reply = await link.fetch('save', {
          name: bindings.data.name
        }, true, 4000);
        // ...
      }
      // ...
    }
    
  5. 参考以下示例,后端接收保存请求。

    server.on("save", (msg, client, reply) => {
      // ...
      var info = faceai.detect(lastpic);
      if (info == undefined) {
        reply({ res: "error", info: "No face detect!" });
      } else if (info.faces.length > 1) {
        reply({ res: "error", info: "Too many face detect!" });
      } else {
        var feature = faceai.feature(info.faces[0], info.bitmap);
        if (feature) {
          faceai.save(msg.name, feature);
          reply({ res: "ok" });
        } else {
          reply({ res: "error", info: "Facial features are not obvious!" });
        }
      }
    });
    

补充说明

faceai 模块介绍

在以上用户注册与人脸解锁过程中都使用了 faceai 模块,faceai 模块实现了一个 FaceDatabase 类,用来保存用户注册信息,然后提供了两组接口,一组是数据库接口,另一组是 AI 接口。

FaceDatabase 类封装了一个 LightKV 对象和一个 Map 对象。

class FaceDatabase {
  constructor() {
    this.lkv = new LightKV("./faces.lkv", "c+", LightKV.OBJECT);
    this.map = this.lkv.toMap();
  }
  // ...
}

faceai 主要包括以下接口:

  • faceai.detect() 接口
function detect(picture) {
  try {
    var bitmap = imagecodec.decode(picture, {
      components: imagecodec.COMPONENTS_RGB,
    });
    if (bitmap == undefined) {
      throw new Error("Unknown picture format!");
    }
    var faces = facenn.detect(
      bitmap.buffer,
      {
        width: bitmap.width,
        height: bitmap.height,
        pixelFormat: facenn.PIX_FMT_RGB24,
      },
      true
    );
    if (faces) {
      for (var i = 0; i < faces.length; i++) {
        if (faces[i].score < DETECT_CONFIDENCE_RATE) {
          faces.splice(i, 1);
          i--;
        }
      }
      if (faces.length) {
        console.log("Detect face num:", faces.length);
        return { faces, bitmap };
      }
    }
  } catch (error) {
    console.error("Image decode error", error);
  }
  return undefined;
}

faceai.detect() 实现图像数据接收,侦测图像中人脸,并返回侦测结果,操作步骤如下:

  1. imagecodec.decode() 对图片进行解码,imagecodec 模块请参考 imagecodec
  2. facenn.detect() 侦测图像中人脸,返回侦测到的一组人脸信息,facenn 模块请参考 facenn
  3. 在一个循环中遍历侦测到的人脸信息,faces[i].score < DETECT_CONFIDENCE_RATE 实现按默认阈值过滤掉辨识度低的人脸信息。
  4. 返回已识别的人脸信息。
  • faceai.feature() 接口
function feature(face, bitmap) {
  var feature = facenn.feature(
    bitmap.buffer,
    {
      width: bitmap.width,
      height: bitmap.height,
      pixelFormat: facenn.PIX_FMT_RGB24,
    },
    face,
    { live: true }
  );
  if (feature.live < LIVING_BODY_CONFIDENCE_RATE) {
    console.log("No live:", feature.live);
    return undefined;
  } else {
    return feature.keys;
  }
}

faceai.feature() 接口用于提取人脸特征值,它接收从 faceai.detect() 返回的人脸数据及解码的图像数据,操作步骤如下:

  1. faceai.feature() 使用 facenn.feature() 提取人脸特征信息。
  2. 提供特征值之后,feature.live < LIVING_BODY_CONFIDENCE_RATE 按默认阈值判断活体对象。
  3. 返回满足活体检测的人脸特征值。
  • faceai.best() 接口
function best(feature) {
  var iter = facedb.map[Symbol.iterator]();
  var name = undefined;
  var near = 0;
  for (var item of iter) {
    var same = facenn.compare(feature, item[1]);
    if (same > near) {
      near = same;
      name = item[0];
    }
  }
  if (name) {
    console.info("Best mach with:", name, "prob:", near);
  }
  return near >= RECONGNITION_CONFIDENCE_RATE ? name : undefined;
}

faceai.best() 用于查询用户,接口接收一个人脸特征值,从数据库中检索出与特征值最匹配的用户。操作步骤如下:

  1. facedb.map[Symbol.iterator]() 从数据库缓存中获取迭代器,检索每个用户记录。

  2. facenn.compare(feature, item[1]) 将待匹配的特征值与每个用户数据比对,最后获得与特征值最相似的用户。

  3. 最后还要进行阈值判断,如果匹配成功返回用户名。

扩展 CameraSource

  1. 在 CameraSource 接口 start() 中,新创建的 MediaDecoder 需监听 video 事件获取视频帧数据。

    start() {
      var netcam = new MediaDecoder();
      // ...
      new Promise((resolve, reject) => {
        netcam.open(url, { proto: 'tcp', name: name }, 10000, (err) => {
          // ...
          netcam.on('video', self.onVideo.bind(self));
          resolve(netcam);
        });
      });
      // ...
    }
    
  2. 参考以下示例,为了准确定位人脸在视频中的相对位置,需要将原始视频分辨率信息反馈给前端。

    start() {
      // ...
      super.start.call(self);
      netcam.start();
      var info = netcam.srcVideoFormat();
      self.mediaInfo = { width: info.width, height: info.height, fps: Math.round(info.fps) };
      self.sendDataHeader({ type: 'media' }, self.mediaInfo); /* {width, height, fps} */
      // ...
    }
    
  3. 参考以下示例,为了识别人脸信息,引入 facenn 模块。

    var facenn = require("facenn");
    
  4. 参考以下示例,在注册的事件中接收并处理视频数据。

    onVideo(frame) {
      var buf = new Buffer(frame.arrayBuffer);
      const view = DEF_DETEC_VIEW;
      var faceInfo = facenn.detect(buf, { width: view.width, height: view.height, pixelFormat: FACENN_PIXEL_FORMAT });
      var ret = []; /* Empty array - clear. */
    
      for (var i = 0; i < faceInfo.length; i++) {
        var info = {};
        info.x0 = Math.max(faceInfo[i].x0 - 10, 0);
        info.x1 = Math.min(faceInfo[i].x1 + 10, view.width - 1);
        info.y0 = Math.max(faceInfo[i].y0 - 10, 0);
        info.y1 = Math.min(faceInfo[i].y1, view.height - 1);
    
        info.x0 = Math.round(info.x0 * this.mediaInfo.width / view.width);
        info.x1 = Math.round(info.x1 * this.mediaInfo.width / view.width);
        info.y0 = Math.round(info.y0 * this.mediaInfo.height / view.height);
        info.y1 = Math.round(info.y1 * this.mediaInfo.height / view.height);
        ret.push(info);
      }
    
        var cliMgr = this.getCliMgr();
        cliMgr.iter(function (cli) {
          cli.sendData({ type: 'face' }, ret);
        });
    }
    
  5. 通过 cliMgr 遍历每个客户端,将识别的人脸信息分发给每个已连接的客户端。cli.sendData() 设计为利用数据通道向客户端推送结构化数据的接口。

前端扩展

本节示例前端基于 智能摄像头 示例前端工程 app-demo-camera-base/web 扩展实现, 工程位于 app-demo-camera-ai/web 目录下。本节使用 @edgeros/web-mediaclient 数据通道接口,接收流媒体服务器的人脸识别信息。

  1. 为了便于处理,继承 MediaClient 实现了一个扩展的类 ClientWrap,代码位于 app-demo-camera-ai/web/src/lib/mediaclient.js。

    export default function createMediaClient(
      ClientType,
      origin,
      canvas,
      opts,
      shakeHandle
    ) {
      class ClientWrap extends ClientType {
        constructor(origin, canvas, opts, shakeHandle) {
          super(origin, shakeHandle, opts);
          // ...
        }
      }
      // ...
      return new ClientWrap(origin, canvas, opts, shakeHandle);
    }
    
  2. 参考以下示例,在 Player.vue 中创建 mediaClient 对象。

    import createMediaClient from "@/lib/mediaclient";
    // ...
    const { w, h } = this.getPageSize();
    this.np = new NodePlayer();
    // ...
    var canvas = document.getElementById("layout");
    var proto = location.protocol === "http:" ? "ws:" : "wss:";
    var host = `${proto}//${window.location.host}`;
    var mediaClient = createMediaClient(
      MediaClient,
      host,
      canvas,
      {
        canvaw: w,
        canvah: h,
        path: this.dev.path,
      },
      (client, path) => {
        this.np.start(host + path);
      }
    );
    
  3. 将 layout 画布置于 video 视频画布之上与之重合,实现人脸识别与视频同步。

  4. 处理 ClientWrap 内部实现,参考以下示例。通过监听 MediaClient 的 data 事件,接收流媒体服务器数据通道推送数据。

    constructor(origin, canvas, opts, shakeHandle) {
      super(origin, shakeHandle, opts);
      // ...
      this.on('data', this.onData.bind(this));
    }
    
  5. 参考以下示例,onData 接收并处理 data 事件。

    onData(self, opts, data) {
      var type = opts && opts.type ? opts.type : null;
      if (type === 'media') {
        this.videow = data.width;
        this.videoh = data.height;
        this.rw = this.canvaw / this.videow;
        this.rh = this.canvah / this.videoh;
    
      } else if (type === 'face') {
        this.draw(data);
    
      } else {
        console.error('Data invalid.');
      }
    }
    
文档内容是否对您有所帮助?
有帮助
没帮助