uniapp人脸识别解决方案

news2024/11/16 15:35:14

APP端:
因为APP端无法使用uni的camera组件,最开始考虑使用内嵌webview的方式,通过原生dom调用video渲染画面然后通过canvas截图。但是此方案兼容性在ios几乎为0,如果app只考虑安卓端的话可以采用此方案。后面又想用live-pusher组件来实现,但是发现快照api好像需要真实流地址才能截取图像。因为种种原因,也是安卓ios双端兼容性不佳。最终决定采用5+api实现。经实测5+api兼容性还算可以,但是毕竟是调用原生能力,肯定是没有原生开发那么丝滑的,难免会出现一些不可预测的兼容性问题。所以建议app和手机硬件交互强的话还是不要用uni开发了。不然真的是翻文档能翻死人。社区也找不到靠谱的解决方案。

5+api 文档
https://www.html5plus.org/doc/zh_cn/video.html#plus.video.createLivePusher

就是使用这个api调用原生的camera完成。并且可以直接在预览模式下完成快照,也不需要真实的推流地址。

<template>
    <view class="content">
        <view class="c-footer">
            <view class="msg-box">
                <view class="msg">
                    1、请保证本人验证。
                </view>
                <view class="msg">
                    2、请使头像正面位于画框中。
                </view>
                <view class="msg">
                    3、请使头像尽量清晰。
                </view>
                <view class="msg">
                    4、请保证眼镜不反光,双眼可见。
                </view>
                <view class="msg">
                    5、请保证无墨镜,口罩,面膜等遮挡物。
                </view>
                <view class="msg">
                    6、请不要化浓妆,不要戴帽子。
                </view>
            </view>
            <view class="but" @click="snapshotPusher" v-if="!cilckSwitch">采集本人人脸</view>
        </view>
    </view>
</template>

<script>
    import permission from '../../util/permission.js'
    export default {
        data() {
            return {
                type: '', //是否是补签拉起的人脸识别
                imgData: '',
                pusher: null,
                scanWin: null,
                faceInitTimeout: null,
                snapshTimeout: null,
                uploadFileTask: null,
                cilckSwitch: false, //防止多次点击
            };
        },
        methods: {
            //人脸比对
            handleFaceContrast(param) {
                uni.hideLoading()
                this.$http({
                    url: '/API_AUTH/AppIaiFace/faceContrast.api',
                    data: {
                        ...param,
                        userid: uni.getStorageSync('userInfo').id
                    }
                }).then(res => {
                    console.log(res)
                    if (res.data.compareResult == 1) {
                        let pages = getCurrentPages(); //获取所有页面栈实例列表
                        let nowPage = pages[pages.length - 1]; //当前页页面实例
                        let prevPage = pages[pages.length - 2]; //上一页页面实例
                        if (this.type == 'signOut') {
                            prevPage.$vm.signOutXh = param.xh;
                            prevPage.$vm.signOutPhotoPath = param.path
                        } else {
                            prevPage.$vm.xh = param.xh;
                            prevPage.$vm.photoPath = param.path
                        }
                        uni.navigateBack()
                    } else {
                        uni.showToast({
                            title: '人脸比对不通过,请重试',
                            icon: 'none'
                        })
                        this.cilckSwitch = false
                    }
                }).catch(err => {
                    uni.showToast({
                        title: '人脸比对不通过,请重试',
                        icon: 'none'
                    })
                    this.cilckSwitch = false
                })
            },
            //初始化
             faceInit() {
                 uni.showLoading({
                    title: '请稍后...'
                 })
                this.faceInitTimeout = setTimeout(async () => {
                    //创建livepusher
                    if (uni.getSystemInfoSync().platform === 'android') {
                        const data1 = await permission.requestAndroidPermission(
                            "android.permission.RECORD_AUDIO")
                        const data2 = await permission.requestAndroidPermission("android.permission.CAMERA")
                        console.log(data1,data2,1111)
                        if (data1 == 1 && data2 == 1) {
                            this.pusherInit();
                        }
                    } else {
                        this.pusherInit();
                    }
                    //覆盖在视频之上的内容,根据实际情况编写
                    // this.scanWin = plus.webview.create('/hybrid/html/faceTip.html', '', {
                    //  background: 'transparent'
                    // });
                    //新引入的webView显示
                    // this.scanWin.show();
                    //初始化上传本地文件的api
                    this.initUploader()
                }, 500);
            },
            //初始化播放器
            pusherInit() {
                const currentWebview = this.$mp.page.$getAppWebview();
                this.pusher = plus.video.createLivePusher('livepusher', {
                    url: '',
                    top: '0px',
                    left: '0px',
                    width: '100%',
                    height: '50%',
                    position: 'absolute',
                    aspect: '9:16',
                    muted: false,
                    'z-index': 999999,
                    'border-radius': '50%',
                });
                currentWebview.append(this.pusher);
                //反转摄像头
                this.pusher.switchCamera();
                //开始预览
                this.pusher.preview();
                uni.hideLoading()
            },
            //初始化读取本地文件
            initUploader() {
                let that = this
                this.uploadFileTask = plus.uploader.createUpload(
                    "完整的接口请求地址", {
                        method: "POST",
                        headers: {
                            // 修改请求头Content-Type类型 此类型为文件上传
                            "Content-Type": "multipart/form-data"
                        }
                    },
                    // data:服务器返回的响应值 status: 网络请求状态码
                    (data, status) => {
                        // 请求上传文件成功
                        if (status == 200) {
                            console.log(data)
                            // 获取data.responseText之后根据自己的业务逻辑做处理
                            let result = JSON.parse(data.responseText);
                            console.log(result.data.xh)
                            that.handleFaceContrast({
                                xh: result.data.xh,
                                path: result.data.path
                            })
                        }
                        // 请求上传文件失败
                        else {
                            uni.showToast({
                                title: '上传图片失败',
                                icon: 'none'
                            })
                            console.log("上传失败", status)
                            that.cilckSwitch = false
                            uni.hideLoading()
                        }
                    }
                );
            },
            //快照
            snapshotPusher() {
                if (this.cilckSwitch) {
                    uni.showToast({
                        title: '请勿频繁点击',
                        icon: 'none'
                    })
                    return
                }
                this.cilckSwitch = true
                uni.showLoading({
                    title: '正在比对,请勿退出'
                })
                let that = this
                this.snapshTimeout = setTimeout(() => {
                    this.pusher.snapshot(
                        e => {
                            // this.pusher.close();
                            // this.scanWin.hide();
                            //拿到本地文件路径
                            var src = e.tempImagePath;
                            this.uploadImg(src)
                            //获取图片base64
                            // this.getMinImage(src);
                        },
                        function(e) {
                            plus.nativeUI.alert('snapshot error: ' + JSON.stringify(e));
                            that.cilckSwitch = false
                            uni.hideLoading()
                        }
                    );
                }, 500);
            },
            //调用原生能力读取本地文件并上传
            uploadImg(imgPath) {
                this.uploadFileTask.addFile('file://' + imgPath, {
                    key: "file" // 填入图片文件对应的字段名
                });
                //添加其他表单字段(参数) 两个参数可能都只支持传字符串
                // uploadFileTask.addData("参数名", 参数值);
                this.uploadFileTask.start();
            },
            //获取图片base64
            getMinImage(imgPath) {
                plus.zip.compressImage({
                        src: imgPath,
                        dst: imgPath,
                        overwrite: true,
                        quality: 40
                    },
                    zipRes => {
                        setTimeout(() => {
                            var reader = new plus.io.FileReader();
                            reader.onloadend = res => {
                                var speech = res.target.result; //base64图片
                                console.log(speech.length);
                                console.log(speech)
                                this.imgData = speech;
                            };
                            reader.readAsDataURL(plus.io.convertLocalFileSystemURL(zipRes.target));
                        }, 1000);
                    },
                    function(error) {
                        console.log('Compress error!', error);
                    }
                );
            },
        },
        onLoad(option) {
            //#ifdef APP-PLUS
            this.type = option.type
            this.faceInit();
            
            //#endif
        },
        onHide() {
            console.log('hide')
            this.faceInitTimeout && clearTimeout(this.faceInitTimeout);
            this.snapshTimeout && clearTimeout(this.snapshTimeout);
            // this.scanWin.hide();
        },
        onBackPress() {
            // let pages = getCurrentPages(); //获取所有页面栈实例列表
            // let nowPage = pages[pages.length - 1]; //当前页页面实例
            // let prevPage = pages[pages.length - 2]; //上一页页面实例
            // prevPage.$vm.xh = '11111';
            // prevPage.$vm.photoPath = '22222' 
            this.faceInitTimeout && clearTimeout(this.faceInitTimeout);
            this.snapshTimeout && clearTimeout(this.snapshTimeout);
            // this.scanWin.hide();
        },
        onUnload() {
            this.faceInitTimeout && clearTimeout(this.faceInitTimeout);
            this.snapshTimeout && clearTimeout(this.snapshTimeout);
            // this.scanWin.hide();
        },
    };
</script>

<style lang="scss" scoped>
    .but {
        margin: 50rpx auto 0;
        border-radius: 10px;
        width: 80%;
        height: 100rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: #008AFF;
        font-size: 16px;
        color: #FFFFFF;
    }

    .c-footer {

        width: 100%;
        position: fixed;
        top: 50%;
        left: 0;
        z-index: 10;
        padding: 30rpx 0;

        .msg-box {
            width: 80%;
            margin: 0 auto;
            text-align: left;

            .msg {
                margin-bottom: 15rpx;
                font-size: 13px;
                color: #666;
            }
        }
    }

    .img-data {
        width: 100%;
        height: auto;
    }

    .content {
        background-color: #fff;

    }
</style>

 以上是完整的包含逻辑的代码。

关键代码
//初始化播放器
            pusherInit() {
                const currentWebview = this.$mp.page.$getAppWebview();
                this.pusher = plus.video.createLivePusher('livepusher', {
                    url: '',
                    top: '0px',
                    left: '0px',
                    width: '100%',
                    height: '50%',
                    position: 'absolute',
                    aspect: '9:16',
                    muted: false,
                    'z-index': 999999,
                    'border-radius': '50%',
                });
                currentWebview.append(this.pusher);
                //反转摄像头
                this.pusher.switchCamera();
                //开始预览
                this.pusher.preview();
                uni.hideLoading()
            },

 //快照
            snapshotPusher() {
                if (this.cilckSwitch) {
                    uni.showToast({
                        title: '请勿频繁点击',
                        icon: 'none'
                    })
                    return
                }
                this.cilckSwitch = true
                uni.showLoading({
                    title: '正在比对,请勿退出'
                })
                let that = this
                this.snapshTimeout = setTimeout(() => {
                    this.pusher.snapshot(
                        e => { 
                            //拿到本地文件路径
                            var src = e.tempImagePath;
  //这里因为接口参数需要加密,用base64的话加密出来的参数太大了,所以选择了直接读取本地文件上传文件流的方式。
                            this.uploadImg(src)
                            //获取图片base64
                            // this.getMinImage(src);
                        },
                        function(e) {
                            plus.nativeUI.alert('snapshot error: ' + JSON.stringify(e));
                            that.cilckSwitch = false
                            uni.hideLoading()
                        }
                    );
                }, 500);
            },
            //获取图片base64
            getMinImage(imgPath) {
                plus.zip.compressImage({
                        src: imgPath,
                        dst: imgPath,
                        overwrite: true,
                        quality: 40
                    },
                    zipRes => {
                        setTimeout(() => {
                            var reader = new plus.io.FileReader();
                            reader.onloadend = res => {
                                var speech = res.target.result; //base64图片
                                console.log(speech.length);
                                console.log(speech)
                                this.imgData = speech;
                            };
                            reader.readAsDataURL(plus.io.convertLocalFileSystemURL(zipRes.target));
                        }, 1000);
                    },
                    function(error) {
                        console.log('Compress error!', error);
                    }
                );
            },
            //初始化读取本地文件
            initUploader() {
                let that = this
                this.uploadFileTask = plus.uploader.createUpload(
                    "完整的接口请求地址", {
                        method: "POST",
                        headers: {
                            // 修改请求头Content-Type类型 此类型为文件上传
                            "Content-Type": "multipart/form-data"
                        }
                    },
                    // data:服务器返回的响应值 status: 网络请求状态码
                    (data, status) => {
                        // 请求上传文件成功
                        if (status == 200) {
                            console.log(data)
                            // 获取data.responseText之后根据自己的业务逻辑做处理
                            let result = JSON.parse(data.responseText);
                            console.log(result.data.xh)
                            that.handleFaceContrast({
                                xh: result.data.xh,
                                path: result.data.path
                            })
                        }
                        // 请求上传文件失败
                        else {
                            uni.showToast({
                                title: '上传图片失败',
                                icon: 'none'
                            })
                            console.log("上传失败", status)
                            that.cilckSwitch = false
                            uni.hideLoading()
                        }
                    }
                );
            },
     //调用原生能力读取本地文件并上传
            uploadImg(imgPath) {
                this.uploadFileTask.addFile('file://' + imgPath, {
                    key: "file" // 填入图片文件对应的字段名
                });
                //添加其他表单字段(参数) 两个参数可能都只支持传字符串
                // uploadFileTask.addData("参数名", 参数值);
                this.uploadFileTask.start();
            },

以上就是关键的代码。
接下来补充几个坑的地方。创建出来的livepusher层级很高,无法在同一页面被别的元素遮挡。所以想要在他上面写样式是行不通了。只能再创建一个webview。然后将这个webview覆盖在livepusher上,达到人脸识别页面的样式。

//覆盖在视频之上的内容,根据实际情况编写
                     this.scanWin = plus.webview.create('/hybrid/html/faceTip.html', '', {
                      background: 'transparent'
                     });
                    //新引入的webView显示
                   this.scanWin.show();
                   //新引入的webView影藏
                     this.scanWin.hide();

这种方案在ios基本没问题。至少目前没遇到过。但是安卓就一言难尽了。首先这个组件默认调起的是后置摄像头,这显然不符合我们的需求。但是官方提供的文档里也没有明确支持可以配置优先调起哪个摄像头。好早提供了一个switchCamera的api可以翻转摄像头。

但是在安卓系统上,尤其是鸿蒙系统,调用这个api就会导致程序闪退,而且发生频率还特别高。这个问题至今不知道该怎么解决。除了闪退问题,安卓还存在一个麻烦事儿,那就是首次进入app,翻转摄像头的api没有用,拉起的还是后置摄像头。但是后续再进入app就无此问题了。后面折腾来折腾去,发现好像是首次进入拉起授权弹窗的时候才会出现这种问题。
然后写了个定时器做测试,五秒之后再拉起摄像头再去翻转摄像头。然后再五秒内赶紧把授权给同意了。结果发现翻转竟然生效了。
然后决定再渲染推流元素之前先让用户通过权限授权,然后再拉起摄像头。 也就是上文完整代码中的

 

 //创建livepusher
                    if (uni.getSystemInfoSync().platform === 'android') {
                        const data1 = await permission.requestAndroidPermission(
                            "android.permission.RECORD_AUDIO")
                        const data2 = await permission.requestAndroidPermission("android.permission.CAMERA")
                        console.log(data1,data2,1111)
                        if (data1 == 1 && data2 == 1) {
                            this.pusherInit();
                        }
                    } else {
                        this.pusherInit();
                    }

具体的意思就不过多赘述了,自行看permission的文档。或者看他的代码。很简单
permission下载地址
https://ext.dcloud.net.cn/plugin?id=594
以上就是调用原生能力拉起摄像头实现快照功能的所有内容了。

下面也记录一下web端如果实现这种功能,毕竟当时搞出来也不容易,但是最终还是败在了兼容性上

方案的话大致有两种,一种是借助tracking js 有兴趣的可以了解一下,一个web端人脸识别库。他可以识别画面中是否出现人脸。以及一下更高级的功能我就没有去探索了。有需要的可以自行研究

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>人脸识别</title>
        <script src="../html/js/tracking-min.js"></script>
        <script src="../html/js/face-min.js"></script>
        <style>
            video,
            canvas {
                position: absolute;
            }
        </style>
    </head>
    <body>
        <div class="demo-container">
            <video id="video" width="320" height="240" preload autoplay loop muted></video>
            <canvas id="canvas" width="320" height="240"></canvas>
        </div>
        <script>
            window.onload = function() {
                console.log(123123123)
                // 视频显示
                var video = document.getElementById('video');
                //   绘图
                var canvas = document.getElementById('canvas');
                var context = canvas.getContext('2d');
                var time = 10000;
                var tracker = new tracking.ObjectTracker('face');
                //   设置识别的放大比例
                tracker.setInitialScale(4);
                //   设置步长
                tracker.setStepSize(2);
                //   边缘密度
                tracker.setEdgesDensity(0.1);
                //   启动摄像头,并且识别视频内容
                var trackerTask = tracking.track('#video', tracker, {
                    camera: true
                });
                var flag = true;
                tracker.on('track', function(event) {
                    if (event.data.length === 0) {
                        console.log('未检测到人脸')
                        context.clearRect(0, 0, canvas.width, canvas.height);
                    } else if (event.data.length > 1) { 
                        console.log('检测到多张人脸')
                    } else {
                        context.clearRect(0, 0, canvas.width, canvas.height);
                        event.data.forEach(function(rect) {
                            context.strokeStyle = '#ff0000';
                            context.strokeRect(rect.x, rect.y, rect.width, rect.height);
                            context.fillStyle = "#ff0000";
                            //console.log(rect.x, rect.width, rect.y, rect.height);
                        });
                        if (flag) {
                            console.log("拍照");
                            context.drawImage(video, 0, 0, 320, 240);
                            saveAsLocalImage();
                            context.clearRect(0, 0, canvas.width, canvas.height);
                            flag = false;
                            setTimeout(function() {
                                flag = true;
                            }, time);
                        } else {
                            //console.log("冷却中");
                        }
                    }
                });
            };

            function saveAsLocalImage() {
                var myCanvas = document.getElementById("canvas");
                // here is the most important part because if you dont replace you will get a DOM 18 exception.  
                // var image = myCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream;Content-Disposition: attachment;filename=foobar.png");  
                var image = myCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
                // window.location.href = image; // it will save locally  
                // create temporary link  
                return
                var tmpLink = document.createElement('a');
                tmpLink.download = 'image.png'; // set the name of the download file 
                tmpLink.href = image;

                // temporarily add link to body and initiate the download  
                document.body.appendChild(tmpLink);
                tmpLink.click();
                document.body.removeChild(tmpLink);
            }
        </script>

    </body>

</html>


 另外一种就是纯video+canvas截取一张视频中的画面。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>人脸采集</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" type="text/css" href="./css/index.css" />
        <script src="./js/jq.js" type="text/javascript" charset="utf-8"></script>
        <!-- uni 的 SDK -->
        <script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js">
        </script>
        <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.0.0/vconsole.min.js" type="text/javascript"
            charset="utf-8"></script>
        <script>
            // init vConsole,app中查看
            var vConsole = new VConsole();
            // console.log('Hello world');
        </script>
        <style>
            .mui-content {
                margin: 0 auto;
                text-align: center;
                border: 0px solid red;
            }
            /*摄像头翻转180度*/
            video {
                transform: rotateY(180deg);
                -webkit-transform: rotateY(180deg);
                /* Safari 和 Chrome */
                -moz-transform: rotateY(180deg);
            }
        </style>
    </head>
    <body class='body'>
        <div class="mui-content">
            <div style="margin: 40px auto;">
                <!-- <input type="file" id='image' accept="image/*" capture='user'> -->
                <video id="video" style="margin: 0 auto; border-radius: 150px;"></video>
                <canvas id='canvas' width="300" height="300"
                    style="border: 0px solid red;margin: auto; display: none;"></canvas>
            </div>
            <div class="msg-box">
                <div class="msg">
                    1、请保证本人验证。
                </div>
                <div class="msg">
                    2、请使头像正面位于画框中。
                </div>
                <div class="msg">
                    3、请使头像尽量清晰。
                </div>
                <div class="msg">
                    4、请保证眼镜不反光,双眼可见。
                </div>
                <div class="msg">
                    5、请保证无墨镜,口罩,面膜等遮挡物。
                </div>
                <div class="msg">
                    6、请不要化浓妆,不要戴帽子。
                </div>
            </div>
            <div style="width: 80%; position: absolute; bottom: 20px; left: 50%; transform: translate(-50%, -50%);">
                <div class="but" id="start">采集本人人脸</div>
            </div>
        </div>
    </body>
    <script type="text/javascript">
        var video, canvas, vendorUrl, interval, videoHeight, videoWidth, time = 0;
        // 获取webview页面数据
        // var data = JSON.parse(getUrlParam('data'))
        // var info = data.info;
        // var userInfo = data.userInfo;
        // const userId = data.userId; // 当前登录用户id
        $(function() {
            // 初始化
            initVideo()
            // uni.app事件
            document.addEventListener('UniAppJSBridgeReady', function() {
                uni.getEnv(function(res) {
                    console.log('当前环境:' + JSON.stringify(res));
                });
                setInterval(() => {
                    uni.postMessage({
                        data: {
                            action: 'postMessage'
                        }
                    });
                }, 1000)
            });
        })
        // 获取url携带的数据
        function getUrlParam(name) {
            var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
            var r = window.location.search.substr(1).match(reg);
            if (r != null) return unescape(r[2]);
            return null;
        }
        // 摄像头初始化
        function initVideo() {
            console.log('摄像头初始化')
            video = document.getElementById("video");
            videoHeight = 300
            videoWidth = 300
            setTimeout(() => {
                console.log(navigator)
                navigator.mediaDevices
                    .getUserMedia({
                        video: {
                            width: {
                                ideal: videoWidth,
                                max: videoWidth
                            },
                            height: {
                                ideal: videoHeight,
                                max: videoHeight
                            },
                            facingMode: 'user', //前置摄像头
                            // facingMode: { exact: "environment" }, //后置摄像头
                            frameRate: {
                                ideal: 30,
                                min: 10
                            }
                        }
                    })
                    .then(videoSuccess)
                    .catch(videoError);
                if (
                    navigator.mediaDevices.getUserMedia ||
                    navigator.getUserMedia ||
                    navigator.webkitGetUserMedia ||
                    navigator.mozGetUserMedia ||
                    navigator.mediaCapabilities
                ) {
                    console.log('调用用户媒体设备, 访问摄像头')
                    //调用用户媒体设备, 访问摄像头
                    getUserMedia({
                            video: {
                                width: {
                                    ideal: videoWidth,
                                    max: videoWidth
                                },
                                height: {
                                    ideal: videoHeight,
                                    max: videoHeight
                                },
                                facingMode: 'user', //前置摄像头
                                // facingMode: { exact: "environment" }, //后置摄像头
                                frameRate: {
                                    ideal: 30,
                                    min: 10
                                }
                            }
                        },
                        videoSuccess,
                        videoError
                    );
                } else {}
            }, 300);
        }
        // 获取用户设备
        function getUserMedia(constraints, success, error) {
            if (navigator.mediaDevices.getUserMedia) {
                //最新的标准API
                navigator.mediaDevices
                    .getUserMedia(constraints)
                    .then(success)
                    .catch(error);
            } else if (navigator.webkitGetUserMedia) {
                //webkit核心浏览器
                navigator.webkitGetUserMedia(constraints, success, error);
            } else if (navigator.mozGetUserMedia) {
                //firfox浏览器
                navigator.mozGetUserMedia(constraints, success, error);
            } else if (navigator.getUserMedia) {
                //旧版API
                navigator.getUserMedia(constraints, success, error);
            }
        }
        // 开始有画面
        function videoSuccess(stream) {
            //this.mediaStreamTrack = stream;
            console.log("=====stream")
            video.srcObject = stream;
            video.play();
            //$("#max-bg").css('background-color', 'rgba(0,0,0,0)')
            // 这里处理我的的东西
        }

        function videoError(error) {
            alert('摄像头获取错误')
            console.log('摄像头获取错误')
            setTimeout(() => {
                initVideo()
            }, 6000)
        }
        // 单次拍照
        function getFaceImgBase64() {
            canvas = document.getElementById('canvas');
            //绘制canvas图形
            canvas.getContext('2d').drawImage(video, 0, 0, 300, 300);
            //把canvas图像转为img图片
            var bdata = canvas.toDataURL("image/jpeg");
            //img.src = canvas.toDataURL("image/png");
            return bdata.split(',')[1]; //照片压缩成base位数据
        }
        $('#start').on('click', function() {
            time = 0;
            console.log("开始人脸采集,请正对屏幕");
            faceGather();
        })
        // 人脸采集
        function faceGather() {
            const faceImgBase64 = getFaceImgBase64();
            console.log(faceImgBase64);
        }
    </script>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/410552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】vi编辑器——插入模式

目录 插入模式 光标移动命令 a A i I o O命令 a----------------在光标后附加文本 A----------------在文本行末附加文本 i------------------在光标前插入文本 I-------------------在文本开始插入文本 o----------------在光标下插入新行 O---------------在…

陶泓达:4.11黄金原油白银最新走势分析及操作策略!

黄金行情走势分析&#xff1a;   周一(4月10日)&#xff0c;国际金价一度跌约1%&#xff0c;重回2000美元干口下方。此前公布的美国3月非农就业数据显示劳动力市场吃紧&#xff0c;并提高了美联储5月再次加息的预期。金属公司MKS PAMP在一份报告中表示&#xff1a;“金价自20…

Doris(5):数据导入(Load)之Broker load

为适配不同的数据导入需求&#xff0c;Doris系统提供了五种不同的数据导入方式&#xff0c;每种数据导入方式支持不同的数据源&#xff0c;存在不同的方式&#xff08;异步&#xff0c;同步&#xff09; Broker load 通过Broker进程访问并读取外部数据源&#xff08;HDFS&#…

elasticsearch之DSL查询文档

目录 DSL查询分类 全文检索查询 match查询&#xff1a; multi_match&#xff1a; 精准查询 地理坐标查询 矩形范围查询 中心点范围 组合查询 elasticsearch中的相关性打分算法是什么&#xff1f; 案例——给“如家”这个品牌的酒店排名靠前一些 function score query…

ChatGPT写新闻-ChatGPT写文章

ChatGPT写新闻 ChatGPT可以用于生成新闻稿件&#xff0c;但需要注意的是&#xff0c;由ChatGPT生成的新闻稿件可能存在语义、逻辑、事实准确性等方面的问题&#xff0c;因此需要进行人工审核和编辑&#xff0c;确保其准确性。 下面是一个示例过程&#xff0c;大致了解如何使用…

GPT3.5 , InstructGPT和ChatGPT的关系

GPT-3.5 GPT-3.5 系列是一系列模型&#xff0c;从 2021 年第四季度开始就使用文本和代一起进行训练。以下模型属于 GPT-3.5 系列&#xff1a; code-davinci-002 是一个基础模型&#xff0c;非常适合纯代码完成任务text-davinci-002 是一个基于 code-davinci-002 的 InstructG…

PixiJS 文字模糊处理策略

pixijs介绍 PixiJS是一个用于创建交互式2D图形和动画的JavaScript库。它是一个快速、轻量级、易于使用的库&#xff0c;可以在WebGL和Canvas上运行。支持WebGL和Canvas两种渲染方式。WebGL是一种基于OpenGL的3D图形API&#xff0c;可以在GPU上进行高效的图形渲染。Canvas是一种…

基于神经辐射场NeRF的SLAM方法

随着2020年NeRF[1]的横空出世&#xff0c;神经辐射场方法&#xff08;Neural Radiance Fields&#xff09;如雨后春笋般铺天盖地卷来。NeRF最初用来进行图像渲染&#xff0c;即给定相机视角&#xff0c;渲染出该视角下的图像。NeRF是建立在已有相机位姿的情况下&#xff0c;但在…

Android kotlin (仿淘宝app收藏)用RecyclerView(androidx+BRVAH3.0.6)实现单选/多选/全选/反选/批量删除功能

文章目录 一、实现效果二、引入依赖三、实现源码1、实体类2、适配器3、提示框(关闭和确定)4、视图实现一、实现效果 二、引入依赖 在app的build.gradle在添加以下代码 implementation com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0

使用采集工具,轻松获取目标受众的数据,让您的市场营销更加精准

【数据采集神器】使用采集工具&#xff0c;轻松获取目标受众的数据&#xff0c;让您的市场营销更加精准&#xff01; 在当前这个信息化社会中&#xff0c;数据已经成为了企业发展和市场营销的必要手段。企业需要通过数据来了解市场的需求&#xff0c;了解自己产品的竞争优势&a…

你真的懂docker吗?25个试题,学过你就来

前言&#xff1a;遇到不会的&#xff0c;答案偷偷放在评论区了哦~ 1.什么是Docker A 虚拟机 B 半虚拟化技术 C 开源的应用容器引擎 D 后端软件 2. 如何搜索Nginx镜像 A docker search nginx B docker rm nginx C doc…

剑指 Offer II 024. 反转链表(经典题型)

时间是伟大的作者&#xff0c;她能写出未来的结局。 ——卓别林 目录 题目描述&#xff1a; 方法1&#xff1a;迭代法&#xff08;翻指针&#xff09; 方法2&#xff1a;头插法 方法3&#xff1a;递归法 题目描述&#xff1a; 给定单链…

【工作感悟】老程序员总结的四条工作经验教训

文章目录前言1. 不要做小需求2. 要做大需求3. 定期同步工作进度4. 项目结束&#xff0c;主动复盘总结前言 想来从事互联网工作已经很多年了&#xff0c;已经从当初的懵懂少年逐渐退化成老油条。刚毕业的时候&#xff0c;真是个愣头青&#xff0c;什么都不懂&#xff0c;也什么…

SegGPT: Segmenting Everything In Context

目录摘要本文方法上下文着色Context EnsembleIn-Context Tuning消融实验摘要 最近基于大规模数据的模型越来越火了&#xff0c;chat GPT以及seg everything&#xff0c;感觉后面很多像目标检测&#xff0c;图像恢复等等都会出现这种泛化能力强&#xff0c;基于大规模数据的模型…

[2019.01.24]JNI经验积累

[1 jobject<--->jclass|jstring](1)jobject向上转型jclass|jstring:jclass jcls static_cast<jclass>(jobject);jstring jstr static_cast<jclass>(jobject);(2)jclass|jstring向下转型jobject:默认情况下是自动转换的[2 jstring<--->const char*](1…

c++string相关内容

1. string基本概念本质:string是C风格的字符串&#xff0c;而string本质上是一个类string和 char *区别:char * 是一个指针string 是一个类&#xff0c;类内部封装了char *&#xff0c;管理这个字符串&#xff0c;是一个char*型的容器 特点:string类内部封装了很多成员方法例如…

当下的网络安全行业前景到底怎么样?还能否入行?

前言网络安全现在是朝阳行业&#xff0c;缺口是很大。不过网络安全行业就是需要技术很多的人达不到企业要求才导致人才缺口大常听到很多人不知道学习网络安全能做什么&#xff0c;发展前景好吗&#xff1f;今天我就在这里给大家介绍一下。网络安全作为目前比较火的朝阳行业&…

又一恐怖技能!卡耐基梅隆大学发布超强智能体,炸翻科研圈

文 | 小戏一名普通博士生的工作日常是什么&#xff1f;上网查查资料&#xff1f;读读文献&#xff1f;根据各种完善工具的 API 或者文档写两行代码&#xff0c;然后再输给实验机器完成高精度的实验&#xff1f;仔细思考一下我们这些所谓“科研工作者”的工作流&#xff0c;却细…

如何使用ChatGPT API 及 问题解决

如何使用ChatGPT API 及 问题解决1, 注册OpenAI账号2&#xff0c;获取OpenAI的API秘钥3&#xff0c;pip安装openai库3.1 pip安装openai库3.2 pip安装错误4&#xff0c;Chatgpt API连接测试4.1 Chatgpt API连接测试4.2 连接测试错误1, 注册OpenAI账号 关于注册账号&#xff0c;…

订单超时自动取消3种方案

文章目录 1.定时任务2.被动取消3.延时消息大家对电商购物应该都比较熟悉了,我们应该注意到,在下单之后,通常会有一个倒计时,如果超过支付时间,订单就会被自动取消。 今天,我们来聊聊订单超时未支付自动取消的几种方案。 1.定时任务 这是最容易想到的办法,定时任务去轮…