百度人脸识别算法包含有在线算法和本地离线算法,同时也涵盖了多种语言:有C#、C++、Android。现在就带大家来认识一下,百度人脸识别算法(C#版本)是个什么样子的。

准备工作

先注册百度帐号,并登录
SDK完整详细文档:https://ai.baidu.com/sdk
SDK在线版本文档:https://ai.baidu.com/ai-doc/FACE/Ck37c1ri0
SDK离线版本文档:https://ai.baidu.com/ai-doc/FACE/4k37c1n7e

在线版本

  1. 进入控制台,创建新的应用,获取授权 API Key , Secret Key ,地址:https://console.bce.baidu.com/ai/#/ai/face/app/list
  2. 安装在线依赖库。可通过页面下载或nuget安装【在NuGet中搜索 Baidu.AI,安装最新版即可】,推荐使用nuget安装。

  3. 安装好后,我们可以在C#代码中,编写以下调用代码:
    //在线调用
    private JObject MatchFace(byte[] image2)
    {
        //初始化百度人脸在线sdk
        var face = new Baidu.Aip.Face.Face("API Key", "Secret Key");
        //读取一张需要对比的本地图片
        var image1 = File.ReadAllBytes("bp.jpg");

        var faces = new JArray
            {
              new JObject
                {
                    {"image",Convert.ToBase64String(image1)},
                    {"image_type", "BASE64"},
                    {"face_type", "LIVE"},
                    {"quality_control", "LOW"},
                    {"liveness_control", "NONE"},
                },
              new JObject
                {
                    {"image", Convert.ToBase64String(image2)},
                    {"image_type", "BASE64"},
                    {"face_type", "LIVE"},
                    {"quality_control", "LOW"},
                    {"liveness_control", "NONE"},
                }
            };

        var result = face.Match(faces);
        return result;
    }

通过以上代码就可以完成最简单的人脸比对功能了。

注意:百度的算法依赖Newtonsoft.Json.dll,如果本地也存在此dll,需要注意版本冲突!

在线版本其它功能都有详细的文档介绍,此处不做过多介绍了,主要了解使用线上版本的先决步骤和注意事项。

离线版本

离线版本相比在线版本会更复杂,离线版本需要先在官网上下载相关的SDK,约580MB。
下载后解压压缩包,使用visual studio 2015以上版本打开。
函数的入口在Face.cs文件中,可根据示例代码进行自行学习。 自己对代码稍微整理了下,请大家享用(其它代码,有需要请邮箱留言):

    public class BaiduFaceApi
    {
        const string STR_BAIDU_API = "BaiduFaceApi.dll";

        /// <summary>
        /// 初始化人脸sdk
        /// </summary>
        /// <param name="id_card"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "sdk_init", CharSet = CharSet.Ansi
             , CallingConvention = CallingConvention.Cdecl)]
        public static extern int sdk_init(bool id_card);

        /// <summary>
        /// sdk销毁
        /// </summary>
        [DllImport(STR_BAIDU_API, EntryPoint = "sdk_destroy", CharSet = CharSet.Ansi
             , CallingConvention = CallingConvention.Cdecl)]
        public static extern void sdk_destroy();

        /// <summary>
        /// 是否授权
        /// </summary>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "is_auth", CharSet = CharSet.Ansi
                , CallingConvention = CallingConvention.Cdecl)]
        public static extern bool is_auth();

        /// <summary>
        /// 获取设备指纹
        /// </summary>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "get_device_id", CharSet = CharSet.Ansi
                 , CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr get_device_id();

        /// <summary>
        /// 人脸跟踪
        /// </summary>
        /// <param name="oface"></param>
        /// <param name="mat"></param>
        /// <param name="max_track_num"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "track_mat", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern int track_mat(IntPtr oface, IntPtr mat, ref int max_track_num);

        /// <summary>
        /// 跟踪最大人脸
        /// </summary>
        /// <param name="file_name"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "track_max_face", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr track_max_face(string file_name);

        /// <summary>
        /// 通过二进制图片跟踪人脸
        /// </summary>
        /// <param name="image"></param>
        /// <param name="size"></param>
        /// <param name="max_track_num"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "track_by_buf", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr track_by_buf(byte[] image, int size, int max_track_num);

        /// <summary>
        /// 清除人脸跟踪
        /// </summary>
        [DllImport(STR_BAIDU_API, EntryPoint = "clear_tracked_faces", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern void clear_tracked_faces();

        /// <summary>
        /// 跟踪人脸
        /// </summary>
        /// <param name="file_name"></param>
        /// <param name="max_track_num"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "track", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr track(string file_name, int max_track_num);

        /// <summary>
        /// 跟踪最大人脸
        /// </summary>
        /// <param name="image"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "track_max_face_by_buf", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr track_max_face_by_buf(byte[] image, int size);


        /// <summary>
        /// 人脸1:1比对(传二进制图片buffer)
        /// </summary>
        /// <param name="buf1"></param>
        /// <param name="size1"></param>
        /// <param name="buf2"></param>
        /// <param name="size2"></param>
        /// <returns></returns>
        [DllImport(STR_BAIDU_API, EntryPoint = "match_by_buf", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr match_by_buf(byte[] buf1, int size1, byte[] buf2, int size2);

        /// <summary>
        /// 设置人脸最小尺寸
        /// </summary>
        /// <param name="size"></param>
        [DllImport("BaiduFaceApi.dll", EntryPoint = "set_min_face_size", CharSet = CharSet.Ansi
        , CallingConvention = CallingConvention.Cdecl)]
        public static extern void set_min_face_size(int size = 30);

        [DllImport("BaiduFaceApi.dll", EntryPoint = "rgb_ir_liveness_check_by_buf", CharSet = CharSet.Ansi
    , CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr rgb_ir_liveness_check_by_buf(byte[] rgb_buf, int rgb_size,
     byte[] ir_buf, int ir_size);

        // 单目RGB静默活体检测(传入图片文件二进制buffer)
        [DllImport("BaiduFaceApi.dll", EntryPoint = "rgb_liveness_check_by_buf", CharSet = CharSet.Ansi
           , CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr rgb_liveness_check_by_buf(byte[] buf, int size);
    }

打开摄像头并用WPF显示的方法:

OpenCvSharp.Extensions.BitmapConverter 类需要反编译出来,百度算法自带的没有包含。

        private void StartCamera()
        {
            Task.Run(() =>
            {
                while (PlayState.Starting == State)
                {
                    try
                    {
                        var curCamera = Cv2.CreateFrameSource_Camera(0);
                        Mat curFrame=new Mat();
                        System.Drawing.Bitmap bitmap;
                        State = PlayState.Playing;
                        while (PlayState.Stoped != State)
                        {
                            curCamera.NextFrame(curFrame);
                            //设置摄像头的画面镜像显示,以保证人物移动方向和视频流中相同
                            //除此方法还有更强大的方法,是使用Remap.该方法功能更强大,同时也更耗性能
                            curFrame=curFrame.Flip(FlipMode.Y);

                            bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(curFrame);                            

                            Dispatcher.Invoke(() => img.Source = ImageUtil.BitmapToBitmapImage(bitmap));
                        }

                        curCamera.Dispose();
                    }
                    catch (Exception)
                    {
                        Thread.Sleep(1000);
                    }
                }
            });
        }

        public enum PlayState
        {
            Starting,
            Playing,
            Pause,
            Stoped
        }

remap的参考实现

        public static void TestRemap()
        {
            int index = 0;
            Mat src = new Mat(path, ImreadModes.AnyColor | ImreadModes.AnyDepth);
            Mat dst = new Mat(src.Size(), src.Type());
            Mat x = new Mat(src.Rows, src.Cols, MatType.CV_32FC1);
            Mat y = new Mat(src.Rows, src.Cols, MatType.CV_32FC1);
            //new OpenCvSharp.Window("Input", WindowMode.AutoSize, src);
            int key = 0;
            while (true)
            {
                key = Cv2.WaitKey(500);
                index = key % 4;
                if ((char)key == 27)
                {
                    break;
                }
                Console.WriteLine("{0}%{1}={2}", key, 4, index);
                UpdateRemap(src, x, y, 0);
                Cv2.Remap(src, dst, x, y, InterpolationFlags.Linear, BorderTypes.Constant, new Scalar(0, 0, 255));
                //new OpenCvSharp.Window("Output", WindowMode.AutoSize, dst);
            }
        }

        public static void UpdateRemap(Mat src, Mat x, Mat y, int flipMode)
        {
            for (int row = 0; row < src.Rows; row++)
            {
                for (int col = 0; col < src.Cols; col++)
                {
                    switch (flipMode)
                    {
                        case 0:
                            if (col > (src.Cols * 0.25) && col < (src.Cols * 0.75) && row > (src.Rows * 0.25) && row < (src.Rows * 0.75))
                            {
                                /*
                                 * 图像的 Cols 列 组成矩阵的宽度 对应 二维平面的x轴
                                 * 图像的 Rows 行 组成矩阵的高度 对应 二维平面的y轴
                                 * if 条件成立必定满足  
                                 *          src.Cols*0.75 <= col <= src.Cols*0.25
                                 *          src.Rows*0.75 <= row <= src.Rows*0.25
                                 * 则:
                                 *  (col - (src.Cols * 0.25)范围在 0 -- 0.5 之间
                                 *   若 (2 * (col - (src.Cols * 0.25)) 则:范围在 0 --1 之间 (原图像的大小)X轴
                                 *   (row - (src.Rows * 0.25))范围在 0 -- 0.5 之间
                                 *   若 (2 * (row - (src.Rows * 0.25)) 则:范围在 0 --1 之间 (原图像的大小)Y轴
                                 *   
                                 *   前面创建 x ,y 对象是的类型是 CV_32FC1 所以 赋值要用 float 类型
                                 */
                                float xVal = (float)(2 * (col - (src.Cols * 0.25)));
                                float yVal = (float)(2 * (row - (src.Rows * 0.25)));
                                x.Set<float>(row, col, xVal);
                                y.Set<float>(row, col, yVal);
                            }
                            else
                            {
                                x.Set<float>(row, col, 0);
                                y.Set<float>(row, col, 0);
                            }
                            break;
                        case 1:
                            x.Set<float>(row, col, (src.Cols - col - 1)); //左右映射  X 轴
                            y.Set<float>(row, col, row); //Y 轴不变
                            break;
                        case 2:
                            x.Set<float>(row, col, col); //X 轴
                            y.Set<float>(row, col, (src.Rows - row - 1)); //上下映射 Y轴
                            break;
                        case 3:
                            x.Set<float>(row, col, (src.Cols - col - 1)); //左右映射  X 轴
                            y.Set<float>(row, col, (src.Rows - row - 1));//上下映射 Y轴
                            break;
                    }
                }
            }
        }

划重点:百度SDK在使用的过程中会遇到很多问题,影响我们项目推进。为了防止大家入坑

  1. 使用百度算法之前,必须要先注册,否则SDK无法正常使用。注册方法只需要打开启动目录下的LicenseTool.exe文件,输入授权码。授权成功后,会生成两个授权文件:license.inilicense.key。授权是绑定物理机器,如果换另外一台机器,授权码将失效。授权过程中,如果遇到授权失败,则多点几次重试即可。
  2. face-resource 资源包,一定要放在程序运行的上一级目录,否则会导致SDK初始化失败,也就是返回值为-1
  3. 人脸识别算法在调用的过程中,我们可能会发现错误返回,抽取人脸特征数据失败,这种情况多数因为 人脸识别的人脸大小过大,把人脸size设置更小就可以了(调用set_min_face_size方法进行设置),默认值是100。具体参数需要根据实际设定。
  4. 百度算法依赖opencv相关的库,我们在打开摄像头的过程中,也需要使用opencv的相关功能。为了避免冲突,我们需要从baidu sdk目录下的common文件夹中,把里面的文件拷贝到启动目录下,否则会出现版本冲突导致的编译错误。
  5. 程序启动时,会出现无法定位程序输入点 SfmDxGetSwapChainStats 于动态链接库 xxx d3d9.dll的错误,只需要将d3d9.dll改成d3nv.dll即可
  6. opencv相关的扩展方法,百度SDK所使用的版本过低,不包含该扩展方法,需要自行反编译出来加入使用,需要开启启用不安全代码
  7. 人脸识别率会受装饰,头发等影响,使用过程中尽可能避免脸部遮挡,画浓妆等,推荐阈值80
  8. 调用人脸接口,可能会出现返回的指针为空指针,那么这种情况很有可能是因为百度SDKDispose

持续更新……


本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/%E7%99%BE%E5%BA%A6%E4%BA%BA%E8%84%B8%E5%85%A5%E9%97%A8.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系