为什么80%的码农都做不了架构师?>>>   

前后端的耦合想了很久,上下课都在思考怎么做,然后终于憋出来了。这是之前搞的一个视觉计算的项目,boss叫对接到前端,于是就产生了这样一个诡异的需求,就是前端打开摄像头,同时需要把摄像头的数据回传到后端进行图像处理(比如美颜啊脑袋上加个装饰品之类),这就需要涉及到前端和服务端的数据编码耦合,想了想既然任何图像在内存里面都是一个uchar矩阵,于是琢磨了这个东西出来。

一般情况下,图像在内存里的表达都是个uchar串,或者说byte流,因为我经常需要写跨语言调用的玩意儿,所以一般在内存里我都是用字符串和比特流进行交互,这里我采用了同样的思想,我们把opencv的图像进行编码为png,然后再一次编码为base64,通过websocket传输给前端。大致过程如下。

首先假设我们的前端打开websocket连接后端,连接上了以后前端打开摄像头取摄像头数据传输给后端,后端通过一系列的图像处理机器学习以后编码图像回传给前端。

前端代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<video id="video" style="display: none" width="480" height="320" controls></video><canvas id="canvas" width="480" height="320"></canvas>
<img id="target" width="480" height="320"></img>
<script>var video = document.getElementById('video');var canvas = document.getElementById('canvas');var image = document.getElementById('target');var context = canvas.getContext('2d');var ws = new WebSocket("ws://127.0.0.1:9002");ws.binaryType = "arraybuffer";ws.onopen = function() {ws.send("I'm client");};ws.onmessage = function (evt) {console.log("resive");try{//显示后端回传回来的base64图像image.src="https://img-blog.csdnimg.cn/2022010707385143216.png"+evt.data;console.log(evt.data);}catch{}};ws.onclose = function() {alert("Closed");};ws.onerror = function(err) {alert("Error: " + err);};function getUserMedia(constraints, success, error) {if (navigator.mediaDevices.getUserMedia) {navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);}}//成功回调函数function success(stream){video.srcObject=stream;video.play();}function error(error) {console.log('访问用户媒体失败:',error.name,error.message);}//这个函数是实现将canvas上面的base64图像转为图像数据流的字符串形式function dataURItoBlob(dataURI) {// convert base64/URLEncoded data component to raw binary data held in a stringvar byteString;if (dataURI.split(',')[0].indexOf('base64') >= 0)byteString = atob(dataURI.split(',')[1]);elsebyteString = unescape(dataURI.split(',')[1]);// separate out the mime componentvar mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];// write the bytes of the string to a typed arrayvar ia = new Uint8Array(byteString.length);for (var i = 0; i < byteString.length; i++) {ia[i] = byteString.charCodeAt(i);}return new Blob([ia], {type:mimeString});}if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {//调用用户媒体设备, 访问摄像头getUserMedia({video: {width: 480, height: 320}}, success, error);timer = setInterval(function () {context.drawImage(video,0,0,480,320);var data = canvas.toDataURL('image/jpeg', 1.0);newblob = dataURItoBlob(data);//将转换好成为字符串的图像数据发送出去ws.send(newblob);}, 100);//这里我们的前端还是需要延时的,如果我们的后端计算实时性不是很强的话,而恰好我的项目后端计算规模非常大,所以需要50ms的等待} else {alert('不支持访问用户媒体');}</script>
</body>
</html>

C++服务器端(这里需要使用到websocket++读者请自行编译)

opencv_websocket_server.h

//
// Created by Pulsar on 2019/4/16.
//#ifndef WEBSOCKETPP_OPENCV_WEBSOCKET_H
#define WEBSOCKETPP_OPENCV_WEBSOCKET_H#include <opencv2/opencv.hpp>#include <boost/thread/thread.hpp>
//#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>typedef websocketpp::server<websocketpp::config::asio> WebsocketServer;
typedef WebsocketServer::message_ptr message_ptr;class opencv_websocket {
public:opencv_websocket(std::string file_path)  ;void Run(int port);~opencv_websocket();
};#endif //WEBSOCKETPP_OPENCV_WEBSOCKET_H

opencv_websocket_server.cpp

//
// Created by Pulsar on 2019/4/16.
//#include <opencv_websocket.h>
//using websocketpp::lib::placeholders::_1;
//using websocketpp::lib::placeholders::_2;
//using websocketpp::lib::bind;
boost::shared_mutex  read_write_mutex;
boost::mutex lock;
cv::CascadeClassifier cascade;
//解码base64数据
static std::string base64Decode(const char *Data, int DataByte) {//解码表const char DecodeTable[] ={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,62, // '+'0, 0, 0,63, // '/'52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'0, 0, 0, 0, 0, 0, 0,0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'0, 0, 0, 0, 0, 0,26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'};std::string strDecode;int nValue;int i = 0;while (i < DataByte) {if (*Data != '\r' && *Data != '\n') {nValue = DecodeTable[*Data++] << 18;nValue += DecodeTable[*Data++] << 12;strDecode += (nValue & 0x00FF0000) >> 16;if (*Data != '=') {nValue += DecodeTable[*Data++] << 6;strDecode += (nValue & 0x0000FF00) >> 8;if (*Data != '=') {nValue += DecodeTable[*Data++];strDecode += nValue & 0x000000FF;}}i += 4;} else {Data++;i++;}}return strDecode;
}//编码base64数据
static std::string base64Encode(const unsigned char *Data, int DataByte) {//编码表const char EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//返回值std::string strEncode;unsigned char Tmp[4] = {0};int LineLength = 0;for (int i = 0; i < (int) (DataByte / 3); i++) {Tmp[1] = *Data++;Tmp[2] = *Data++;Tmp[3] = *Data++;strEncode += EncodeTable[Tmp[1] >> 2];strEncode += EncodeTable[((Tmp[1] << 4) | (Tmp[2] >> 4)) & 0x3F];strEncode += EncodeTable[((Tmp[2] << 2) | (Tmp[3] >> 6)) & 0x3F];strEncode += EncodeTable[Tmp[3] & 0x3F];if (LineLength += 4, LineLength == 76) {strEncode += "\r\n";LineLength = 0;}}//对剩余数据进行编码int Mod = DataByte % 3;if (Mod == 1) {Tmp[1] = *Data++;strEncode += EncodeTable[(Tmp[1] & 0xFC) >> 2];strEncode += EncodeTable[((Tmp[1] & 0x03) << 4)];strEncode += "==";} else if (Mod == 2) {Tmp[1] = *Data++;Tmp[2] = *Data++;strEncode += EncodeTable[(Tmp[1] & 0xFC) >> 2];strEncode += EncodeTable[((Tmp[1] & 0x03) << 4) | ((Tmp[2] & 0xF0) >> 4)];strEncode += EncodeTable[((Tmp[2] & 0x0F) << 2)];strEncode += "=";}return strEncode;
}//imgType 包括png bmp jpg jpeg等opencv能够进行编码解码的文件
static std::string Mat2Base64(const cv::Mat &img, std::string imgType) {//Mat转base64std::string img_data;std::vector<uchar> vecImg;std::vector<int> vecCompression_params;vecCompression_params.push_back(CV_IMWRITE_JPEG_QUALITY);vecCompression_params.push_back(90);imgType = "." + imgType;//重点来了,它是负责把图像从opencv的Mat变成编码好的图像比特流的重要函数cv::imencode(imgType, img, vecImg, vecCompression_params);img_data = base64Encode(vecImg.data(), vecImg.size());return img_data;
}//base64转Mat
static cv::Mat Base2Mat(std::string &base64_data) {cv::Mat img;std::string s_mat;s_mat = base64Decode(base64_data.data(), base64_data.size());std::vector<char> base64_img(s_mat.begin(), s_mat.end());img = cv::imdecode(base64_img, CV_LOAD_IMAGE_COLOR);return img;
}void OnOpen(WebsocketServer *server, websocketpp::connection_hdl hdl) {std::cout << "have client connected" << std::endl;
}void OnClose(WebsocketServer *server, websocketpp::connection_hdl hdl) {std::cout << "have client disconnected" << std::endl;
}void OnMessage(WebsocketServer *server, websocketpp::connection_hdl hdl, message_ptr msg) {std::string image_str = msg->get_payload();std::vector<char> img_vec(image_str.begin(), image_str.end());try {//把前端传来的图像字符串进行解码cv::Mat img = cv::imdecode(img_vec, CV_LOAD_IMAGE_COLOR);if (!img.empty()) {
//            cv::imshow("", img);std::vector<cv::Rect> faces;lock.lock();
//            cascade.detectMultiScale(img, faces, 1.1, 3, 0, cv::Size(30, 30));
//            for (size_t t = 0; t < faces.size(); t++){
//                cv::rectangle(img, faces[t], cv::Scalar(0, 0, 255), 2, 8);
//            }lock.unlock();cv::Mat output = img;if (!output.empty()) {//把你处理完的图像转换为字符串返回给前端std::string strRespon = Mat2Base64(output, "bmp");server->send(hdl, strRespon, websocketpp::frame::opcode::text);}
//            cv::waitKey(10);}}catch (const std::exception &) {std::cout << " 解码异常" << std::endl;}
}opencv_websocket::opencv_websocket(std::string file_path) {//训练好的文件名称,放置在可执行文件同目录下if(!cascade.load(file_path))perror("Load Model Error");
}opencv_websocket::~opencv_websocket() {}void opencv_websocket::Run(int port) {WebsocketServer server;server.set_access_channels(websocketpp::log::alevel::all);server.clear_access_channels(websocketpp::log::alevel::frame_payload);// Initialize Asioserver.init_asio();// Register our message handlerserver.set_open_handler(websocketpp::lib::bind(&OnOpen, &server, ::websocketpp::lib::placeholders::_1));server.set_close_handler(websocketpp::lib::bind(&OnClose, &server, websocketpp::lib::placeholders::_1));server.set_message_handler(websocketpp::lib::bind(OnMessage, &server, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2));// Listen on port 9002server.listen(port);// Start the server accept loopserver.start_accept();// Start the ASIO io_service run loopserver.run();
}int main(int argc, char **argv) {std::cout<<"[INFO] load model"<<std::endl;opencv_websocket opencv_websocket_server("haarcascade_frontalface_alt.xml");std::cout<<"[INFO] start server"<<std::endl;opencv_websocket_server.Run(9002);std::cout<<"[INFO] listen"<<std::endl;getchar();return 0;
}

上述工程地址:

https://gitee.com/Luciferearth/websocketpp
example\opencv_websocket_server下

注意websocket在Windows下需要改动编译依赖

去掉

iostream_server

testee_server

testee_client

utility_client

的Cmake(直接全部注释)

CmakeLists.txt

set (WEBSOCKETPP_LIB ${WEBSOCKETPP_BUILD_ROOT}/lib)

后面加入以下编译命令

#########################################OpenSSL#######################################
set(OPENSSL_INCLUDE_DIR D:/pgsql/include)
set(OPENSSL_LIBRARIES D:/pgsql/lib/ssleay32MD.lib;D:/pgsql/lib/libeay32MD.lib)
#######################################################################################
##########################Windows 下对Boost的引用######################################
set(BUILD_EXAMPLES ON)
set(BUILD_EXAMPLES ON)set(Boost_FOUND TRUE)
set(Boost_INCLUDE_DIRS E:/local/boost_1_67_0)
set(Boost_INCLUDE_DIR E:/local/boost_1_67_0)
set(Boost_LIBRARY_DIRS E:/local/boost_1_67_0/lib64-msvc-14.0 )
set(Boost_LIBRARIESboost_filesystem-vc140-mt-x64-1_67.libboost_filesystem-vc140-mt-gd-x64-1_67.liblibboost_zlib-vc140-mt-gd-x64-1_67.liblibboost_zlib-vc140-mt-x64-1_67.libboost_system-vc140-mt-gd-x64-1_67.libboost_system-vc140-mt-x64-1_67.liblibboost_chrono-vc140-mt-s-x64-1_67.liblibboost_chrono-vc140-mt-gd-x64-1_67.libboost_thread-vc140-mt-gd-x64-1_67.libboost_thread-vc140-mt-x64-1_67.lib)
###################################################

opencv-server

file (GLOB SOURCE_FILES *.cpp)
file (GLOB HEADER_FILES *.hpp)set(OPENCV_INCLUDE_DIR F:/Smart_Classroom/3rdparty/ALLPLATHFORM/opencv/include)
message(${OPENCV_INCLUDE_DIR})
set(OPENCV_LIB_DIR F:/Smart_Classroom/3rdparty/ALLPLATHFORM/opencv/x64/vc14/lib)
message(${OPENCV_LIB_DIR})
include_directories(${OPENCV_INCLUDE_DIR})
link_directories(${OPENCV_LIB_DIR})
init_target (opencv_websocket_server)build_executable (${TARGET_NAME} ${SOURCE_FILES} ${HEADER_FILES})
file(COPY haarcascade_frontalface_alt.xml DESTINATION ${CMAKE_BINARY_DIR}/bin/)
#
link_boost ()
final_target ()
target_link_libraries(opencv_websocket_serveropencv_world341.libopencv_world341d.lib)
#
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "examples")

代码难免在打字的时候打错,有什么问题联系笔者。整个服务端的实现难点无非在于编码与解码的方法保持客户端和服务端数据耦合性,这个东西也琢磨了我好几天才琢磨透,再接再厉把,io真的是一个神奇的东西,当你把它深刻的理解到内存的时候,它就像个听话的孩子。

转载于:https://my.oschina.net/VenusV/blog/3030001

websocket传输canvas图像数据给C++服务端opencv图像实现web在线实时图像处理相关推荐

  1. netty websocket客户端_Websocket操作字节序 之 服务端

    Websocket在JavaScript中操作字节序 之 客户端 在上一篇文章中,把页面的websocket编码写好了,那么服务端又该如何实现呢?由于该文是在上上篇demo中修改的,所以不全的代码还请 ...

  2. websocket测试工具,支持ws wss服务端和客户端

    介绍一个名为:WebsocketMan的websocket测试工具,支持ws wss服务端和客户端.可以将请求保存为文件,支持header非常方便测试.支持Windows Linux macOS系统. ...

  3. springboot实现SSE服务端主动向客户端推送数据,java服务端向客户端推送数据,kotlin模拟客户端向服务端推送数据

    SSE服务端推送 服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(以下简称 SSE).本文介绍它的用法. 在很多业务场景中,会涉及到服务端向客户端 ...

  4. STM32F407传输OV2640视频数据并在PC端显示

    一.整体架构 STM32使用DCMI驱动OV2640,DMA通道获取图像数据,然后通过以太网将数据发送至PC端,PC端通过socket接收数据,并用BufferedImage将其显示.(PC端使用的j ...

  5. WebSocket 从入门到精通 -- Spring boot服务端客户端 -- HTML客户端

    注意:学习本文章一定要打开自己的开发工具,代码中有详细的解释.电脑不在身边建议先收藏,方便日后观看.最后祝大家技术突飞猛进,早日拿到心仪的offer. WebSocket -- 从入门到精通 基础讲解 ...

  6. 分块传输的请求次数与客户端/服务端工作过程

    一 分块传输是每一个分块一次请求还是所有分块就一次请求 http传输永远是一个请求一个响应的工作模式,只是响应是chunked分块,body数据不是一次性发过来的,而是分批分块发送,但仍然是在一个报文 ...

  7. Socket通信 客户端加密数据,传递数据密文到服务端,服务端解密密文 输出明文

    server // sdf_cpp_warpper.cpp : 此文件包含 "main" 函数.程序执行将在此处开始并结束. // server端#ifndef UNICODE # ...

  8. 用C语言写一个通信软件,客户端可以实现文字聊天,文件传输,建立群聊;服务端可以创建账号吧...

    首先,需要为客户端和服务端分别编写代码,客户端的代码应该包括实现文字聊天.文件传输和建立群聊的功能,而服务端的代码则需要实现创建账号的功能.需要考虑到网络的安全性.客户端的用户体验以及服务端的可扩展性 ...

  9. SharePoint 2013 APP 开发示例 (六)服务端跨域访问 Web Service (REST API)

    上个示例(SharePoint 2013 APP 开发示例 (五)跨域访问 Web Service (REST API))是基于JavaScript,运行在web browser内去访问REST AP ...

最新文章

  1. 2021年大数据Kafka(十一):❤️Kafka的消费者负载均衡机制和数据积压问题❤️
  2. Flash Builder 4快速入门----学习笔记
  3. 什么是服务质量(QoS) ?—Vecloud微云
  4. python word 1_Python word | 学步园
  5. Ubuntu20.04下面运行applet与freemind部署到web上(调研+找到替换方案)
  6. C++ 多继承 初步01
  7. ODBC、OLEDB、ADO的区别和联系
  8. 机器视觉及图像处理系列之二(C++,VS2015)——图像级的人脸识别(1)
  9. 克服浮躁_建立强大的全球社区时克服挑战
  10. javascript实现页面滚屏效果
  11. 【转】JMeter学习(二十八)内存溢出解决方法
  12. 【异常(待解决)】org.apache.http.NoHttpResponseException: api.weixin.qq.com:443 failed to respond
  13. apache通过AD验证
  14. C++调用C#编写的com组件方法
  15. python struct pack_Python(12)教程11.标准库简介——第二部分
  16. 大学计算机计算思维期末试题及答案,大学计算机计算思维导论期末考试
  17. linux 安装pkg文件,Linux的pkg-config命令
  18. 冰尘社补丁php,300英雄冰尘社盒子ios
  19. 【历史上的今天】6 月 30 日:冯·诺依曼发表第一份草案;九十年代末的半导体大战;CBS 收购 CNET
  20. android hud软件,手机HUD靠谱软件选哪些?_手机_手机生活应用-中关村在线

热门文章

  1. 水准网测量平差matlab_【干货】史诗级测量大神分享道路测量全过程经验
  2. sql科学计数法转换为普通数字_一张图读懂Python3的Number(数字)类型
  3. html距离已过去多久,用javascript写的倒计时,从某年某月距离到今天还有多少时间...
  4. java ftp 下载慢_Java实现ftp文件上传下载解决慢中文乱码多个文件下载等问题
  5. python中函数修饰符_python中的函数修饰符
  6. 赋值后页面不渲染_第七节:框架搭建之页面静态化的剖析
  7. 【杂谈】如何使用有三AI生态学习计算机视觉和自然语言处理等内容
  8. 【杂谈】为什么有三AI自断财路,从来不接广告
  9. 【分割模型解读】感受野与分辨率的控制术—空洞卷积
  10. 全球及中国益生菌市场应用发展与投资前景调研报告2022版