Skip to content

Commit

Permalink
opencv: support cross-compilation and add sample function (#44)
Browse files Browse the repository at this point in the history
* opencv: support cross-compilation and add sample function

* nits: clean-up

* func: pass --cmdline

* opencv: add example doing k-NN inference

* gha: re-generate wasm if we add new function

* gha: few nits

* cpp: bump for latest s3

* gha: make cache depend on functions too
  • Loading branch information
csegarragonz authored Oct 16, 2024
1 parent 5cdd796 commit 268a78d
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 5 deletions.
38 changes: 34 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ jobs:

# Work-out whether we need to re-build the examples. We need to re-build the
# examples if either _any_ fo the examples has changed, or the WASM cache
# has expired
# has expired, or the ./funcs folder have changed
needs-build:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
outputs:
needs-wasm: ${{ steps.wasm-cache.outputs.cache-hit != 'true' }}
needs-wasm: ${{ (steps.wasm-cache.outputs.cache-hit != 'true') || (steps.filter.outputs.funcs-changed == 'true') }}
steps:
- name: "Checkout code"
uses: actions/checkout@v4
Expand All @@ -52,6 +52,14 @@ jobs:
path: ./wasm
key: wasm-${{ steps.submodule-commit.outputs.digest }}
lookup-only: true
# Check if any of the submodules have been modified
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
funcs-changed:
- 'func/**'
build-examples:
needs: needs-build
Expand All @@ -73,6 +81,8 @@ jobs:
rustup default stable
rustup target add wasm32-wasip1
./bin/inv_wrapper.sh rabe jwt
- name: "Build OpenCV"
run: ./bin/inv_wrapper.sh opencv
- name: "Build FFmpeg"
run: ./bin/inv_wrapper.sh ffmpeg
- name: "Build ImageMagick"
Expand All @@ -93,14 +103,22 @@ jobs:
run: ./bin/inv_wrapper.sh tensorflow
- name: "Build PolyBench/C"
run: ./bin/inv_wrapper.sh polybench polybench --native
- name: "Manually re-compile libz (the sysroot has a version with atomics)"
run: |
git submodule update --init
./bin/inv_wrapper.sh zlib
working-directory: ./cpp
- name: "Build functions used in the tests"
run: ./bin/inv_wrapper.sh func.tests
- name: "Get CPP/Python commits"
id: submodule-commit
run: |
apt install -y zstd
git config --global --add safe.directory "$GITHUB_WORKSPACE"
echo "digest=$(git submodule status | awk '{ print $1; }' | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
submodule_digest=$(git submodule status | awk '{ print $1; }' | md5sum | awk '{ print $1}')
func_digest=$(find 'func' -type f -exec md5sum {} + | sort | md5sum | awk '{ print $1 }')
echo "${submodule_digest}-${func_digest}"
echo "digest=$(echo -n '${submodule_digest}-${func_digest}' | md5sum | awk '{ print $1 }')" >> $GITHUB_OUTPUT
# Also move to a different path to restore from
mv /usr/local/faasm/wasm ./wasm
# If we are here we _always_ want to overwrite the cache
Expand Down Expand Up @@ -168,7 +186,11 @@ jobs:
run: |
sudo apt install -y zstd
git config --global --add safe.directory "$GITHUB_WORKSPACE"
echo "digest=$(git submodule status | awk '{ print $1; }' | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
submodule_digest=$(git submodule status | awk '{ print $1; }' | md5sum | awk '{ print $1}')
func_digest=$(find 'func' -type f -exec md5sum {} + | sort | md5sum | awk '{ print $1 }')
echo "${submodule_digest}-${func_digest}"
echo "digest=$(echo -n '${submodule_digest}-${func_digest}' | md5sum | awk '{ print $1 }')" >> $GITHUB_OUTPUT
# echo "digest=$(git submodule status | awk '{ print $1; }' | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
- name: "Get WASM cache"
uses: actions/cache/restore@v4
id: cpp-wasm-cache
Expand All @@ -182,6 +204,14 @@ jobs:
- name: "Print logs if wasm-upload fails"
if: failure() && (steps.wasm-upload.outcome == 'failure')
run: faasmctl logs -s upload
- name: "Run OpenCV check"
if: "contains(env.FAASM_WASM_VM, 'wamr')"
timeout-minutes: 2
run: faasmctl invoke opencv pca --cmdline "faasm://opencv/composers"
- name: "Run OpenCV k-NN Inference"
if: "contains(env.FAASM_WASM_VM, 'wamr')"
timeout-minutes: 2
run: faasmctl invoke opencv check --cmdline "faasm://opencv/bus_photo.bmp faasm://opencv/out.bmp"
- name: "Run MPI kernels"
timeout-minutes: 2
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@
[submodule "examples/tless-jwt"]
path = examples/tless-jwt
url = https://github.com/faasm/tless-jwt.git
[submodule "examples/opencv"]
path = examples/opencv
url = https://github.com/opencv/opencv.git
2 changes: 1 addition & 1 deletion cpp
Binary file added data/beethoven.bmp
Binary file not shown.
Binary file added data/bus_photo.bmp
Binary file not shown.
Binary file added data/tchaikovsky.bmp
Binary file not shown.
Binary file added data/wagner.bmp
Binary file not shown.
1 change: 1 addition & 0 deletions examples/opencv
Submodule opencv added at 0f2342
1 change: 1 addition & 0 deletions func/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ add_subdirectory(ffmpeg)
add_subdirectory(jwt)
add_subdirectory(lammps)
add_subdirectory(mpi)
add_subdirectory(opencv)
add_subdirectory(rabe)
add_subdirectory(tf)
7 changes: 7 additions & 0 deletions func/opencv/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set(FAASM_USER opencv)

faasm_example_func(check check.cpp)
target_link_libraries(opencv_check opencv_core opencv_imgcodecs opencv_imgproc)

faasm_example_func(pca pca.cpp)
target_link_libraries(opencv_pca opencv_core opencv_imgcodecs opencv_imgproc opencv_ml z)
46 changes: 46 additions & 0 deletions func/opencv/check.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Even when threading is completely disabled, OpenCV assumes the C++ library
// has been built with threading support, and typedefs (no-ops) these two
// symbols. To prevent an undefined symbol error, we define them here.
namespace std {
class recursive_mutex {
public:
void lock() {}
bool try_lock() { return true; }
void unlock() {}
};

template <typename T>
class lock_guard {
public:
explicit lock_guard(T&) {}
};
}

#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "Usage: <image_name_in.bmp> <image_name_out.bmp>" << std::endl;
return 1;
}

// Load an image
cv::Mat img = cv::imread(argv[1], cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "Error: Could not load image!" << std::endl;
return -1;
}

// Convert the image to grayscale
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

// Save the grayscale image
cv::imwrite(argv[2], gray);
std::cout << "Image has been converted to grayscale and saved as " << argv[2] << std::endl;

return 0;
}
88 changes: 88 additions & 0 deletions func/opencv/pca.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Even when threading is completely disabled, OpenCV assumes the C++ library
// has been built with threading support, and typedefs (no-ops) these two
// symbols. To prevent an undefined symbol error, we define them here.
namespace std {
class recursive_mutex {
public:
void lock() {}
bool try_lock() { return true; }
void unlock() {}
};

template <typename T>
class lock_guard {
public:
explicit lock_guard(T&) {}
};
}

#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/ml.hpp>
#include <iostream>
#include <filesystem>

void loadImages(const std::string& folder, std::vector<cv::Mat>& data, std::vector<int>& labels) {
int label = 0;
for (const auto& entry : std::filesystem::directory_iterator(folder)) {
if (entry.is_regular_file()) {

std::cout << " - loading image: " << entry.path().string() << " (" << label << ")" << std::endl;
cv::Mat img = cv::imread(entry.path().string(), cv::IMREAD_GRAYSCALE);
if (!img.empty()) {
cv::resize(img, img, cv::Size(64, 64));
data.push_back(img.reshape(1, 1));
labels.push_back(label);
}
}

label++;
}
}

int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "Usage: <image_path>" << std::endl;
return 1;
}

// Load images
std::cout << "Beginning to load images..." << std::endl;
std::vector<cv::Mat> images;
std::vector<int> labels;
loadImages(argv[1], images, labels);
std::cout << "Images loaded!" << std::endl;

// Convert data to a single matrix
std::cout << "Converting data..." << std::endl;
cv::Mat data;
cv::vconcat(images, data);
data.convertTo(data, CV_32F);
std::cout << "Data converted!" << std::endl;

// Perform PCA with 10 principal components
std::cout << "Performing PCA analysis..." << std::endl;
cv::PCA pca(data, cv::Mat(), cv::PCA::DATA_AS_ROW, 10);
cv::Mat pcaResult;
pca.project(data, pcaResult);
std::cout << "PCA on images succeded!" << std::endl;

// Prepare labels for training
cv::Mat labelsMat(labels);
labelsMat.convertTo(labelsMat, CV_32S);

// Train k-NN classifier with PCA result
cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();
knn->setDefaultK(3);
knn->train(pcaResult, cv::ml::ROW_SAMPLE, labelsMat);
std::cout << "Training k-NN classifier succeeded!" << std::endl;

// Perform a prediction on the first sample as an example
cv::Mat sample = pcaResult.row(0);
cv::Mat response;
knn->findNearest(sample, knn->getDefaultK(), response);
std::cout << "Predicted label: " << response.at<float>(0, 0) << std::endl;

return 0;
}
2 changes: 2 additions & 0 deletions tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from . import lammps
from . import libpng
from . import lulesh
from . import opencv
from . import polybench
from . import rabe
from . import tensorflow
Expand All @@ -32,6 +33,7 @@
lammps,
libpng,
lulesh,
opencv,
polybench,
rabe,
tensorflow,
Expand Down
16 changes: 16 additions & 0 deletions tasks/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@
join(EXAMPLES_DATA_HOST_DIR, "grace_hopper.bmp"),
join(EXAMPLES_DATA_BASE_DIR, "tflite", "grace_hopper.bmp"),
],
[
join(EXAMPLES_DATA_HOST_DIR, "bus_photo.bmp"),
join(EXAMPLES_DATA_BASE_DIR, "opencv", "bus_photo.bmp"),
],
[
join(EXAMPLES_DATA_HOST_DIR, "tchaikovsky.bmp"),
join(EXAMPLES_DATA_BASE_DIR, "opencv", "composers", "tchaikovsky.bmp"),
],
[
join(EXAMPLES_DATA_HOST_DIR, "wagner.bmp"),
join(EXAMPLES_DATA_BASE_DIR, "opencv", "composers", "wagner.bmp"),
],
[
join(EXAMPLES_DATA_HOST_DIR, "beethoven.bmp"),
join(EXAMPLES_DATA_BASE_DIR, "opencv", "composers", "beethoven.bmp"),
],
]


Expand Down
2 changes: 2 additions & 0 deletions tasks/func.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def tests(ctx, clean=False):
"""
funcs = [
["ffmpeg", "check"],
["opencv", "check"],
["opencv", "pca"],
["rabe", "test"],
["tf", "check"],
# TODO: this two functions are not used in the tests, as they are used
Expand Down
Loading

0 comments on commit 268a78d

Please sign in to comment.