如果想要在浏览器运行代码,能使用的语言只有JavaScript ,因为在浏览器里配置了JavaScript 引擎用来负责解析和执行JavaScript 。但是,很多时候JavaScript并不适合我们想要实现的所有功能,比如想在浏览器运动C++、C或Rust等代码,那么WebAssembly则是目前最佳的选择。
WebAssembly(缩写WASM)是浏览器所能执行的一种新类型的代码,它的目标是为了在网络上获得更好的性能。 WASM是一种低层的二进制格式代码,体积小,因此加载和执行速度快。而且不用直接编写WASM的代码,它可以从高级语言(比如C++、C、Rust)编译而来。比如当前DAPP(mora星球)所在的ICP链也是把motoko或rust编写的智能合约编译成WebAssembly之后安装到Canister运行。
WebAssembly 在 web 中被设计成无版本、特性可测试、向后兼容的。WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web环境下。emscripten可以将C/C++代码可以通过编译为 webassembly。
计算机视觉给人类生活带很多方便,但也有很多数据安全方面的问题,比如应用在安防上的网络摄像头,当摄像头检测到的画面要回传到服务器才能检测出当面视频流属于什么状态。举个例子,应用于智慧城市的安防摄像头,它要检测当前画面是否有人在禁止吸烟的区域吸烟了,工人进入工地时是否做了安全防护措拖,是否有起烟或者火灾的现象,如果要把图像时时回传服务器才做检测,这样可能会引起数据安全隐私问题。
这里使用WebAssembly做推理就可以解决实现在浏览器就把结果检测出来,只要把结果给传递给处理中心就可以了,用户数据不会上传,保护用户隐私。部署起来也简单,只要安装浏览器就行,无客户端。
那么现在可以把一个目标识别的算法部署到浏览器上运行试试,当前代码所使用的语言为C++,目标检测算法是YOLOV5,用到图像算法库OpenCV,推理加速库是NCNN。
当前测试的环境为Mac,浏览器为google chrome,首先要安装emscripten用来编译C++代码。
1.安装emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install 2.0.8
./emsdk activate 2.0.8
source emsdk/emsdk_env.sh
2.下载NCNN库
wget https://github.com/Tencent/ncnn/releases/download/20220216/ncnn-20220216-webassembly.zip
unzip ncnn-20220216-webassembly.zip
1.工程资源
工程使用的模型是yolov5的模型,原模型是onnx格式的,要用使用ncnn转换成ncnn模型,关于模型训练和模型转换,可以参考ncnn的git和yolov5的git。
2.工程后端代码
yolov5.h
```cpp
#ifndef YOLOV5_H
#define YOLOV5_H
#include
#include
struct Object
{
cv::Rect_<float> rect;
int label;
float prob;
};
class YOLOv5
{
public:
YOLOv5();
int load();
int detect(const cv::Mat& rgba, std::vector<Object>& objects, float prob_threshold = 0.4f, float nms_threshold = 0.5f);
int draw(cv::Mat& rgba, const std::vector<Object>& objects);
private:
ncnn::Net yolov5;
};
#endif // YOLOV5_H
yolov5.cpp
```cpp
#include "yolov5.h"
#include
#include
#include
static inline float intersection_area(const Object& a, const Object& b)
{
cv::Rect_ inter = a.rect & b.rect;
return inter.area();
}
static void qsort_descent_inplace(std::vector
运行效果:
4.交互代码
```cpp
#include
#include
#include "yolov5.h"
static int draw_fps(cv::Mat& rgba)
{
// resolve moving average
float avg_fps = 0.f;
{
static double t0 = 0.f;
static float fps_history[10] = {0.f};
double t1 = ncnn::get_current_time();
if (t0 == 0.f)
{
t0 = t1;
return 0;
}
float fps = 1000.f / (t1 - t0);
t0 = t1;
for (int i = 9; i >= 1; i--)
{
fps_history[i] = fps_history[i - 1];
}
fps_history[0] = fps;
if (fps_history[9] == 0.f)
{
return 0;
}
for (int i = 0; i < 10; i++)
{
avg_fps += fps_history[i];
}
avg_fps /= 10.f;
}
char text[32];
sprintf(text, "FPS=%.2f", avg_fps);
int baseLine = 0;
cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
int y = 0;
int x = rgba.cols - label_size.width;
cv::rectangle(rgba, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),
cv::Scalar(255, 255, 255, 255), -1);
cv::putText(rgba, text, cv::Point(x, y + label_size.height),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0, 255));
return 0;
}
static YOLOv5* g_yolov5 = 0;
static void on_image_render(cv::Mat& rgba)
{
if (!g_yolov5)
{
g_yolov5 = new YOLOv5;
g_yolov5->load();
}
std::vector<Object> objects;
g_yolov5->detect(rgba, objects);
g_yolov5->draw(rgba, objects);
draw_fps(rgba);
}
#ifdef __EMSCRIPTEN_PTHREADS__
static const unsigned char* rgba_data = 0;
static int w = 0;
static int h = 0;
static ncnn::Mutex lock;
static ncnn::ConditionVariable condition;
static ncnn::Mutex finish_lock;
static ncnn::ConditionVariable finish_condition;
static void worker()
{
while (1)
{
lock.lock();
while (rgba_data == 0)
{
condition.wait(lock);
}
cv::Mat rgba(h, w, CV_8UC4, (void*)rgba_data);
on_image_render(rgba);
rgba_data = 0;
lock.unlock();
finish_lock.lock();
finish_condition.signal();
finish_lock.unlock();
}
}
#include
static std::thread t(worker);
extern "C" {
void yolov5_ncnn(unsigned char* _rgba_data, int _w, int _h)
{
lock.lock();
while (rgba_data != 0)
{
condition.wait(lock);
}
rgba_data = _rgba_data;
w = _w;
h = _h;
lock.unlock();
condition.signal();
// wait for finished
finish_lock.lock();
while (rgba_data != 0)
{
finish_condition.wait(finish_lock);
}
finish_lock.unlock();
}
}
#else // __EMSCRIPTEN_PTHREADS__
extern "C" {
void yolov5_ncnn(unsigned char* rgba_data, int w, int h)
{
cv::Mat rgba(h, w, CV_8UC4, (void*)rgba_data);
on_image_render(rgba);
}
}
#endif // __EMSCRIPTEN_PTHREADS__
5.前端代码
index.html
```html
ncnn webassembly yolov5
ncnn webassembly yolov5
6.make文件
project(yolov5)
cmake_minimum_required(VERSION 3.10)
set(CMAKE_BUILD_TYPE release)
if(NOT WASM_FEATURE)
message(FATAL_ERROR "You must pass cmake option -DWASM_FEATURE and possible values are basic, simd, threads and simd-threads")
endif()
set(ncnn_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ncnn/${WASM_FEATURE}/lib/cmake/ncnn")
find_package(ncnn REQUIRED)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=256MB -s EXIT_RUNTIME=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=256MB -s EXIT_RUNTIME=1")
set(CMAKE_EXECUTBLE_LINKER_FLAGS "${CMAKE_EXECUTBLE_LINKER_FLAGS} -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=256MB -s EXIT_RUNTIME=1")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sEXPORTED_FUNCTIONS=['_yolov5_ncnn'] --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@.")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sEXPORTED_FUNCTIONS=['_yolov5_ncnn'] --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@.")
set(CMAKE_EXECUTBLE_LINKER_FLAGS "${CMAKE_EXECUTBLE_LINKER_FLAGS} -sEXPORTED_FUNCTIONS=['_yolov5_ncnn'] --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@.")
if(${WASM_FEATURE} MATCHES "threads")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp -pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fopenmp -pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4")
endif()
add_executable(yolov5-${WASM_FEATURE} yolov5.cpp yolov5ncnn.cpp)
target_link_libraries(yolov5-${WASM_FEATURE} ncnn)
1.工程包含文件
image.pngassets里面模型文件,ncnn目录是刚刚下载的ncnn依赖库。
2.build
在build前把emsdk环境启动。
source emsdk/emsdk_env.sh
在工程目录下添加一个build目录
mkdir build
编译项目
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DWASM_FEATURE=basic ..
make -j4
cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DWASM_FEATURE=simd ..
make -j4
cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DWASM_FEATURE=threads ..
make -j4
cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DWASM_FEATURE=simd-threads ..
make -j4
3.启动服务
创建一个server的目录,在目录里面添加一个python的微服务器用来测试。
mkdir server
cd server
vim server.py
server.py
from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl, os
os.system("openssl req -nodes -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -subj '/CN=mylocalhost'")
port = 8888
httpd = HTTPServer(('0.0.0.0', port), SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, keyfile='key.pem', certfile="cert.pem", server_side=True)
print(f"Server running on https://0.0.0.0:{port}")
httpd.serve_forever()
把bulid目录下的除了和make相关的文件都移动到server目录下:
image.png把工程目录的的index.html和wasmFeatureDetect.js也移动到server目录。
运行server.py
python3 server.py
image.png打开浏览器,输入服务器地址,如果出现安全连接问题
在Chrome提示“您的连接不是私密连接”页面的空白区域点击一下,然后输入“thisisunsafe”(页面不会有任何输入提示),输入完成后会自动继续访问。