Mediapipe android studio windows

Provide feedback

Saved searches

Use saved searches to filter your results more quickly

Sign up

Appearance settings

  1. Installing on Debian and Ubuntu
  2. Installing on CentOS
  3. Installing on macOS
  4. Installing on Windows
  5. Installing on Windows Subsystem for Linux (WSL)
  6. Installing using Docker

Note: To interoperate with OpenCV, OpenCV 3.x to 4.1 are preferred. OpenCV 2.x currently works but interoperability support may be deprecated in the future.

Note: If you plan to use TensorFlow calculators and example apps, there is a known issue with gcc and g++ version 6.3 and 7.3. Please use other versions.

Note: To make Mediapipe work with TensorFlow, please set Python 3.7 as the default Python version and install the Python “six” library by running pip3 install --user six.

Installing on Debian and Ubuntu

  1. Install Bazelisk.

    Follow the official Bazel documentation to install Bazelisk.

  2. Checkout MediaPipe repository.

    $ cd $HOME
    $ git clone https://github.com/google/mediapipe.git
    
    # Change directory into MediaPipe root directory
    $ cd mediapipe
    
  3. Install OpenCV and FFmpeg.

    Option 1. Use package manager tool to install the pre-compiled OpenCV libraries. FFmpeg will be installed via libopencv-video-dev.

    OS OpenCV
    Debian 9 (stretch) 2.4
    Debian 10 (buster) 3.2
    Debian 11 (bullseye) 4.5
    Ubuntu 16.04 LTS 2.4
    Ubuntu 18.04 LTS 3.2
    Ubuntu 20.04 LTS 4.2
    Ubuntu 20.04 LTS 4.2
    Ubuntu 21.04 4.5
    $ sudo apt-get install -y \
        libopencv-core-dev \
        libopencv-highgui-dev \
        libopencv-calib3d-dev \
        libopencv-features2d-dev \
        libopencv-imgproc-dev \
        libopencv-video-dev
    

    MediaPipe’s opencv_linux.BUILD and WORKSPACE are already configured for OpenCV 2/3 and should work correctly on any architecture:

    # WORKSPACE
    new_local_repository(
      name = "linux_opencv",
      build_file = "@//third_party:opencv_linux.BUILD",
      path = "/usr",
    )
    
    # opencv_linux.BUILD for OpenCV 2/3 installed from Debian package
    cc_library(
      name = "opencv",
      linkopts = [
        "-l:libopencv_core.so",
        "-l:libopencv_calib3d.so",
        "-l:libopencv_features2d.so",
        "-l:libopencv_highgui.so",
        "-l:libopencv_imgcodecs.so",
        "-l:libopencv_imgproc.so",
        "-l:libopencv_video.so",
        "-l:libopencv_videoio.so",
      ],
    )
    

    For OpenCV 4 you need to modify opencv_linux.BUILD taking into account current architecture:

    # WORKSPACE
    new_local_repository(
      name = "linux_opencv",
      build_file = "@//third_party:opencv_linux.BUILD",
      path = "/usr",
    )
    
    # opencv_linux.BUILD for OpenCV 4 installed from Debian package
    cc_library(
      name = "opencv",
      hdrs = glob([
        # Uncomment according to your multiarch value (gcc -print-multiarch):
        #  "include/aarch64-linux-gnu/opencv4/opencv2/cvconfig.h",
        #  "include/arm-linux-gnueabihf/opencv4/opencv2/cvconfig.h",
        #  "include/x86_64-linux-gnu/opencv4/opencv2/cvconfig.h",
        "include/opencv4/opencv2/**/*.h*",
      ]),
      includes = [
        # Uncomment according to your multiarch value (gcc -print-multiarch):
        #  "include/aarch64-linux-gnu/opencv4/",
        #  "include/arm-linux-gnueabihf/opencv4/",
        #  "include/x86_64-linux-gnu/opencv4/",
        "include/opencv4/",
      ],
      linkopts = [
        "-l:libopencv_core.so",
        "-l:libopencv_calib3d.so",
        "-l:libopencv_features2d.so",
        "-l:libopencv_highgui.so",
        "-l:libopencv_imgcodecs.so",
        "-l:libopencv_imgproc.so",
        "-l:libopencv_video.so",
        "-l:libopencv_videoio.so",
      ],
    )
    

    Option 2. Run setup_opencv.sh to automatically build OpenCV from source and modify MediaPipe’s OpenCV config. This option will do all steps defined in Option 3 automatically.

    Option 3. Follow OpenCV’s documentation to manually build OpenCV from source code.

    You may need to modify WORKSPACE and opencv_linux.BUILD to point MediaPipe to your own OpenCV libraries. Assume OpenCV would be installed to /usr/local/ which is recommended by default.

    OpenCV 2/3 setup:

    # WORKSPACE
    new_local_repository(
      name = "linux_opencv",
      build_file = "@//third_party:opencv_linux.BUILD",
      path = "/usr/local",
    )
    
    # opencv_linux.BUILD for OpenCV 2/3 installed to /usr/local
    cc_library(
      name = "opencv",
      linkopts = [
        "-L/usr/local/lib",
        "-l:libopencv_core.so",
        "-l:libopencv_calib3d.so",
        "-l:libopencv_features2d.so",
        "-l:libopencv_highgui.so",
        "-l:libopencv_imgcodecs.so",
        "-l:libopencv_imgproc.so",
        "-l:libopencv_video.so",
        "-l:libopencv_videoio.so",
      ],
    )
    

    OpenCV 4 setup:

    # WORKSPACE
    new_local_repository(
      name = "linux_opencv",
      build_file = "@//third_party:opencv_linux.BUILD",
      path = "/usr/local",
    )
    
    # opencv_linux.BUILD for OpenCV 4 installed to /usr/local
    cc_library(
      name = "opencv",
      hdrs = glob([
        "include/opencv4/opencv2/**/*.h*",
      ]),
      includes = [
        "include/opencv4/",
      ],
      linkopts = [
        "-L/usr/local/lib",
        "-l:libopencv_core.so",
        "-l:libopencv_calib3d.so",
        "-l:libopencv_features2d.so",
        "-l:libopencv_highgui.so",
        "-l:libopencv_imgcodecs.so",
        "-l:libopencv_imgproc.so",
        "-l:libopencv_video.so",
        "-l:libopencv_videoio.so",
      ],
    )
    

    Current FFmpeg setup is defined in ffmpeg_linux.BUILD and should work for any architecture:

    # WORKSPACE
    new_local_repository(
      name = "linux_ffmpeg",
      build_file = "@//third_party:ffmpeg_linux.BUILD",
      path = "/usr"
    )
    
    # ffmpeg_linux.BUILD for FFmpeg installed from Debian package
    cc_library(
      name = "libffmpeg",
      linkopts = [
        "-l:libavcodec.so",
        "-l:libavformat.so",
        "-l:libavutil.so",
      ],
    )
    
  4. For running desktop examples on Linux only (not on OS X) with GPU acceleration.

    # Requires a GPU with EGL driver support.
    # Can use mesa GPU libraries for desktop, (or Nvidia/AMD equivalent).
    sudo apt-get install mesa-common-dev libegl1-mesa-dev libgles2-mesa-dev
    
    # To compile with GPU support, replace
    --define MEDIAPIPE_DISABLE_GPU=1
    # with
    --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11
    # when building GPU examples.
    
  5. Run the Hello World! in C++ example.

    $ export GLOG_logtostderr=1
    
    # if you are running on Linux desktop with CPU only
    $ bazel run --define MEDIAPIPE_DISABLE_GPU=1 \
        mediapipe/examples/desktop/hello_world:hello_world
    
    # If you are running on Linux desktop with GPU support enabled (via mesa drivers)
    $ bazel run --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
        mediapipe/examples/desktop/hello_world:hello_world
    
    # Should print:
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    

If you run into a build error, please read Troubleshooting to find the solutions of several common build issues.

Installing on CentOS

Disclaimer: Running MediaPipe on CentOS is experimental.

  1. Install Bazelisk.

    Follow the official Bazel documentation to install Bazelisk.

  2. Checkout MediaPipe repository.

    $ git clone https://github.com/google/mediapipe.git
    
    # Change directory into MediaPipe root directory
    $ cd mediapipe
    
  3. Install OpenCV.

    Option 1. Use package manager tool to install the pre-compiled version.

    Note: yum installs OpenCV 2.4.5, which may have an opencv/gstreamer issue.

    $ sudo yum install opencv-devel
    

    Option 2. Build OpenCV from source code.

    Note: You may need to modify WORKSPACE, opencv_linux.BUILD and ffmpeg_linux.BUILD to point MediaPipe to your own OpenCV and FFmpeg libraries. For example if OpenCV and FFmpeg are both manually installed in “/usr/local/”, you will need to update: (1) the “linux_opencv” and “linux_ffmpeg” new_local_repository rules in WORKSPACE, (2) the “opencv” cc_library rule in opencv_linux.BUILD, and (3) the “libffmpeg” cc_library rule in ffmpeg_linux.BUILD. These 3 changes are shown below:

    new_local_repository(
        name = "linux_opencv",
        build_file = "@//third_party:opencv_linux.BUILD",
        path = "/usr/local",
    )
    
    new_local_repository(
        name = "linux_ffmpeg",
        build_file = "@//third_party:ffmpeg_linux.BUILD",
        path = "/usr/local",
    )
    
    cc_library(
        name = "opencv",
        srcs = glob(
            [
                "lib/libopencv_core.so",
                "lib/libopencv_highgui.so",
                "lib/libopencv_imgcodecs.so",
                "lib/libopencv_imgproc.so",
                "lib/libopencv_video.so",
                "lib/libopencv_videoio.so",
            ],
        ),
        hdrs = glob([
            # For OpenCV 3.x
            "include/opencv2/**/*.h*",
            # For OpenCV 4.x
            # "include/opencv4/opencv2/**/*.h*",
        ]),
        includes = [
            # For OpenCV 3.x
            "include/",
            # For OpenCV 4.x
            # "include/opencv4/",
        ],
        linkstatic = 1,
        visibility = ["//visibility:public"],
    )
    
    cc_library(
        name = "libffmpeg",
        srcs = glob(
            [
                "lib/libav*.so",
            ],
        ),
        hdrs = glob(["include/libav*/*.h"]),
        includes = ["include"],
        linkopts = [
            "-lavcodec",
            "-lavformat",
            "-lavutil",
        ],
        linkstatic = 1,
        visibility = ["//visibility:public"],
    )
    
  4. Run the Hello World! in C++ example.

    $ export GLOG_logtostderr=1
    # Need bazel flag 'MEDIAPIPE_DISABLE_GPU=1' if you are running on Linux desktop with CPU only
    $ bazel run --define MEDIAPIPE_DISABLE_GPU=1 \
        mediapipe/examples/desktop/hello_world:hello_world
    
    # Should print:
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    

If you run into a build error, please read Troubleshooting to find the solutions of several common build issues.

Installing on macOS

  1. Prework:

    • Install Homebrew.
    • Install Xcode and its Command Line Tools by xcode-select --install.
  2. Install Bazelisk.

    Follow the official Bazel documentation to install Bazelisk.

  3. Checkout MediaPipe repository.

    $ git clone https://github.com/google/mediapipe.git
    
    $ cd mediapipe
    
  4. Install OpenCV and FFmpeg.

    Option 1. Use HomeBrew package manager tool to install the pre-compiled OpenCV 3 libraries. FFmpeg will be installed via OpenCV.

    $ brew install opencv@3
    
    # There is a known issue caused by the glog dependency. Uninstall glog.
    $ brew uninstall --ignore-dependencies glog
    

    Option 2. Use MacPorts package manager tool to install the OpenCV libraries.

    Note: when using MacPorts, please edit the WORKSPACE, opencv_macos.BUILD, and ffmpeg_macos.BUILD files like the following:

    new_local_repository(
        name = "macos_opencv",
        build_file = "@//third_party:opencv_macos.BUILD",
        path = "/opt",
    )
    
    new_local_repository(
        name = "macos_ffmpeg",
        build_file = "@//third_party:ffmpeg_macos.BUILD",
        path = "/opt",
    )
    
    cc_library(
        name = "opencv",
        srcs = glob(
            [
                "local/lib/libopencv_core.dylib",
                "local/lib/libopencv_highgui.dylib",
                "local/lib/libopencv_imgcodecs.dylib",
                "local/lib/libopencv_imgproc.dylib",
                "local/lib/libopencv_video.dylib",
                "local/lib/libopencv_videoio.dylib",
            ],
        ),
        hdrs = glob(["local/include/opencv2/**/*.h*"]),
        includes = ["local/include/"],
        linkstatic = 1,
        visibility = ["//visibility:public"],
    )
    
    cc_library(
        name = "libffmpeg",
        srcs = glob(
            [
                "local/lib/libav*.dylib",
            ],
        ),
        hdrs = glob(["local/include/libav*/*.h"]),
        includes = ["local/include/"],
        linkopts = [
            "-lavcodec",
            "-lavformat",
            "-lavutil",
        ],
        linkstatic = 1,
        visibility = ["//visibility:public"],
    )
    
  5. Make sure that Python 3 and the Python “six” library are installed.

    $ brew install python
    $ sudo ln -s -f /usr/local/bin/python3.7 /usr/local/bin/python
    $ python --version
    Python 3.7.4
    $ pip3 install --user six
    
  6. Run the Hello World! in C++ example.

    $ export GLOG_logtostderr=1
    # Need bazel flag 'MEDIAPIPE_DISABLE_GPU=1' as desktop GPU is currently not supported
    $ bazel run --define MEDIAPIPE_DISABLE_GPU=1 \
        mediapipe/examples/desktop/hello_world:hello_world
    
    # Should print:
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    

If you run into a build error, please read Troubleshooting to find the solutions of several common build issues.

Installing on Windows

Disclaimer: Running MediaPipe on Windows is experimental.

Note: building MediaPipe Android apps is still not possible on native Windows. Please do this in WSL instead and see the WSL setup instruction in the next section.

  1. Install MSYS2 and edit the %PATH% environment variable.

    If MSYS2 is installed to C:\msys64, add C:\msys64\usr\bin to your %PATH% environment variable.

  2. Install necessary packages.

    C:\> pacman -S git patch unzip
    
  3. Install Python and allow the executable to edit the %PATH% environment variable.

    Download Python Windows executable from https://www.python.org/downloads/windows/ and install.

  4. Install Visual C++ Build Tools 2019 and WinSDK

    Go to the VisualStudio website, download build tools, and install Microsoft Visual C++ 2019 Redistributable and Microsoft Build Tools 2019.

    Download the WinSDK from the official MicroSoft website and install.

  5. Install Bazel or Bazelisk and add the location of the Bazel executable to the %PATH% environment variable.

    Option 1. Follow the official Bazel documentation to install Bazel 3.7.2 or higher.

    Option 2. Follow the official Bazel documentation to install Bazelisk.

  6. Set Bazel variables. Learn more details about “Build on Windows” in the Bazel official documentation.

    # Please find the exact paths and version numbers from your local version.
    C:\> set BAZEL_VS=C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools
    C:\> set BAZEL_VC=C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC
    C:\> set BAZEL_VC_FULL_VERSION=<Your local VC version>
    C:\> set BAZEL_WINSDK_FULL_VERSION=<Your local WinSDK version>
    
  7. Checkout MediaPipe repository.

    C:\Users\Username\mediapipe_repo> git clone https://github.com/google/mediapipe.git
    
    # Change directory into MediaPipe root directory
    C:\Users\Username\mediapipe_repo> cd mediapipe
    
  8. Install OpenCV.

    Download the Windows executable from https://opencv.org/releases/ and install. We currently use OpenCV 3.4.10. Remember to edit the WORKSPACE file if OpenCV is not installed at C:\opencv.

    new_local_repository(
        name = "windows_opencv",
        build_file = "@//third_party:opencv_windows.BUILD",
        path = "C:\\<path to opencv>\\build",
    )
    
  9. Run the Hello World! in C++ example.

    Note: For building MediaPipe on Windows, please add --action_env PYTHON_BIN_PATH="C://path//to//python.exe" to the build command. Alternatively, you can follow issue 724 to fix the python configuration manually.

    C:\Users\Username\mediapipe_repo>bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 --action_env PYTHON_BIN_PATH="C://python_36//python.exe" mediapipe/examples/desktop/hello_world
    
    C:\Users\Username\mediapipe_repo>set GLOG_logtostderr=1
    
    C:\Users\Username\mediapipe_repo>bazel-bin\mediapipe\examples\desktop\hello_world\hello_world.exe
    
    # should print:
    # I20200514 20:43:12.277598  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.278597  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.279618  1200 hello_world.cc:56] Hello World!
    # I20200514 20:43:12.280613  1200 hello_world.cc:56] Hello World!
    

If you run into a build error, please read Troubleshooting to find the solutions of several common build issues.

Installing on Windows Subsystem for Linux (WSL)

Note: The pre-built OpenCV packages don’t support cameras in WSL. Unless you compile OpenCV with FFMPEG and GStreamer in WSL, the live demos won’t work with any cameras. Alternatively, you use a video file as input.

  1. Follow the instruction to install Windows Sysystem for Linux (Ubuntu).

  2. Install Windows ADB and start the ADB server in Windows.

    Note: Windows’ and WSL’s adb versions must be the same version, e.g., if WSL has ADB 1.0.39, you need to download the corresponding Windows ADB from here.

  3. Launch WSL.

    Note: All the following steps will be executed in WSL. The Windows directory of the Linux Subsystem can be found in C:\Users\YourUsername\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_SomeID\LocalState\rootfs\home

  4. Install the needed packages.

    username@DESKTOP-TMVLBJ1:~$ sudo apt-get update && sudo apt-get install -y build-essential git python zip adb openjdk-8-jdk
    
  5. Install Bazelisk.

    Follow the official Bazel documentation to install Bazelisk.

  6. Checkout MediaPipe repository.

    username@DESKTOP-TMVLBJ1:~$ git clone https://github.com/google/mediapipe.git
    
    username@DESKTOP-TMVLBJ1:~$ cd mediapipe
    
  7. Install OpenCV and FFmpeg.

    Option 1. Use package manager tool to install the pre-compiled OpenCV libraries. FFmpeg will be installed via libopencv-video-dev.

    username@DESKTOP-TMVLBJ1:~/mediapipe$ sudo apt-get install libopencv-core-dev libopencv-highgui-dev \
                           libopencv-calib3d-dev libopencv-features2d-dev \
                           libopencv-imgproc-dev libopencv-video-dev
    

    Option 2. Run setup_opencv.sh to automatically build OpenCV from source and modify MediaPipe’s OpenCV config.

    Option 3. Follow OpenCV’s documentation to manually build OpenCV from source code.

    Note: You may need to modify WORKSPACE and opencv_linux.BUILD to point MediaPipe to your own OpenCV libraries, e.g., if OpenCV 4 is installed in “/usr/local/”, you need to update the “linux_opencv” new_local_repository rule in WORKSPACE and “opencv” cc_library rule in opencv_linux.BUILD like the following:

    new_local_repository(
        name = "linux_opencv",
        build_file = "@//third_party:opencv_linux.BUILD",
        path = "/usr/local",
    )
    
    cc_library(
        name = "opencv",
        srcs = glob(
            [
                "lib/libopencv_core.so",
                "lib/libopencv_highgui.so",
                "lib/libopencv_imgcodecs.so",
                "lib/libopencv_imgproc.so",
                "lib/libopencv_video.so",
                "lib/libopencv_videoio.so",
            ],
        ),
        hdrs = glob(["include/opencv4/**/*.h*"]),
        includes = ["include/opencv4/"],
        linkstatic = 1,
        visibility = ["//visibility:public"],
    )
    
  8. Run the Hello World! in C++ example.

    username@DESKTOP-TMVLBJ1:~/mediapipe$ export GLOG_logtostderr=1
    
    # Need bazel flag 'MEDIAPIPE_DISABLE_GPU=1' as desktop GPU is currently not supported
    username@DESKTOP-TMVLBJ1:~/mediapipe$ bazel run --define MEDIAPIPE_DISABLE_GPU=1 \
        mediapipe/examples/desktop/hello_world:hello_world
    
    # Should print:
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    

If you run into a build error, please read Troubleshooting to find the solutions of several common build issues.

Installing using Docker

This will use a Docker image that will isolate mediapipe’s installation from the rest of the system.

  1. Install Docker on your host system.

  2. Build a docker image with tag “mediapipe”.

    $ git clone https://github.com/google/mediapipe.git
    $ cd mediapipe
    $ docker build --tag=mediapipe .
    
    # Should print:
    # Sending build context to Docker daemon  147.8MB
    # Step 1/9 : FROM ubuntu:latest
    # latest: Pulling from library/ubuntu
    # 6abc03819f3e: Pull complete
    # 05731e63f211: Pull complete
    # ........
    # See http://bazel.build/docs/getting-started.html to start a new project!
    # Removing intermediate container 82901b5e79fa
    # ---> f5d5f402071b
    # Step 9/9 : COPY . /mediapipe/
    # ---> a95c212089c5
    # Successfully built a95c212089c5
    # Successfully tagged mediapipe:latest
    
  3. Run the Hello World! in C++ example.

    $ docker run -it --name mediapipe mediapipe:latest
    
    root@bca08b91ff63:/mediapipe# GLOG_logtostderr=1 bazelisk run --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hello_world:hello_world
    
    # Should print:
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    # Hello World!
    

If you run into a build error, please read Troubleshooting to find the solutions of several common build issues.

  1. Build a MediaPipe Android example.

    $ docker run -it --name mediapipe mediapipe:latest
    
    root@bca08b91ff63:/mediapipe# bash ./setup_android_sdk_and_ndk.sh
    
    # Should print:
    # Android NDK is now installed. Consider setting $ANDROID_NDK_HOME environment variable to be /root/Android/Sdk/ndk-bundle/android-ndk-r19c
    # Set android_ndk_repository and android_sdk_repository in WORKSPACE
    # Done
    
    root@bca08b91ff63:/mediapipe# bazel build -c opt --config=android_arm64 mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu:objectdetectiongpu
    
    # Should print:
    # Target //mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu:objectdetectiongpu up-to-date:
    # bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu/objectdetectiongpu_deploy.jar
    # bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu/objectdetectiongpu_unsigned.apk
    # bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps/objectdetectiongpu/objectdetectiongpu.apk
    # INFO: Elapsed time: 144.462s, Critical Path: 79.47s
    # INFO: 1958 processes: 1 local, 1863 processwrapper-sandbox, 94 worker.
    # INFO: Build completed successfully, 2028 total actions
    

This article was co-authored with Nathan Smith and Anh Tran.

Machine learning and computer vision can be daunting subjects for developers without direct experience. Our team is looking to leverage ready-made solutions to help developers add machine learning capabilities to their products, without needing to train and understand complex models. In this series, we’re aiming to recognize different gestures made by a user, and display these gestures on an Android device. This project requires accurate tracking and understanding of the hand via a video input, and Google’s MediaPipe looks like an ideal tool to get this working quickly.

MediaPipe Overview

MediaPipe is a framework that offers machine learning solutions primarily involving live detection, segmentation and object tracking through video sources. The framework works on multiple platforms, including Android, iOS, Python, Javascript, and C++. For our project, we’ll be focusing on working off of the Android resources, and will generally use Android Studio for the development of our app. To run and test these apps, we’ll be making use of the Android Debug Bridge (ADB). ADB allows you to communicate with and install APK files onto connected devices and the Android Emulator.

MediaPipe provides a number of out-of-the-box computer vision solutions. One that will be especially useful for our goals is MediaPipe’s Hands solution, which tracks both fingers and hands even while they are self-occluded. For example, while the user is making a fist, the application can still register all of the landmarks of the hand.

Images by MediaPipe via MediaPipe

MediaPipe can be installed on MacOS, Linux (Debian and Ubuntu), Windows (using the Windows Subsystem for Linux, or WSL), and via a Docker image. While MediaPipe markets itself as an out-of-the-box solution where you can have an example solution running within minutes, we found that the ease of setup varies drastically between platforms. While following the official documentation went pretty seamlessly on MacOS, and running the Hands solution on an Android phone was issue-free, setting everything up on Linux and using the Android Studio Emulator had a number of challenges, as well as setting things up on Windows with WSL. In this article, we’ll review setting up MediaPipe on Linux and Windows machines, as well as on a Docker container.

Before we can use the MediaPipe repository, we need to have Bazel set up on our machine. Bazel is a Google-developed open source tool that is used to test and build projects on multiple different platforms, and supports multiple languages and frameworks. Although you can install Bazel in a variety of ways, we used npm, running the following command in our terminal: 

npm install -g @bazel/bazelisk

This installs Bazel via Bazelisk, a wrapper that automatically picks a proper version of Bazel to install on your device. Once this is installed, we can clone the MediaPipe repository. This repository holds all the example solutions that developers can leverage, including face detection and hand tracking. Now we will need to set up OpenCV and FFmpeg. OpenCV is an open source computer vision and machine learning software library containing thousands of algorithms, which will be necessary for the 3D tracking that is used in many of the Mediapipe solutions. FFmpeg is a solution for converting and streaming audio and video on multiple platforms. 

The version of OpenCV we will be using will depend on the version of Debian/Ubuntu we are using. We are on Ubuntu 22.04 and are using OpenCV 4, and thus will need to make the following changes to the opencv_linux.BUILD file in the /mediapipe/third_party/ directory.

Running gcc -print-multiarch in our terminal yields x86_64-linux-gnu. Thus, we update this file by uncommenting the include/x86_64-linux-gnulines. There are other methods of setting up OpenCV on the official documentation for MediaPipe installation on their webpage, however, we did not go over those as they are more tedious than the previous method and not necessary for our goals. Now, to make sure we’ve properly installed and set up MediaPipe and Bazel, we run the following commands:

export GLOG_logtostderr=1
bazel run --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
mediapipe/examples/desktop/hello_world:hello_world

This prints Hello World! 10 times within our terminal.

During the course of testing out different MediaPipe solutions, we primarily used Android Studio, which was a new development environment for us. Thankfully, Android Studio has a very straightforward user interface, which provided by far the easiest way for setting up our Android SDK (Software Development Kit, a set of tools for developing and building Android applications) and Android NDK (Native Development Kit, allowing you to use C and C++ code with Android). In order to set these up, all we had to do was use Android Studio’s SDK tools menu to install the required APIs.

In order to actually build and run the solutions provided by MediaPipe, it is necessary that you set an ANDROID_HOME & ANDROID_NDK_HOME environment variable. Ubuntu makes use of a .bashrc file where variables can be manually and permanently set. Inside this .bashrc file, we added:

export ANDROID_HOME="/path/to/Android/Sdk"
export ANDROID_NDK_HOME="path/to/Android/Sdk/ndk/21.4.7075529"

The path to your Android SDK & NDK can be a bit annoying to find, however, Android Studio makes the whole process easier. All we had to do was navigate to the SDK tools menu and right at the top, the path to the SDK is listed, and inside the SDK path is the NDK.

Now, all that’s left is to build and run the ML solution/app. Thus far, we haven’t manually implemented all the necessary components for the application and will be using one of the provided examples that came with the cloned repository. To build the android “Hello World” application, all we had to do was navigate to the application directory and run the following command:

bazel build -c opt --config=android_arm64 :helloworld

Once the application is successfully built, you should find a folder in the following directory:

/mediapipe/bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps

We navigate into the folder and find an apk file that contains the name of the application which will be used to install the built app. Now, typically developers will download their apk file by just dragging and dropping the apk file onto the screen of their running virtual device, however that is a bit clunky, and oftentimes non-descriptive errors can result from this assuming something went wrong. The best way we found of installing this was through the use of the Android Debug Bridge (ADB).

All that had to be done was to create and run a virtual device in Android Studio, and then run the emulator. Once the virtual device is running, we were able to see the device listed through running adb devices. We could then navigate into the apk file’s directory and run adb install <filename>.apk, or helloworld.apk in our case, to install the app onto the running devices. The app could then be run on the device without a problem.

Challenges setting up MediaPipe on Windows/WSL

We found that we could not reasonably set up MediaPipe on Windows or WSL in a reasonable timeframe, due to issues that would come up during the process. For the Windows setup, the primary issue was that MediaPipe on Windows was still in an experimental phase, and as of the publication of this article, it is yet to be possible to build MediaPipe Android apps on native Windows. This fact would lead us to attempt to set MediaPipe up on WSL.

Regarding WSL, we were able to set it up to the point that the Hello World application was able to build and run. However, when it came to attempting to build the Android apps, there were repeated issues working between MediaPipe and the Android SDK. Ultimately, we felt that it would take too long to try and diagnose the specific issues, and thus we pivoted to the Docker solution.

Setting up MediaPipe via Docker

The prerequisites for setting up MediaPipe in a Docker container are setting up WSL2 for Docker integration, then installing Docker Desktop. WSL2 integration with Docker allows us to run Linux containers through Docker, which helps us bypass the issues we previously ran into on Windows. Once Docker is installed, proceed to clone the MediaPipe repo to your machine and move into it, then build a docker image with tag “mediapipe”.

git clone https://github.com/google/mediapipe.git
cd mediapipe
docker build --tag=mediapipe .

Next, proceed to create the container with the docker run command. This command creates the container, then starts it.

docker run -it --name mediapipe mediapipe:latest

The first time you run this, you should end up in a Bash terminal within the container. If this is not the first time, and you are starting from an exited container, use the following commands to enter the container: 

docker start mediapipe
docker exec -it mediapipe /bin/bash

Once you are inside the container, attempt to run the Hello World example to verify success.

root@bca08b91ff63:/mediapipe# GLOG_logtostderr=1 bazel run --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hello_world

Then, to set up for building Android apps, run setup_android_sdk_and_ndk.sh from inside /mediapipe. This command will automatically download and set up Android SDK and NDK, as an alternative to setting them up via Android Studio.

Building the app once you have everything set up is fairly straightforward. For example, to build the hand tracking demo, run the following command:

bazel build -c opt --config=android_arm64 mediapipe/examples/android/src/java/com/google/mediapipe/apps/handtrackinggpu:handtrackinggpu

This can take quite a while depending on your machine, so patience will be necessary. Completion should look something like this:

Once you are done building the app, the next step would be to install the app onto your device. 

The recommended way to do this would be to install the app via adb. However, there were issues with connecting my phone to the container over adb due to home network issues that could not be resolved, so an alternative solution was pursued.

The alternative solution that I used to install the app is fairly simple. What I did was copy the apk file to my host machine, then dropped it into my Android device’s storage. From there, I installed it directly from my device. To copy the apk file from the docker container, use the following command from your host machine:

docker cp <containerId>:<filePathWithinContainer> <hostPathTarget>

An example of this command would be: 

docker cp example_container:/example_file.apk .

Then, place the apk file into your device and install it from your device. In this example, I placed the handtrackinggpu.apk into my downloads folder.

The MediaPipe Hands Project

Real-time hand tracking anywhere, anytime. That is what MediaPipe provides its users. While many state-of-the-art solutions for this employ powerful desktop environments, MediaPipe is able to achieve this on mobile devices. This allows us to employ the technology in a wide range of applications that would otherwise be hampered by the requirement of a powerful machine.

MediaPipe accomplishes this by using an ML pipeline that consists of multiple models working together. A palm detection model operates on the entire image and returns a cropped image defined by an oriented hand bounding box. A hand landmark model operates on this cropped image and returns high-fidelity 3D hand landmarks. By utilizing the cropped hand image for the hand landmark model, MediaPipe is able to significantly reduce data augmentation, and is able to dedicate most of its processing capacity towards accurately predicting the coordinates of the key points. Later crops can also be generated based on hand landmarks previously identified, and only when the landmarks can no longer be identified does the palm detection get invoked again. A breakdown of the mapped landmarks is as follows:

Images by MediaPipe via MediaPipe

Hypothetically, we could use the hand landmarks to trigger events in an application. For example, if landmark 8, at the tip of the index finger, is within a certain frame on the screen for some period of time, we could interpret that as the user selecting an option in an interface. Here’s an example of MediaPipe’s default overlay for a pointing hand:

By leveraging MediaPipe’s clear and accurate understanding of different points of the hand relative to one another, and training it with examples of different gestures, we can develop a live gesture recognition application. We’ll need a labeled dataset of hands in different configurations and their specific gestures, such as pointing, a peace sign, a fist, and more. We could then leverage a machine learning solution such as TensorFlow to predict a hand gesture, with the location of our hand landmarks as the input.

Conclusion

Despite some issues setting it up across Linux and Windows devices, MediaPipe provides an impressive array of computer vision solutions that can be set up quite easily without any machine learning knowledge. The Hands solution provides a shockingly accurate estimation of many landmarks on the hand, providing a wealth of possibilities for recognizing both stationary gestures, such as pointing, in addition to motion gestures such as waving and swiping. In the next article in this series, we’ll be exploring how to develop an Android app that displays live estimations of a user’s hand gestures in the UI, and think forward to how these features could be employed in a real-world solution, such as a touchless kiosk for ordering food.

New call-to-action

About Mission Data

We’re designers, engineers, and strategists building innovative digital products that transform the way companies do business. Learn more: https://www.missiondata.com.

Сегодня множество сервисов используют в своей работе нейросетевые модели. При этом из-за невысокой производительности клиентских устройств вычисления в большинстве случаев производятся на сервере. Однако производительность смартфонов с каждым годом растет и сейчас становится возможным запуск небольших моделей на клиентских устройствах. Возникает вопрос: как это сделать? Помимо запуска модели требуется выполнять предобработку и постобработку данных. К тому же, есть как минимум две платформы, где это нужно реализовать: android и iOS. Mediapipe — фреймворк для запуска пайплайнов (предобработка данных, запуск (inference) модели, а также постобработка результатов модели) машинного обучения, позволяющий решить описанные выше проблемы и упростить написание кроссплатформенного кода для запуска моделей.

Содержание

  1. Обзор системы сборки Bazel
  2. Фреймворк Mediapipe
    1. Что такое Mediapipe. Из чего состоит и зачем нужен
    2. HelloWorld приложение на Mediapipe
    3. Запуск модели с помощью Mediapipe
  3. Создание приложения с помощью Mediapipe
  4. Заключение

Обзор системы сборки Bazel

В Mediapipe для сборки используется Bazel. Я столкнулся с этой системой сборки впервые, поэтому параллельно с Mediapipe разбирался c Bazel, собирая информацию с разных источников и наступая на грабли. Поэтому, перед тем, как рассказать о самом Mediapipe, мне хотелось бы дать небольшое введение по системе сборки Bazel, которая используется для сборки Mediapipe и проектов на его основе. Bazel — система сборки от Google, доработанная версия их внутренней системы сборки Blaze, выложенная в общий доступ. Bazel позволяет в одном проекте объединять различные языки и фреймворки простой командой:

bazel build TARGET

Как система сборки понимает, какой именно инструмент необходимо использовать для того или иного таргета? Для начала необходимо понять, из чего складывается Bazel-проект. Главным файлом проекта является текстовый файл WORKSPACE, в котором указываются все зависимости проекта. Говоря обо всех зависимостях, подразумеваются транзитивные зависимости тоже. Иными словами, если проект зависит от A, который зависит от B, то необходимо будет указать в WORKSPACE файле как зависимость от A, так и от B. Из-за этого WORKSPACE может сильно разрастаться, что можно будет заметить в дальнейшем примере работы с Mediapipe. Разработчики Bazel объясняют такое решение тем, что при изменении у прямых зависимостей проекта их зависимостей, которые используются в текущем проекте, проект может ломаться и искать проблему становится очень сложно, поэтому требуется явно указать всё, от чего зависит проект. Вот как может выглядить пример файла WORKSPACE:

# Загрузка правила для получения зависимостей скачиванием
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# Скачивание зависимости с набором правил для сборки c++
http_archive(
    name = "rules_cc",
    strip_prefix = "rules_cc-master",
    urls = ["https://github.com/bazelbuild/rules_cc/archive/master.zip"],
)

# Скачивание зависимости googletest
http_archive(
    name = "com_google_googletest",
    strip_prefix = "googletest-master",
    urls = ["https://github.com/google/googletest/archive/master.zip"],
)

Файл состоит из правил (bazel rule). Каждое из этих правил — функция на языке Starlark. Расширяемость и универсальность Bazel обеспечивается за счет возможности написания правил, с помощью которых можно добавлять в систему сборки новые возможности, новые языки и экосистемы. Например, в указанном выше примере сначала загружается правило http_archive для загрузки зависимостей, а затем с помощью http_archive загружается набор правил для сборки C++ проектов и библиотека googletest.

Помимо файла WORKSPACE любой Bazel проект содержит некоторое количество файлов BUILD. BUILD файл описывает директорию, в которой он находится, как модуль и содержит описание таргетов для сборки. Вот так выглядит BUILD файл для сборки C++ проекта с одним файлом:

cc_binary(
    name="HelloWorld",
    srcs=["main.cpp"],
    copts=["-Wall", "-Wpedantic", "-Werror"]
)

В данном случае используется единственное правило, создающее таргет для сборки, которое было загружено в файле WORKSPACE с помощью http_archive. Для получения простого Bazel-проекта досточно добавить файл с исходным кодом main.cpp.

  • ./WORKSPACE
  • ./Source/BUILD
  • ./Source/main.cpp

Для сборки этого проекта достаточно выполнить команду

bazel build //Source:HelloWorld

где Source — модуль, HelloWorld — таргет в модуле.

Правило вызовет cmake, результат сборки(исполняемый файл) будет расположен в ./bazel-bin.

Добавлять всё зависимости и исходники таргета в одно правило часто может быть затруднительно, правила могут разрастаться и быть трудноподдерживаемыми, а также много кода может дублироваться. Для решения данной проблемы в Bazel есть библиотеки (libraries).

cc_library(
    name = "lib",
    hdrs = ["utils.h"],
    srcs = ["utils.cpp"],
    visibility = [
        "//visibility:public",
    ],
)

cc_binary(
    name="HelloWorld",
    srcs=["main.cpp"],
    deps=[":lib"],
    copts=["-Wall", "-Wpedantic", "-Werror"]
)

Библиотеки могут располагаться как в одном BUILD файле, так и в разных модулях или в разных проектах. Чтобы указать зависимость от модуля в другом проекте, необходимо до // написать название проекта

@some_repository//Module:lib

Bazel содержит набор встроенных правил, подходящих для большинства случаев. Помимо http_archive, зависимости можно добавлять с помощью git_repository, local_repository и др.

На текущий момент есть две мажорные версии Bazel. На момент написания статьи актуальная версия Mediapipe собирается с помощью bazel версии 2.0.0, при этом более рании версии собирались с bazel 1.2.1. В репозитории Ubuntu 19.04 пакет bazel представляет собой bazel-1.2.1, тогда как для Ubuntu 19.10 пакет bazel — это bazel-2.0.0. Для сборки примеров достаточно установить пакет bazel-2.0.0 и для сборки указывать его явно:

bazel-2.0.0 build TARGET

Mediapipe — кроссплатформенный фреймворк для запуска пайплайнов машинного обучения. Сам пайплайн задается в форме графа, граф состоит из следующих элементов (в скобках указывается оригинальный термин):

  1. Вершины графа (Calculators) — это некоторые преобразования для данных (пакетов). У каждого калькулятора должен быть как минимум один входящий и как минимум один исходящий поток. Калькулятор представляет из себя C++ класс, реализующий интерфейс CalculatorBase:
    • static Status GetContract(CalculatorContract*); — статический метод, в котором калькулятор описывает форматы данных, которые ждет на вход и готов отдать на выход.
    • Status Open(CalculatorContext*); — инициализация калькулятора при создании графа. Здесь, например, может быть загрузка данных, требуемых для работы.
    • Status Process(CalculatorContext*); — обработка поступившего пакета.
    • Status Close(CalculatorContext*); — закрытие вершины.
  2. Ребра графа (Streams) задают связи между калькуляторами. С помощью потоков по графу перемещаются пакеты с данными. Поток может быть внутренний, входной (input) и исходящий (output). Внутренний поток соединяет два калькулятора, по входному потоку из внешнего кода в граф попадают данные, а с помощью исходящего потока граф отправляет данные наружу, в вызывающий код.
  3. Пакет (Packet) — единица данных, перемещаемая по потокам и обрабатываемая калькулятором. Каждый пакет несёт в себе данные определенного типа — это может быть строка, целое число, массив чисел с плавающей запятой или пользовательский тип, описанный и сериализуемый в protobuf. Каждый пакет содержит в себе timestamp — отметку времени, ассоциированную с пакетом. Необходима для того, чтобы отличать, какой пакет был раньше, какой позже, напрямую с реальным временем не связано.

Графы описываются в формате protobuf.

Mediapipe позволяет из калькуляторов составлять необходимый пайплайн для запуска модели, а затем просто встраивать его в приложения на разных платформах. Сейчас разработчики заявляют о поддержке нескольких дистрибутивов Linux, WSL, MacOS, Android, iOS. В Mediapipe есть встроенные калькуляторы для запуска TensorFlow и TFLite моделей. Поддержки других фреймворков машинного обучения сейчас нет, но так как можно создавать собственные калькуляторы и встраивать их в граф, то возможность добавления поддержки других фреймворков есть.

Далее на примерах показано, как использовать Mediapipe. Здесь будут разобраны самые примитивные примеры, показывающие решение упрощенных учебных задач. Более сложные и практические примеры есть в репозитории Mediapipe, но разбираться с нуля по ним может быть сложно, полезно начинать с чего-то максимально простого.

Для начала мы рассмотрим самый простой граф, состоящий из одного калькулятора, который повторяет N раз пакет, полученный на вход.

Проект содержит описание графа в .pbtxt файле, реализацию калькулятора и код приложения в main.cpp.

├── hello-world
│   ├── BUILD
│   ├── graph.pbtxt
│   ├── main.cpp
│   ├── RepeatNTimesCalculator.cpp
│   └── RepeatNTimesCalculator.proto
└── WORKSPACE

Конфигурация графа выглядит следующим образом:

input_stream: "in"
output_stream: "out"

node {
    calculator: "RepeatNTimesCalculator"
    input_stream: "in"
    output_stream: "OUTPUT_TAG:out"
    node_options: {
        [type.googleapis.com/mediapipe_demonstration.RepeatNTimesCalculatoOptions] {
            n: 3
        }
    }
}

Калькулятор RepeatNTimesCalculator написать достаточно просто. Для этого необходимо реализовать GetContract, в котором будет указан тип входных и выходных пакетов, Open, в котором загружается количество повторений из конфигурации графа, а также метод Process, выполняющий повторение поступившего на вход пакета.

class RepeatNTimesCalculator : public mediapipe::CalculatorBase {
public:
    static mediapipe::Status GetContract(mediapipe::CalculatorContract* cc) {
        // На вход ожидается поток без тега с пакетами, содержащими строки
        cc->Inputs().Get("", 0).Set<std::string>();
        // Из калькулятора выходит поток с тегом OUTPUT_TAG, в котором пакеты со строками
        cc->Outputs().Get("OUTPUT_TAG", 0).Set<std::string>();
        return mediapipe::OkStatus();
    }

    mediapipe::Status Open(mediapipe::CalculatorContext* cc) final {
        // Загрузка параметров калькулятора, указанных при описании графа
        const auto& options = cc->Options<mediapipe_demonstration::RepeatNTimesCalculatoOptions>();
        // n - количество повторений входного сигнала на выходе
        n_ = options.n();
        return mediapipe::OkStatus();
    }

    mediapipe::Status Process(mediapipe::CalculatorContext* cc) final {
        // Получение текста из входного пакета
        // Из массива входных потоков берется поток без тега с нулевым индексом
        // И из него достается содержимое типа std::string
        auto txt = cc->Inputs().Index(0).Value().Get<std::string>();

        for (int i = 0; i < n_; ++i) {
            // Создание пакета с содержимым из входного
            auto packet = mediapipe::MakePacket<std::string>(txt).At(cc->InputTimestamp() + i);
            // Отправка пакета по потоку с тегом OUTPUT_TAG и индексом 0
            cc->Outputs().Get("OUTPUT_TAG", 0).AddPacket(packet);
        }

        return mediapipe::OkStatus();
    }
private:
    int n_;
};
// Макрос для регистрации калькулятора
REGISTER_CALCULATOR(RepeatNTimesCalculator);

Весь класс располагается в .cpp файле, заголовочный файл не требуется, регистрацию класса производит макрос REGISTER_CALCULATOR.

Помимо самого кода калькулятора нам необходимо определить proto-файл с конфигурацией калькулятора. В данном примере это RepeatNTimesCalculatoOptions, который используется для указания того, сколько раз необходимо повторить входной сигнал на выходе.

syntax = "proto2";
package mediapipe_demonstration;
import "mediapipe/framework/calculator_options.proto";
message RepeatNTimesCalculatoOptions {
  extend mediapipe.CalculatorOptions {
    optional RepeatNTimesCalculatoOptions ext = 350607623;
  }
  required int32 n = 2;
}

Теперь достаточно запустить полученный граф в коде приложения:

mediapipe::Status RunGraph() {
    // Загрузка графа из файла
    std::ifstream file("./hello-world/graph.pbtxt");
    std::string graph_file_content;
    graph_file_content.assign(
        std::istreambuf_iterator<char>(file), 
        std::istreambuf_iterator<char>());
    mediapipe::CalculatorGraphConfig config = 
        mediapipe::ParseTextProtoOrDie<mediapipe::CalculatorGraphConfig>(graph_file_content);
    // Инициализация графа
    mediapipe::CalculatorGraph graph;
    MP_RETURN_IF_ERROR(graph.Initialize(config));
    // Подписка на выходной поток
    ASSIGN_OR_RETURN(mediapipe::OutputStreamPoller poller, graph.AddOutputStreamPoller("out"));
    // Запуск графа
    MP_RETURN_IF_ERROR(graph.StartRun({}));
    // Отправка пакета на вход и закрытие входного потока
    auto input_packet = mediapipe::MakePacket<std::string>("Hello!").At(mediapipe::Timestamp(0));
    MP_RETURN_IF_ERROR(graph.AddPacketToInputStream("in", input_packet));
    MP_RETURN_IF_ERROR(graph.CloseInputStream("in"));
    // Получение пакета на выходе
    mediapipe::Packet packet;
    while (poller.Next(&packet)) {
        std::cout << packet.Get<std::string>() << std::endl;
    }
    return graph.WaitUntilDone();
}

Наконец, чтобы собрать всё воедино, необходимо написать BUILD файл с набором правил сборки для файла настроек калькулятора, исходного кода калькулятора и вызывающего кода.

load("@mediapipe_repository//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
# Правило сборки для настроек калькулятора
proto_library(
    name = "repeat_n_times_calculator_proto",
    srcs = ["RepeatNTimesCalculator.proto"],
    visibility = ["//visibility:public"],
    deps = [
        "@mediapipe_repository//mediapipe/framework:calculator_proto",
    ],
)
# Правило сборки для кода калькулятора
mediapipe_cc_proto_library(
    name = "repeat_n_times_calculator_cc_proto",
    srcs = ["RepeatNTimesCalculator.proto"],
    cc_deps = [
        "@mediapipe_repository//mediapipe/framework:calculator_cc_proto",
    ],
    visibility = ["//visibility:public"],
    deps = [":repeat_n_times_calculator_proto"],
)
# Правило сборки калькулятора. Указано название, список исходников и зависимости
cc_library(
    name = "repeat_n_times_calculator",
    srcs = ["RepeatNTimesCalculator.cpp"],
    visibility = [
        "//visibility:public",
    ],
    deps = [
        ":repeat_n_times_calculator_cc_proto",
        "@mediapipe_repository//mediapipe/framework:calculator_framework",
        "@mediapipe_repository//mediapipe/framework/port:status",
    ],
    alwayslink = 1,
)
# Правило сборки исполняемого файла, который будет запускать граф.
cc_binary(
    name = "HelloMediapipe",
    srcs = ["main.cpp"],
    deps = [
        "repeat_n_times_calculator",
        "@mediapipe_repository//mediapipe/framework/port:logging",
        "@mediapipe_repository//mediapipe/framework/port:parse_text_proto",
        "@mediapipe_repository//mediapipe/framework/port:status",
    ],
)

Сборка и запуск:

$ bazel-2.0.0 build --define MEDIAPIPE_DISABLE_GPU=1 //hello-world:HelloMediapipe
...
INFO: Build completed successfully, 4 total actions
$ ./bazel-bin/hello-world/HelloMediapipe
Hello!
Hello!
Hello!

Данный пример демонстрирует пример запуска графа Mediapipe, однако не имеет отношения к запуску ML моделей.

Теперь рассмотрим, как с помощью Mediapipe запускать tflite модели на разных устройствах. Мы хотим сделать классификацию фотографий на телефоне и десктопе. Для этого возьмем готовую и сконвертированную сеть, обученную на ImageNet1k. В отличие от предыдущего пункта, дополнительных калькуляторов создавать не требуется, стандартных вполне достаточно. Проект состоит из двух приложений, а также файла модели и графа.

├── inference
│   ├── android/src/main/java/com/com/mediapipe_demonstration/inference
│   │   ├── AndroidManifest.xml
│   │   ├── BUILD
│   │   ├── MainActivity.kt
│   │   └── res
│   │       └── ...
│   ├── BUILD
│   ├── desktop
│   │   ├── BUILD
│   │   └── main.cpp
│   ├── graph.pbtxt
│   ├── img.jpg
│   └── mobilenetv2_imagenet.tflite
└── WORKSPACE

Граф выглядит следующим образом:

input_stream: "in"
output_stream: "out"
node: {
  calculator: "ImageTransformationCalculator"
  input_stream: "IMAGE:in"
  output_stream: "IMAGE:transformed_input"
  node_options: {
    [type.googleapis.com/mediapipe.ImageTransformationCalculatorOptions] {
      output_width: 224
      output_height: 224
    }
  }
}
node {
  calculator: "TfLiteConverterCalculator"
  input_stream: "IMAGE:transformed_input"
  output_stream: "TENSORS:image_tensor"
  node_options: {
      [type.googleapis.com/mediapipe.TfLiteConverterCalculatorOptions] {
        zero_center: false
      }
  }
}
node {
  calculator: "TfLiteInferenceCalculator"
  input_stream: "TENSORS:image_tensor"
  output_stream: "TENSORS:prediction_tensor"
  node_options: {
    [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] {
      model_path: "inference/mobilenetv2_imagenet.tflite"
    }
  }
}
node {
  calculator: "TfLiteTensorsToFloatsCalculator"
  input_stream: "TENSORS:prediction_tensor"
  output_stream: "FLOATS:out"
}

Граф содержит 4 калькулятора:

  1. Изменение размера входного изображения на 224×224.
  2. Нормализация на диапазон [-1, 1] (параметр калькулятора zero_center: false, по умолчанию нормализация проводится на [0, 1]), а затем преобразование из изображения в формате ImageFrame (представление изображения в Mediapipe, именно с этим форматом работает большинство калькуляторов, обрабатывающих изображения) в TfLiteTensor (точнее std::vector<TfLiteTensor> размера 1). Пакет с данными типа TfLiteTensor необходим следующему калькулятору (согласно его GetContract), который выполняет запуск модели.
  3. Запуск модели.
  4. Преобразование выходного тезнора TfLiteTensor в вектор чисел std::vector<float>. Обратное преобразование для выдачи приложению массива чисел с предсказаниями.

Теперь достаточно запустить граф и отправить в него данные, так как все части составленного графа уже реализованы.

Запуск графа на десктопе похож на рассмотренный пример выше. Разница в предобработке входных данных и постобработке выходных. Ниже представлен код, загружающий фотографию, отправляющий ее в граф и выводящий индекс наиболее вероятного класса.

// Загрузка изображения
auto img_mat = cv::imread("./inference/img.jpg");
// Преобразование изображения в пакет
auto input_frame = std::make_unique<mediapipe::ImageFrame>(
    mediapipe::ImageFormat::SRGB, img_mat.cols, img_mat.rows,
    mediapipe::ImageFrame::kDefaultAlignmentBoundary);
cv::Mat input_frame_mat = mediapipe::formats::MatView(input_frame.get());
img_mat.copyTo(input_frame_mat);
auto frame = mediapipe::Adopt(input_frame.release()).At(mediapipe::Timestamp(0));
// Отправка пакета в граф
MP_RETURN_IF_ERROR(graph.AddPacketToInputStream("in", frame));
MP_RETURN_IF_ERROR(graph.CloseInputStream("in"));
// Получение результата их графа с выводом предсказания
mediapipe::Packet packet;
while (poller.Next(&packet)) {
    auto predictions = packet.Get<std::vector<float>>();
    int idx = std::max_element(predictions.begin(), predictions.end()) - predictions.begin();
    std::cout << idx << std::endl;
}

Теперь можем запустить код и проверить, что изображено на фотографии:

$ bazel-2.0.0 build --define MEDIAPIPE_DISABLE_GPU=1 //inference/desktop:Inference
...
INFO: Build completed successfully, 3 total actions
$ ./bazel-bin/inference/desktop/Inference
INFO: Initialized TensorFlow Lite runtime.
151

151 метка в ImageNet соответствует «Chihuahua».

Теперь получим такое же поведение на смартфоне. Нам нужно написать Activity, которая при запуске (метод onCreate) загружает файл с графом, а затем инициализирует Mediapipe. В данном случае граф хранится в бинарном protobuf-представлении. В приложении есть кнопка, по нажатию на которую открывается галерея для выбора изображения. После выбора выполняется метод onActivityResult, который выбранную фотографию отправляет в граф. Также важно не забыть загрузить нативную Mediapipe библиотеку в конструкторе объекта-компаньона (статический конструктор).

class MainActivity : AppCompatActivity() {
    val PICK_IMAGE = 1
    var mpGraph: Graph? = null
    var timestamp = 0L
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        this.setContentView(R.layout.activity_main)
        val outputTv = findViewById<TextView>(R.id.outputTv)
        val button = findViewById<Button>(R.id.selectButton)
        AndroidAssetUtil.initializeNativeAssetManager(this)
        // Загрузка и инициализация графа
        // В данном случае граф преобразуется в бинарный формат
        val graph = Graph()
        assets.open("mobile_binary_graph.binarypb").use {
            val graphBytes = it.readBytes()
            graph.loadBinaryGraph(graphBytes)
        }
        // Подписка на выходной поток
        graph.addPacketCallback("out") {
            val res = PacketGetter.getFloat32Vector(it)
            val label = res.indices.maxBy { i -> res[i] } ?: -1
            this@MainActivity.runOnUiThread {
                outputTv.text = label.toString()
            }
        }
        graph.startRunningGraph()
        // Кнопка для выбора изображения из галереи
        button.setOnClickListener {
            val intent = Intent()
            intent.type = "image/*"
            intent.action = Intent.ACTION_GET_CONTENT
            startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE)
        }
        mpGraph = graph
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == PICK_IMAGE) {
            // Получение выбранного изображения из галереи и его отрисовка
            val outputTv = findViewById<TextView>(R.id.outputTv)
            val imageView = findViewById<ImageView>(R.id.imageView)
            val uri = data?.data!!
            // Отправка изображения в граф
            val graph = mpGraph!!
            val creator = AndroidPacketCreator(graph)
            val stream = contentResolver.openInputStream(uri)
            val bitmap = BitmapFactory.decodeStream(stream)
            imageView.setImageBitmap(bitmap)
            val packet = creator.createRgbImageFrame(bitmap)
            graph.addPacketToInputStream("in", packet, timestamp)
        }
    }
    companion object {
        init {
            // Загрузка нативной mediapipe библиотеки
            System.loadLibrary("mediapipe_jni")
        }
    }
}

Соберем приложение и загрузим его на смартфон для отладки:

$ bazel-2.0.0 mobile-install --start_app -c opt --config=android_arm64 //inference/android/src/main/java/com/mediapipe_demonstration/inference:Inference

В приложении можно выбрать фото из галереи и предсказать, что же на фотографии изображено:

Результат аналогичен тому, который был получен для десктоп приложения.

В данном разделе приведены только участки кода, полный код примеров можно посмотреть на github

Более сложные и практические примеры расположены в репозитории Mediapipe

Теперь рассмотрим более практический пример. Представим сервис по распознаванию моделей автомобилей. Есть приложение, через которое можно сфотографировать авто и отправить на сервер. Есть сервер, который для присланной фотографии запускает модель классификации и отдает полученный результат на клиент. Количество пользователей растет, нагрузка на сервер тоже и требуется решать проблему производительности. Перед нами встает два варианта:

  • Добавить еще мощности для сервера, докупить GPU, добавить несколько серверов;
  • Перенести часть вычислений на клиент.

Выбираем второй вариант, для его реализации можно воспользоваться Mediapipe. Теперь приложение должно не только снимать автомобиль, но также находить его в кадре и переводить его в некоторый вектор чисел, потому что передавать по сети около тысячи чисел проще, чем целое изображение. Далее такой вектор будем называть дескриптором. Нам потребуется две модели: модель детекции автомобиля в кадре и модель для получения дескриптора из участка кадра, на котором первая модель обнаружила автомобиль.

За основу приложения может быть использован пример для Mediapipe, решающий задачу детекции. В качестве сети для получения дескрипторов возьмем классифицирующую сеть, обученную на ImageNet1k, дообучим её для классификации автомобилей на Stanford Cars Dataset. После этого дескриптор автомобиля можно будет получить перед первым полносвязным слоем. Схематичная схема сети:

На этапе обучения сеть оптимизируется через выход с logit-ами с помощью NLL loss. Затем «голову» сети можно откинуть и получать дескрипторы после mean pool.

Структура проекта включает в себя Android приложение, калькуляторы для Mediapipe, Mediapipe граф, модель для получения дескрипторов и серверный код.

.
├── Android/src/main/java/com/kshmax/objectrecognition
│   └── objectrecognition
│       ├── AndroidManifest.xml
│       ├── BUILD
│       ├── MainActivity.kt
│       ├── Models.kt
│       ├── ObjectDetectionFrameProcessor.java
│       └── res
│           └── ...
├── Calculators
│   ├── BoundaryBoxCropCalculator.cpp
│   ├── BUILD
│   ├── ClickLocation.proto
│   ├── DetectionFilterCalculator.cpp
│   ├── DetectionFilterCalculator.proto
├── Graphs
│   ├── BUILD
│   └── ObjectRecognitionGraph.pbtxt
├── Models
│   ├── BUILD
│   └── car_vectorizer.tflite
├── Server
│   ├── labels.npy
│   ├── server.py
│   └── vectors.npy
└── WORKSPACE

Mediapipe граф для приложения выглядит следующим образом:

Калькуляторы на белом фоне взяты из оригинального примера, а калькуляторы на голубом фоне добавлены мной.

Представленный граф гораздо объемнее, чем рассмотренные ранее. Это связано с тем, что данные стали потоковыми, предобработка и постобработка увеличилась. Частота видеопотока ограничивается производительностью модели детекции: пока сеть определяет автомобили на одном кадре, все последующие кадры откидываются. Это реализовано с помощью FlowLimiter и обратного ребра. На выход из графа в данной задаче поступает видеопоток с наложенными рамками машин. AnnotationOverlay накладывает на исходный кадр рамку, полученную в результате работы модели детекции. После этого кадр отправляется в приложение и выводится на экран.

В этот процесс был добавлен DetectionFilter, потому что в результате работы модели может быть много различных классов, а для решения задачи нужен только класс автомобиль. Для этого я написал калькулятор DetectionFilter, который на вход принимал массив детекшенов, а на выходе были только те, классы которых указаны в настройках калькулятора в графе.

Метод Process для DetectionFilter калькулятора представлен ниже:

mediapipe::Status DetectionFilterCalculator::Process(mediapipe::CalculatorContext *cc) {
    const auto& input_detections = cc->Inputs().Get("", 0).Get<std::vector<::mediapipe::Detection>>();
    std::vector<::mediapipe::Detection> output_detections;
    for (const auto& input_detection : input_detections) {
        bool next_detection = false;

        for (int pass_id : pass_ids_) {
            for (int label_id : input_detection.label_id()) {
                if (pass_id == label_id) {
                    output_detections.push_back(input_detection);
                    next_detection = true;
                    break;
                }
            }
            if (next_detection) {
                break;
            }
        }
    }
    auto out_packet = mediapipe::MakePacket<std::vector<mediapipe::Detection>>(output_detections).At(cc->InputTimestamp());
    cc->Outputs().Get("", 0).AddPacket(out_packet);

    return mediapipe::OkStatus();
}

DetectionFilterCalculator добавляется в граф следующим образом:

node {
    calculator: "DetectionFilterCalculator"
    input_stream: "filtered_detections"
    output_stream: "car_detections"
    node_options: {
        [type.googleapis.com/objectrecognition.DetectionFilterCalculatorOptions] {
            pass_id: 3
        }
    }
}

Теперь пользователь на экране увидит только обнаруженные автомобили. Когда пользователь захочет узнать модель автомобиля, он нажимает по рамке, в которую автомобиль обведен.

Информация о нажатии передается внутрь графа через входной поток ScreenTap, а затем попадает в BoundaryBoxCrop. Важно заметить, что пакеты из ScreenTap также проходят через FlowLimiter, хотя очевидно, что они приходят в граф гораздо реже, чем пакеты с кадрами видео из камеры. Это связано с тем, что входные потоки должны быть синхронизированы, иными словами, обработка пакетов начнется только тогда, когда на вход поступит кадр и информация о нажатии по экрану. Отправкой кадров из камеры в граф занимается стандартный класс FrameProcessor, поэтому для отправки кадров и координат нажатия мне понадобилось написать собственный ObjectDetectionFrameProcessor.

Сначала я пытался отправлять в входной поток ScreenTap пакеты только при нажатии на экран, но столкнулся с непонятным поведением, при котором через граф проходил только один пакет, а потом ни один калькулятор не вызывался и в приложении был «черный экран». Решением данной проблемы стала синхронная отправка в граф пакета с кадром и пакета с информацией о нажатии. Чаще всего пакет с информацией о нажатии пустой, но в случае, когда пользователь нажимает на экран, то пакет заполняется координатами экрана (причем, в данной задаче удобно, чтобы координаты были нормализованы в [0, 1]).

BoundaryBoxCrop на входе проверяет, что в пакет с информацией по нажатию не пустой. Если он не пустой, то из DetectionFilter берутся полученные рамки, из FlowLimiter берется текущий кадр, предварительно пересланный из GPU в CPU. Этой информации достаточно, чтобы определить, по какой рамке было произведено нажатие, а затем вырезать её из общего кадра.

mediapipe::Status BoundaryBoxCropCalculator::Process(mediapipe::CalculatorContext *cc) {
    auto& detections_packet = cc->Inputs().Get("DETECTION", 0);
    auto& frames_packet = cc->Inputs().Get("IMAGE", 0);
    auto& click_packet = cc->Inputs().Get("CLICK", 0);

    if (detections_packet.IsEmpty()
        || frames_packet.IsEmpty()
        || click_packet.IsEmpty()) {
        return mediapipe::OkStatus();
    }

    const std::vector<mediapipe::Detection>& detections =
            detections_packet.Get<std::vector<mediapipe::Detection>>();
    const mediapipe::ImageFrame& image_frame = frames_packet.Get<mediapipe::ImageFrame>();

    // Java код записывает сериализованный protobuf как строку, поэтому его нужно вручную разбирать.
    auto click_location_str = click_packet.Get<std::string>();
    objectrecognition::ClickLocation click_location;
    click_location.ParseFromString(click_location_str);
    // Нажатия не было, выходим без дальнейшей обработки
    if (click_location.x() == -1 || click_location.y() == -1) {
        return mediapipe::OkStatus();
    }

    // Определение по какой рамке было нажатие
    absl::optional<mediapipe::Detection> detection = FindOverlappedDetection(click_location, detections);

    if (detection.has_value()) {
        // если нажатие было по рамке, то изображение внутри рамки вырезается
        std::unique_ptr<mediapipe::ImageFrame> cropped_image = CropImage(image_frame, detection.value());
        cc->Outputs().Get("", 0).Add(cropped_image.release(), cc->InputTimestamp());
    }

    return mediapipe::OkStatus();
}

absl::optional<mediapipe::Detection> BoundaryBoxCropCalculator::FindOverlappedDetection(
        const objectrecognition::ClickLocation& click_location,
        const std::vector<mediapipe::Detection>& detections) {
    for (const auto& input_detection : detections) {
        const auto& b_box = input_detection.location_data().relative_bounding_box();

        if (b_box.xmin() < click_location.x() && click_location.x() < (b_box.xmin() + b_box.width())
            && b_box.ymin() < click_location.y() && click_location.y() < (b_box.ymin() + b_box.height())) {
            return input_detection;
        }
    }

    return absl::nullopt;
}

std::unique_ptr<mediapipe::ImageFrame> BoundaryBoxCropCalculator::CropImage(
        const mediapipe::ImageFrame& image_frame,
        const mediapipe::Detection& detection) {
    const uint8* pixel_data = image_frame.PixelData();
    const auto& b_box = detection.location_data().relative_bounding_box();

    int height = static_cast<int>(b_box.height() * static_cast<float>(image_frame.Height()));
    int width = static_cast<int>(b_box.width() * static_cast<float>(image_frame.Width()));
    int xmin = static_cast<int>(b_box.xmin() * static_cast<float>(image_frame.Width()));
    int ymin = static_cast<int>(b_box.ymin() * static_cast<float>(image_frame.Height()));

    if (xmin < 0) {
        width += xmin;
        xmin = 0;
    }
    if (ymin < 0) {
        height += ymin;
        ymin = 0;
    }
    if (width > image_frame.Width()) {
        width = image_frame.Width();
    }
    if (height > image_frame.Height()) {
        height = image_frame.Height();
    }

    std::vector<uint8_t> pixels;
    pixels.reserve(height * width * image_frame.NumberOfChannels());
    for (int y = ymin; y < ymin + height; ++y) {
        int row_offset = y * image_frame.WidthStep();
        for (int x = xmin; x < xmin + width; ++x) {
            for (int ch = 0; ch < image_frame.NumberOfChannels(); ++ch) {
                pixels.push_back(pixel_data[row_offset + x * image_frame.NumberOfChannels() + ch]);
            }
        }
    }

    std::unique_ptr<mediapipe::ImageFrame> cropped_image = std::make_unique<mediapipe::ImageFrame>();
    cropped_image->CopyPixelData(image_frame.Format(), width, height, pixels.data(),
            mediapipe::ImageFrame::kDefaultAlignmentBoundary);

    return cropped_image;
}

После этого вырезанное изображение необходимо предобработать стандартными калькуляторами и запустить модель для получения дескриптора. Полученный дескриптор конвертируется из TfLiteTensor в массив чисел и отправляется на выход из графа в приложение.

Приложение (Java код) получает массив, отправляет его по HTTP на сервер. Сервер представляет из себя простой сервис, который находит для данного дескриптора находит ближайший к нему среди «эталонных» дескрипторов автомобилей и отвечает клиенту названием модели ближайшего вектора. Близость может определяться разными способами, в данном случае использовалось косинусное расстояние (косинус между двумя векторами).

Всё, что остается клиенту — это вывести пользователю результат распознавания. Вот как это выглядит на практике:

Демонстрация проведена на трех автомобилях. Первые две модели распознаны правильно (сервер содержит вектора для camry и hummer), а вот с ford focus вышла ошибка, т.к. на сервере нет вектора-эталона, тем не менее был выбран самый близкий (Hyundai SantaFe).

В верхнем ряду представлены 4 фотографии, по которым вычислялись эталонные дескрипторы, на нижней фотографии была проведена демонстрация.

Итак, в результате получено приложение, выполняющее всю ресурсоемкую работу по детекции и распознаванию, а также простенький сервер, оперирующий только массивами чисел и близостью между векторами. При этом по сети изображения в явном виде не передается, а передается более сжатое представление, что позволяет пользоваться сервисом при медленном соединении.

Заключение

В статье рассмотрены примеры реализаций приложений, использующих в своей работе модели машинного обучения с помощью Mediapipe, продемонстрирован подход к реализации приложений, предназначенных для распознавания, анализа изображений, звуков и текста. При этом удалось реализовать real-time детекцию на устройстве без передачи больших картинок через мобильный интернет. Естественным образом такая концепция улучшает UX, ведь пользователь видит, что именно он отправляет в поиск. При этом рассмотренный пример для распознавания автомобилей является прототипом и демонстрацией proof-of-concept, поэтому содержит недочеты и направления для развития:

  • Вырезание изображения и запуск сети для получения вектора выполняются на CPU, тогда как часть с детекцией, взятая из оригинального примера, выполняется на GPU. Перенос всего на GPU позволит ускорить выполнение и сэкономить на пересылках между CPU и GPU;
  • Добавление трекинга и выполнение распознавания не по нажатию на рамку, а в реальном времени. Это добавит интерактивности и позволит распознавать несколько автомобилей одновременно;
  • Использование квантованных дескрипторов;
  • Перенесение облечненного индекса на клиент для ответа на популярные запросы без необходимости совершать запрос;
  • Оптимизация поиска ближайшего эталона на сервере. Вместо линейного поиска ближайшего вектора могут применяться алгоритмы ANN (Approximate Nearest Neighbors).

MediaPipe official documentation

MEDIAPIPE Framework Learning —Win10 Installing MediaPipe Environment

MediaPipe Framework 2 — Android SDK and NDK Configuration

MediaPipe Framework Three — Build MediaPipe Android Aar Package

MediaPipe Framework Learn — Using MediaPipe AAR Pack, build MediaPipe-based gesture identification app in AS

Attach the project document:handtrackinggpu.zip

Install Windows Subsystem for Linux (WSL), subsystem of Win10 Linux

1. Search and install the subsystem Windows Sysystem for Linux (Ubuntu) in Microsoft Store Applers

  • Note: DefaultInstalled in CDon’t change! ! !
  • Note: The following steps are all executed in WSL.

After the installation is complete, turn on the WSL to initialize the username, password ** of the ** WSL.
Subsystem default installation directory:
C: \ users \ Computer username \ appdata \ local \ packages \ canonicalGroupLimited.ubuntuonWindows_Salary version \ localstate \ rootfs \ home \ WSL username

2. Update the APT-GET, install the necessary dependencies

sudo apt-get update && sudo apt-get install -y build-essential git python zip adb openjdk-8-jdk

3. Install the ADB of Win10 and WSL

  • WSL ADB installation:
sudo apt-get install android-tools-adb
  • Win10 ADB installation:
  1. View WSL ADB version
adb version
  1. Download the corresponding version ADB (I have three versions of ADB here, 1 point is a hard fee)
    1.0.26 version
    1.0.32 version
    1.0.39 version
  2. Unzip 3 files:adb.exe、AdbWinApi.dll、AdbWinUsbApi.dll
  3. Copy 3 files toC:\Windows\System32 、C:\Windows\SysWOW64 Down
  4. WIN + R = «Enter CMD, enter the command window, enteradb versionViewing the version
  • Note: Win10 and WSLThe ADB version should be consistent. For example, if the WSL ADB version is 1.0.39, the ADB version of Win10 should be 1.0.39.

4. Install Bazel

curl -sLO --retry 5 --retry-max-time 10 \
https://storage.googleapis.com/bazel/0.27.0/release/bazel-0.27.0-installer-linux-x86_64.sh && \
sudo mkdir -p /usr/local/bazel/0.27.0 && \
chmod 755 bazel-0.27.0-installer-linux-x86_64.sh && \
sudo ./bazel-0.27.0-installer-linux-x86_64.sh --prefix=/usr/local/bazel/0.27.0 && \
source /usr/local/bazel/0.27.0/lib/bazel/bin/bazel-complete.bash
/usr/local/bazel/0.27.0/lib/bazel/bin/bazel version && \
alias bazel='/usr/local/bazel/0.27.0/lib/bazel/bin/bazel'

Bundlealias bazel='/usr/local/bazel/0.27.0/lib/bazel/bin/bazel'write on /etc/profileIn the file, it is easy to use the bazel command directly:

sudo vim /etc/profile

according to a or A, Go in editing mode, copyalias bazel='/usr/local/bazel/0.27.0/lib/bazel/bin/bazel'Paste in the last side (The right mouse button is paste, don’t press Ctrl + V)。
according to EscExit edit mode, enter **: wq ** save and exit.
Execute the following command to take effect:

source /etc/profile

Can entercat /etc/profileThe command checks if the modification is successful.

5. Download MediaPipe Library to your local

git clone https://github.com/google/mediapipe.git
cd mediapipe

6. Install OpenCV and FFMPEG

sudo apt-get install libopencv-core-dev libopencv-highgui-dev \
                       libopencv-calib3d-dev libopencv-features2d-dev \
                       libopencv-imgproc-dev libopencv-video-dev

7. Run Hello World Desktop Routine

export GLOG_logtostderr=1
 # Because the desktop GPU does not support, you need to set the flag 'MediaPiPIPE_DISABLE_GPU' 1
bazel run --define MEDIAPIPE_DISABLE_GPU=1 \
    mediapipe/examples/desktop/hello_world:hello_world
# Should print:
# Hello World!
# Hello World!
# Hello World!
# Hello World!
# Hello World!
# Hello World!
# Hello World!
# Hello World!
# Hello World!
# Hello World!

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Intel high definition audio driver для windows 10 asus
  • Чем обрезать аудиофайл в windows
  • Невозможно запустить приложение на вашем пк windows 10 gta 5
  • Обновление windows 10 21р2
  • Самый простой файрвол для windows 10