Web编程大作业(六)桌面直播+人脸识别+总结(ffmpeg,jsmpeg,jquery.facedetection)



桌面直播

只能算是一个基于ffmpeg和jsmpeg的应用。

首先需要安装的内容有:ffmpeg、jsmpeg、ws模块

安装过程reference:https://my.oschina.net/chengpengvb/blog/1832469 (ffmpeg建议在官网装最新的可执行版本)

直接把下载下来的jsmpeg的代码引入网站后端

var STREAM_SECRET = 'supersecret',
    STREAM_PORT = 8081,
    WEBSOCKET_PORT = 8082,
    RECORD_STREAM = false;

// Websocket Server
var socketServer = new WebSocket.Server({port: WEBSOCKET_PORT, perMessageDeflate: false});
socketServer.connectionCount = 0;
socketServer.on('connection', function(socket, upgradeReq) {
    socketServer.connectionCount++;
    console.log(
        'New WebSocket Connection: ',
        (upgradeReq || socket.upgradeReq).socket.remoteAddress,
        (upgradeReq || socket.upgradeReq).headers['user-agent'],
        '('+socketServer.connectionCount+' total)'
    );
    socket.on('close', function(code, message){
        socketServer.connectionCount--;
        console.log(
            'Disconnected WebSocket ('+socketServer.connectionCount+' total)'
        );
    });
});
socketServer.broadcast = function(data) {
    socketServer.clients.forEach(function each(client) {
        if (client.readyState === WebSocket.OPEN) {
            client.send(data);
        }
    });
};

// HTTP Server to accept incomming MPEG-TS Stream from ffmpeg
var streamServer = http.createServer( function(request, response) {
    var params = request.url.substr(1).split('/');

    if (params[0] !== STREAM_SECRET) {
        console.log(
            'Failed Stream Connection: '+ request.socket.remoteAddress + ':' +
            request.socket.remotePort + ' - wrong secret.'
        );
        response.end();
    }

    response.connection.setTimeout(0);
    console.log(
        'Stream Connected: ' +
        request.socket.remoteAddress + ':' +
        request.socket.remotePort
    );
    request.on('data', function(data){
        socketServer.broadcast(data);
        if (request.socket.recording) {
            request.socket.recording.write(data);
        }
    });
    request.on('end',function(){
        console.log('close');
        if (request.socket.recording) {
            request.socket.recording.close();
        }
    });

    // Record the stream to a local file?
    if (RECORD_STREAM) {
        var path = 'recordings/' + Date.now() + '.ts';
        request.socket.recording = fs.createWriteStream(path);
    }
})
// Keep the socket open for streaming
streamServer.headersTimeout = 0;
streamServer.listen(STREAM_PORT);

这段代码大概就是创建了用于接收视频流的服务器和推送视频流的websocket服务器。

把一些需要输入的参数直接给出定值。也就是把接受视频流端口设为8081,把ws服务器端口设为8082,密码设为supersecret。

然后教师端先要安装ffmpeg,通过ffmpeg把桌面视频流推送至8081
推流的ffmpeg命令参数如下:(参数含义基本可以从录制信息里面反推出来,比如分辨率和码率之类的,虽然很多还是不太理解但是能用就行)

ffmpeg -f gdigrab -framerate 30 -i desktop -f mpegts -codec:v mpeg1video -s 2560x1440 -b:v 150k -r 30 -bf 0 -ac 1 -b:a 128k http://127.0.0.1:8081/supersecret

(需要注意将最后的ip地址改为网站所在的ip地址)

学生端通过8082端口获取视频流。(在一个html页面中实现)

<!DOCTYPE html>
<html>
<head>
    <title>JSMpeg Stream Client</title>
    <style type="text/css">
        html, body {
            background-color: #111;
            text-align: center;
        }
        .return_index{
            color:white;
        }
    </style>

</head>
<body>
    <canvas id="video-canvas"></canvas>
    <script type="text/javascript" src="/jsmpeg-master/jsmpeg.min.js"></script>
    <script type="text/javascript">
        var canvas = document.getElementById('video-canvas');
        var url = 'ws://'+window.location.hostname+':8082/';
        var player = new JSMpeg.Player(url, {canvas: canvas});
    </script>
    <a class='return_index' href='/'>返回首页</a>
</body>
</html>

然后把这个页面作为“直播间”做到自己的网站里就算是实现了。
在这里插入图片描述
在这里插入图片描述

人脸识别

网上绝大多数人脸识别功能的教程都是用OpenCV+python来实现的。找了半天终于发现一个比较简单而且js能用的插件:jquery.facedetection。不过功能确实也相对比较简单。

通过npm install jquery.facedetection就能完成安装。通过script标签引入。

<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="node_modules/jquery.facedetection/src/ccv.js"></script>
<script src="node_modules/jquery.facedetection/src/jquery.facedetection.js"></script>
<script src="node_modules/jquery.facedetection/src/cascade.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        html,body{
            margin: 0;
            padding:0;
        }
        .drawDiv{
            position: absolute;
            border: 3px solid yellow;
        }
        #image{
            float: left;
        }
        .imgDiv{
            float: left;
        }
    </style>
</head>

<body>
    <img id="image" src=""/>
    <div class="imgDiv">

        <div class="draw"></div>
        <br/>
        <input type="button" value="开始识别" onclick="identifyFace()">
        <input type="file"onchange="selectImage(this);" />
    </div>
    <a class='return_index' href='/'>返回首页</a>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script src="jquery.facedetection/src/ccv.js"></script>
    <script src="jquery.facedetection/src/jquery.facedetection.js"></script>
    <script src="jquery.facedetection/src/cascade.js"></script>
    <script>
        //识别框样式
        var str='';
        //上传图片,使用文件流
        function selectImage(file){
            if(!file.files || !file.files[0]){
                return;
            }
            var reader = new FileReader();
            reader.onload = function(evt){
                console.log(evt);
                $('#image').attr('src', evt.target.result);
            }
            str = '';
            document.getElementsByClassName('draw')[0].innerHTML = '';
            reader.readAsDataURL(file.files[0]);
        }

        //开始识别
        function identifyFace() {
            str='';
            $('#image').faceDetection(
                function (faces) {
                    for (var i in faces) {
                        //识别结果循环传入方法drawFace
                        console.log(faces[i]);
                        drawFace(faces[i].x, faces[i].y, faces[i].width, faces[i].height,faces[i].confidence);
                    }
                }
            );
        }
        //图片识别区的x,y轴以及宽高,confidence表示自信程度
        function drawFace(x,y,width,height,confidence){
            var confidenceStr='';
            if(confidence<0){
                confidenceStr='自信满满'
            }else if(confidence>2){
                confidenceStr='很不自信啊'
            }else{
                confidenceStr='一般般'
            }
            //将框框套上去
            str+='<div class="drawDiv" style="left:'+x+'px;top:'+y+'px;width:'+width+'px;height:'+height+'px;">'+confidenceStr+'</div>'
            document.getElementsByClassName('draw')[0].innerHTML=str
        }
    </script>
</body>
</html>

效果大概是这样
在这里插入图片描述
看一下识别后的对象属性
在这里插入图片描述
除了“脸部区域”的x坐标、y坐标和长宽这些比较基本的信息之外,比较有意思的是还有一个confidence属性来描述这张脸的自信程度。。然后根据自信程度可以给出一个简单的评价,比如“自信”、“一般般”、“不自信”(这个准确度有多少就不知道啦)

有了这个之后也可以把它做进网站里面,但是很明显这个插件只能检测到图像中“是否有人脸”,但不能识别出“这张脸是谁”,所以离实现“人脸识别签到”这个功能肯定还是有距离的。

另一个问题是如何用js控制树莓派的摄像头模块。同样,网上的博客教程基本都是用python实现的。。用js实现的我也没找到。。于是我只能想到一个笨办法,就是让树莓派摄像头定时拍摄,将图片存到一个指定的地址,让网页读取固定地址的图片进行识别。

raspistill在终端控制摄像头 2秒拍摄一次(时间设置一个很大的数,让它一直拍)

raspistill -o Web_v5/public/images/now.jpg -tl 2000 -t 9999999999

然而这个最后实现的效果。。有点一言难尽。首先是树莓派拍出来照片大小的问题,一张照片大概2-3M,做一次识别要花很长时间,其次虽然照片占的空间很大,但是清晰度又很差,导致很难识别出人脸(这个可能是因为没有把摄像头固定起来导致的)。最后摄像头稍微拍了一分多钟就有点开始发烫了。。应该是我让它拍的太频繁的问题。

总之这个功能实现的比较失败吧。。以后真的要做人脸识别类似的功能可能还是用OpenCV+python之类的比较好,回头研究了。。

总结

代码

以上就是这个项目的全部内容。。之前总结的比较多,后面做的这些感觉也没啥好总结的。。

前半部分从无到有,自己用express和angular写框架和前后端交互的时候花的力气比较多,应该收获也是比较大的。最后做的几个要求比如视频推流和人脸识别这些就稍显敷衍,毕竟以前也没有接触过,主要就是想把这些功能都尝试一下。

一个感觉就是,整个项目做下来之后在计算机网络方面的知识确实是学到很多,但是好像对于javascript代码本身还是掌握的比较差,比方说如果拿到一个新的模块,基本上只能通过别人的代码来了解具体的用法、写法,很难通过官方文档来直接理解并构建代码。。(说白了就是只能做做增删改查,但是对底层原理一头雾水吧)