前言

某天某时刻从某位PLMM那里收到了一份礼物,拆开后发现是visionseed... 简单搜索了一下 发现是一个自带人脸检测/识别等一系列算法的摄像头.比较小巧

so...盒子背后还有个开发文档的二维码 visionseed官网 在开发者中心也能看到一些文档

简单看了一下后,发现...完全不懂啊 竟然是C++的..作为鄙视链最低端的前端,并且没有学过C++的渣渣来说 完全是天书啊,本打算辜负妹子的好意,准备收起设备的时候不料在gayhub上发现了新的天地
nodejs版本的sdk
竟然有位大大已经封装了一个js版的sdk,难道那句话就要应验了吗?能用js写的最终都会用js写

通信?

本着好奇的态度想看看底层如何与js通信,简单看了一下源文件发现引入了三个库

  • serialport: 读取串口信息 visionseed设备的baudRate为115200
  • google-protobuf: 端口通信机制? 这个库没太搞懂 类似于GRPC这样的?
    serialport正好之前玩过一点点 大致上明白是一个发送&接收的库,google-protobuf理解为对底层sdk通信的一个机制?

windows下的玩法

简单了解了一下后再去看看sdk文档..嗯...很简单嘛 window下打开cam就可以看到视频了,并且也绘制了人脸识别框balabala的

..嗯嗯嗯嗯...嗯? 调用sdk竟然是ubuntu版本的...虽然不知道js版的sdk是否支持window下直接调用,不过正好我有WSL神器,而且WSL也是装了ubuntu的 那么,是不是可以开启前端的愉快之路了?

WSL

首先要明白每个设备都有串口的,WSL中会自动映射到windows的串口如何映射串口 不看上面那篇文章也是可以的 因为windows默认就给你映射过去了

windows下可以在这里看到串口号,那么对应ubuntu的串口是 /dev/ttyS${串口号},我这里是COM3 对应的wsl设备号就是/dev/ttyS3 知道串口号后就可以根据jssdk进行连接了

let vs = new YtVisionSeed();
vs.registerOnResult( (msg) => {
  if(msg.result.facedetectionresult.faceList&&msg.result.facedetectionresult.faceList.length>0){
    // console.log('检测到人脸')
    myEmitter.emit('face',msg.result.facedetectionresult.faceList);
  }
  // console.log(msg)
})
const main = async function () {
  try {
    await vs.open(DIRPATH)
    console.log('device-id',await vs.getDeviceInfo())
    const dataLink = vs.datalink;
    const port = dataLink.port;
    myEmitter.on('registerFace',async ()=>{
      const res = await vs.registerFaceIdFromCamera('soul',5000)
      console.log('registerFace-res',res)
    })

    // console.log('Face in the lib:', await vs.listFaceId())

    // don't call close() if you want to receive registerOnResult callback
    // vs.close()
  } catch (e) {
    console.log('err',e)
  }
}
main()

这里的DIRPATH就是linux中的设备号,打开后就可以在vs.registerOnResult中接收到相关消息了, 嗯...一切都是这么的美好..但是...我想把视频流push到网页中啊!!离开了网页!前端还是前端么? so...如何获取视频流成了一个问题

获取视频流

在某个地方打了个断点

发现datalink下面有个port,这个port就是serialport读取到的信息.嗯..我是否可以从serialport中拿到相关的流呢?实现也比较简单,因为相关代码已经删除了 这里简单写一下

port.on('data',()=>{data.toString('base64')})

本以为data中传递的都是一帧一帧的图片流..我只需要转为base64然后前端直接显示即可..后来发现我抬年轻了.还是知识量不够啊, 发现转换出来的base64根本不是图片- -..后来进一步猜测 是否传递的是video流?于是用node-fluent-ffmpeg试了一下

var stream  = fs.createWriteStream('outputfile.divx');
ffmpeg(port)
  .output('outputfile.mp4')
  .output(stream);

结果试验出来的文件根本就打不开哦. 后来搜索了一下..发现WSL根本不支持摄像头..对 没看错 WSL不支持摄像头
so...没办法了 那就只能从windows下来获取视频流了

windows下的视频流

这块其实跟直播一样,利用FFmpeg来获取到相关的流 然后推送到服务器上即可
首先查看一下当前设备.\ffmpeg.exe -list_devices true -f dshow -i dummy

找到当前摄像头的名称为Tencent Youtu(R) VisionSeed 接着就可以开始推流了
推流的方法很多,简单一点的就是 推送到http服务器,由http服务器+ws发送图片帧给前端 前端显示
详情的可以参考这篇文章网络摄像机直播 写的非常好。我并不想搞那么复杂 只是想给某个PLMM看看成果而已, So,简单一点处理。利用jsmpeg这个库来处理就好了,
后端代码直接复制过来websocket-relay.js,本来打算用socket.io来实现的..结果发现jsmpeg好像不兼容socket.io 那么socket.io只能用来做其他的事情了。
简单搭建一下就完成了

推流&前端展示

当你搭建好了jsmpeg的后端websocket后 改一下下面的参数


var STREAM_SECRET = process.argv[2] || 'v1', // 安全码 对应你推流后面的路径后缀
	STREAM_PORT = process.argv[3] || 9988, // 对应的你的推送流端口
	WEBSOCKET_PORT = process.argv[4] || 9989, // 对应你的ws端口

完成了上面的简单处理后就可以开始推流了,执行下面的命令
.\ffmpeg.exe -f dshow -i video="Tencent Youtu(R) VisionSeed" -f mpegts -codec:v mpeg1video -codec:a mp2 -s 640x480 -r 32 http://localhost:9988/v1
推流的时候要注意只支持mpeg1相关的视频格式。

前端展示

	<canvas id="canvas"></canvas>
    <script type="text/javascript" src="jsmpeg.min.js"></script>
<script type="text/javascript">
	player = new JSMpeg.Player('ws://127.0.0.1:9989', {
	  canvas: document.getElementById('canvas') // Canvas should be a canvas DOM element
  })	
</script>

就这么简单,接着打开前端网页就展现出视频流来了。

人脸识别

上面说了一堆的视频相关的,还没到核心点就是人脸识别+注册,我想把人脸识别的信息实时推送给前端,所以还需要开启一个ws服务,这里我利用了socket.io来做业务相关的推送

人脸检测

vs.registerOnResult( (msg) => {
  if(msg.result.facedetectionresult.faceList&&msg.result.facedetectionresult.faceList.length>0){
    // console.log('检测到人脸')
    myEmitter.emit('face',msg.result.facedetectionresult.faceList);
  }
  // console.log(msg)
})

人脸检测也比较简单,直接判断msg.result.facedetectionresult.faceList即可,当然这个数组里还有很多属性,因为涉及到两个异步的模块推送 这里用了EventEmitter来做解耦。
socket.io相关代码

io.on('connection', function(socket){
  console.log('a user connected');

  myEmitter.on('face', (e) => {

    // console.log('触发事件人脸检测',e);
    const arr = e.map((n)=>{
      const _a = {
       
        "追踪id":n.traceid,
        "人脸可信度,>0.5可信":n.shape.confidence
      }
      if(n.name){
        _a['当前用户跟匹配库人脸识别度']=n.nameconfidence
        _a["检测到匹配库中的人脸"] = n.name
      }
      return _a
    })

    // 发送前端
    socket.emit('face',arr);

  });
  
  socket.on('registerFace',function(){
    console.log('注册人脸')
    myEmitter.emit('registerFace')
  })
});

解耦后 就可以拿到相关的值 返回给前端了

注册人脸
注册人脸有两种方法,一种是上传图片,一种是从摄像头获取,我这里偷懒用了摄像头获取

myEmitter.on('registerFace',async ()=>{
      const res = await vs.registerFaceIdFromCamera('soul',5000)
      console.log('registerFace-res',res)
    })

这里要注意的是...在注册人脸的时候ffmepg不能获取到推流..具体本渣渣也不知道什么原因,传递的第一个参数为注册人脸名,第二个参数为超时时间。 简单融合整理一下就实现了我想要的效果

结尾

感觉这个小玩意还能做很多的事情.我这里只是简单玩了一下基本的功能。第一次去了解硬件/视频相关的还是很有压力的,还好感谢文中出现的各位大佬提供了各种教程,尤其是提供js-sdk的大佬真的是给了我勇气去写代码~