Skip to content

Commit e5164dd

Browse files
committed
feat(use ncnn for model inference): add tooling and code for ncnn model inference with webassembly
1 parent 74d1501 commit e5164dd

21 files changed

+1052
-193
lines changed

.clang-format

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
BasedOnStyle: Google
3+
IndentWidth: 4
4+
ColumnLimit: 0
5+
---
6+
IndentExternBlock: Indent

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
data/
2-
weights/
2+
models/
3+
emsdk/
4+
ncnn-20220701-webassembly/
5+
wasm_modules/
36

47
### Node ###
58
# Logs

.parcelrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"extends": "@parcel/config-default",
3-
"namers": [ "parcel-namer-rewrite" ]
3+
"namers": ["parcel-namer-rewrite"],
4+
"reporters": ["...", "parcel-reporter-static-files-copy"]
45
}

CMakeLists.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
project(ncnn-webassembly-yolov5)
2+
3+
cmake_minimum_required(VERSION 3.10)
4+
5+
set(CMAKE_BUILD_TYPE release)
6+
7+
if(NOT WASM_FEATURE)
8+
message(FATAL_ERROR "You must pass cmake option -DWASM_FEATURE and possible values are basic, simd, threads and simd-threads")
9+
endif()
10+
11+
set(ncnn_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ncnn-20220701-webassembly/${WASM_FEATURE}/lib/cmake/ncnn")
12+
find_package(ncnn REQUIRED)
13+
14+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sFORCE_FILESYSTEM=1 -sINITIAL_MEMORY=512MB")
15+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sFORCE_FILESYSTEM=1 -sINITIAL_MEMORY=512MB")
16+
set(CMAKE_EXECUTBLE_LINKER_FLAGS "${CMAKE_EXECUTBLE_LINKER_FLAGS} -sFORCE_FILESYSTEM=1 -sINITIAL_MEMORY=512MB")
17+
18+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sEXPORTED_FUNCTIONS='[\"_yolov5_ncnn\",\"_yolov5_ncnn_inference\"]' -sEXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\",\"getValue\",\"setValue\"]' --preload-file '${CMAKE_CURRENT_SOURCE_DIR}/models@.' -sLLD_REPORT_UNDEFINED -sERROR_ON_UNDEFINED_SYMBOLS=0 -sASSERTIONS")
19+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sEXPORTED_FUNCTIONS='[\"_yolov5_ncnn\",\"_yolov5_ncnn_inference\"]' -sEXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\",\"getValue\",\"setValue\"]' --preload-file '${CMAKE_CURRENT_SOURCE_DIR}/models@.' -sLLD_REPORT_UNDEFINED -sERROR_ON_UNDEFINED_SYMBOLS=0 -sASSERTIONS")
20+
set(CMAKE_EXECUTBLE_LINKER_FLAGS "${CMAKE_EXECUTBLE_LINKER_FLAGS} -sEXPORTED_FUNCTIONS='[\"_yolov5_ncnn\",\"_yolov5_ncnn_inference\"]' -sEXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\",\"getValue\",\"setValue\"]' --preload-file '${CMAKE_CURRENT_SOURCE_DIR}/models@.' -sLLD_REPORT_UNDEFINED -sERROR_ON_UNDEFINED_SYMBOLS=0 -sASSERTIONS")
21+
22+
if(${WASM_FEATURE} MATCHES "threads")
23+
MESSAGE(STATUS "running threads")
24+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp -pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4")
25+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4")
26+
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fopenmp -pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4")
27+
endif()
28+
29+
MESSAGE(STATUS "building")
30+
31+
add_executable(yolov5-${WASM_FEATURE} src/yolov5.cpp src/yolov5ncnn.cpp src/globals.cpp)
32+
MESSAGE(STATUS "linking")
33+
target_link_libraries(yolov5-${WASM_FEATURE} ncnn)
34+
MESSAGE(STATUS "copying files to output directory")

app.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,13 @@
1-
from flask import Flask, render_template, request
2-
from PIL import Image
3-
from model_abstractions import YOLOv5_Model
4-
from parabola_manipulation import calculate_parabola_parameters
1+
from flask import Flask, render_template
2+
53

64
app = Flask(__name__, template_folder="dist/templates", static_folder="dist/static")
7-
ball_detection_model = YOLOv5_Model("weights/ball_detection_model.pt")
85

96

107
@app.route("/")
118
def home():
129
return render_template("home.html")
1310

1411

15-
@app.route("/analyze_gameplay")
16-
def analyze_gameplay():
17-
return render_template("analyze_gameplay.html")
18-
19-
20-
@app.route("/api/v1/detect-ball", methods=["POST"])
21-
def detect_ball():
22-
image = Image.open(request.files["file"])
23-
if predictions := ball_detection_model.predict_coords(image):
24-
return max(predictions, key=lambda x: x["confidence"])
25-
return {}
26-
27-
28-
@app.route("/api/v1/calculate_parabola", methods=["POST"])
29-
def calculate_parabola():
30-
# structure: {'point1': {'x': 1, 'y': 2}, 'point2': {'x': 3, 'y': 4}, 'point3': {'x': 5, 'y': 6}}
31-
(x1, y1), (x2, y2), (x3, y3) = map(dict.values, request.json.values())
32-
A, B, C = calculate_parabola_parameters(x1, y1, x2, y2, x3, y3)
33-
return {"A": A, "B": B, "C": C}
34-
35-
3612
if __name__ == "__main__":
3713
app.run(debug=True)

os_switcher.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import re
2+
import os
3+
import argparse
4+
import platform
5+
import subprocess
6+
7+
8+
parser = argparse.ArgumentParser(description="Run different commands based on the OS")
9+
parser.add_argument("--windows", help="Command to run on Windows", required=True)
10+
parser.add_argument("--linux", help="Command to run on Linux", required=True)
11+
parser.add_argument("--macos", help="Command to run on MacOS, defaults to Linux command")
12+
args = parser.parse_args()
13+
14+
if args.macos is None:
15+
args.macos = args.linux
16+
17+
18+
def preprocess(string):
19+
split = re.findall(r"\{(.*?)\}", string)
20+
final_sub = {}
21+
for sub in split:
22+
sub_split = sub.split(":")
23+
if sub_split[0] == "env":
24+
final_sub[sub] = os.environ[sub_split[1]]
25+
for old, new in final_sub.items():
26+
string = string.replace("{" + old + "}", new)
27+
return string
28+
29+
30+
args.windows = preprocess(args.windows)
31+
args.linux = preprocess(args.linux)
32+
args.macos = preprocess(args.macos)
33+
34+
if platform.system() == "Windows":
35+
popen_kwargs = {"stdin": subprocess.PIPE}
36+
proc = subprocess.Popen(args.windows, **popen_kwargs, shell=True)
37+
elif platform.system() == "Linux":
38+
popen_kwargs = {"stdin": subprocess.PIPE}
39+
proc = subprocess.Popen(args.linux, **popen_kwargs, shell=True)
40+
elif platform.system() == "Darwin":
41+
popen_kwargs = {"stdin": subprocess.PIPE}
42+
proc = subprocess.Popen(args.macos, **popen_kwargs, shell=True)
43+
44+
proc.wait()

package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"events": "^3.3.0",
77
"parcel": "^2.6.2",
88
"parcel-namer-rewrite": "^2.0.0-rc.2",
9+
"parcel-reporter-static-files-copy": "^1.3.4",
910
"path-browserify": "^1.0.1",
1011
"postcss": "^8.4.14",
1112
"process": "^0.11.10",
@@ -17,5 +18,9 @@
1718
"(.*)\\.(svg|png|gif|jpg|css|js)": "static/$1.{hash}$2",
1819
"(.*)\\.(html)": "templates/$1.$2"
1920
}
21+
},
22+
"staticFiles": {
23+
"staticPath": "static/wasm_modules",
24+
"staticOutPath": "static/wasm_modules"
2025
}
2126
}

poetry.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,42 @@ scipy = "^1.8.1"
2222
torchvision = "^0.13.0"
2323
protobuf = "<4.21.3"
2424
seaborn = "^0.11.2"
25+
imagesize = "^1.4.1"
2526

2627
[tool.poetry.dev-dependencies]
2728
tqdm = "^4.64.0"
2829

2930
[tool.poe.tasks.watch]
3031
shell = "npx parcel watch templates/*.html --no-source-maps --dist-dir dist"
31-
interpreter = ["pwsh", "posix"]
32+
interpreter = ["powershell", "posix"]
3233

3334
[tool.poe.tasks.build]
3435
shell = "npx parcel build templates/*.html --no-source-maps --dist-dir dist"
35-
interpreter = ["pwsh", "posix"]
36+
interpreter = ["powershell", "posix"]
3637

3738
[tool.poe.tasks.server]
3839
shell = "poetry run python app.py"
39-
interpreter = ["pwsh", "posix"]
40+
interpreter = ["powershell", "posix"]
41+
42+
[tool.poe.tasks.build_basic_wasm]
43+
shell = '''python os_switcher.py --windows 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=basic -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && move yolov5* ../static/wasm_modules && cd .. && del /s /q build > NUL && rmdir /s /q build' --linux 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=basic -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && mv yolov5* ../static/wasm_modules && cd .. && rm -r build''''
44+
interpreter = ["powershell", "posix"]
45+
46+
[tool.poe.tasks.build_simd_wasm]
47+
shell = '''python os_switcher.py --windows 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=simd -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && move yolov5* ../static/wasm_modules && cd .. && del /s /q build > NUL && rmdir /s /q build' --linux 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=simd -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && mv yolov5* ../static/wasm_modules && cd .. && rm -r build''''
48+
interpreter = ["powershell", "posix"]
49+
50+
[tool.poe.tasks.build_threads_wasm]
51+
shell = '''python os_switcher.py --windows 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=threads -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && move yolov5* ../static/wasm_modules && cd .. && del /s /q build > NUL && rmdir /s /q build' --linux 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=threads -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && mv yolov5* ../static/wasm_modules && cd .. && rm -r build''''
52+
interpreter = ["powershell", "posix"]
53+
54+
[tool.poe.tasks.build_simd_threads_wasm]
55+
shell = '''python os_switcher.py --windows 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=simd-threads -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && move yolov5* ../static/wasm_modules && cd .. && del /s /q build > NUL && rmdir /s /q build' --linux 'mkdir build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=\"{env:EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake\" -DWASM_FEATURE=simd-threads -G \"Unix Makefiles\" .. && cmake --build . --config Release -j 4 && mv yolov5* ../static/wasm_modules && cd .. && rm -r build''''
56+
interpreter = ["powershell", "posix"]
57+
58+
[tool.poe.tasks.build_wasm]
59+
shell = "poetry poe build_basic && poetry poe build_simd && poetry poe build_threads && poetry poe build_simd_threads"
60+
interpreter = ["powershell", "posix"]
4061

4162
[build-system]
4263
requires = ["poetry-core>=1.0.0"]

0 commit comments

Comments
 (0)