M5UnitV2 から画像をブラウザへ配信する


## 概要
M5UnitV2はディスプレイを搭載していないため、OpenCVのcv::imshow()関数などでの確認ができません。画像処理の結果を確認するために、画像をブラウザで配信して確認できるようにします。

## 環境構築

構築手順に入る前に、まずPCに以下のバージョンのUbuntuをインストールした環境で、構築しました。

[M5UnitV2のクロスコンパイル環境を構築する手順](https://qiita.com/nnn112358/pbulic/9e15450265b689300216)

```
Ubuntu 22.04.3 LTS(x64)
OpenCV ver 0.4.4
ncnn ver 231027
```

## Cpp MJPEG Streamerのダウンロード

GitHubからnadjieb氏のcpp-mjpeg-streamerのコードをダウンロードします。
Cpp MJPEG Streamerは、OpenCVの Video画像をJPEGに変換して、HTTP Streamingで配信するためのプログラムです。Cpp MJPEG StreamerはC++のプログラムをヘッダファイルのみでコンパイルして、HTTP Streamingサーバを動かすことができます。

```
% mkdir example
% cd example
% curl -LO https://raw.githubusercontent.com/nadjieb/cpp-mjpeg-streamer/master/single_include/nadjieb/mjpeg_streamer.hpp
```

## CMakeLists.txt ファイルの作成

cmakeでコンパイルするために、CMakeLists.txtを作成します。

```
gedit CMakeLists.txt
```

```CMakeLists.txt
SET(CMAKE_BUILD_TYPE "Release")

SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

# Ubuntuでデバックするためにx64を選択、UnitV2ではarmを指定
if(TARGET_COMPILER STREQUAL "arm")
    message(STATUS "TARGET_COMPILE STREQUAL arm")
    SET(CMAKE_CXX_COMPILER arm-none-linux-gnueabihf-g++)
    SET(CMAKE_C_COMPILER arm-none-linux-gnueabihf-gcc)
    SET(NCNN_INSTALL_DIR /opt/external/ncnn/install/arm/)
    SET(ZBAR_INSTALL_DIR /opt/external/zbar-0.10/build/arm)
    SET(OpenCV_DIR /opt/external/opencv/build/arm)
else()
    message(STATUS "TARGET_COMPILE STREQUAL X64")
    SET(NCNN_INSTALL_DIR /opt/external/ncnn/install/x64/)
    SET(ZBAR_INSTALL_DIR /opt/external/zbar-0.10/build/x64/)
    SET(OpenCV_DIR /opt/external/opencv/build/x64) 
endif()


PROJECT(mpegstream)
cmake_minimum_required(VERSION 3.8)
set(CMAKE_CXX_STANDARD 17)

if(OpenCV_FOUND)
 message(OPENCV_VERSION)
endif(OpenCV_FOUND)

find_package(OpenCV REQUIRED)

link_directories(${OpenCV_LIBRARY_DIRS})

include_directories(${OpenCV_INCLUDE_DIRS} )
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${NCNN_INSTALL_DIR}/include/ncnn)

add_executable(mpegstreamer main.cpp)
target_link_libraries(mpegstreamer ${OpenCV_LIBRARIES})
target_link_libraries(mpegstreamer ${NCNN_INSTALL_DIR}/lib/libncnn.a -fopenmp)

```

## サンプルプログラム

MJPEG Streamerでサーバを建てるサンプルプログラムです。

```sh
% gedit main.cpp
```

```main.cpp
#include <opencv2/opencv.hpp>
#include <mjpeg_streamer.hpp>
#include <net.h>
#include <pthread.h>

using namespace std;
using namespace cv;

using MJPEGStreamer = nadjieb::MJPEGStreamer;

int main(int argc, char **argv) {

    int VideoPort = 0;
    bool low_quality_mode = true;

    if(argc >= 2) {
        VideoPort = atoi(argv[1]);
    }

    VideoCapture cap(VideoPort);

    if(!cap.isOpened()) {
        std::cout << "Cannot open cam" << std::endl;
        return -1;
    }

    cap.set(cv::CAP_PROP_FOURCC, cv::CAP_OPENCV_MJPEG);
    if(!cap.set(cv::CAP_PROP_FRAME_WIDTH, 640))
        std::cout << "camera set width error" << std::endl;
    if(!cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480))
        std::cout << "camera set height error" << std::endl;
    
    MJPEGStreamer streamer;
    std::vector<int> params = {cv::IMWRITE_JPEG_QUALITY, 90};
    streamer.start(7778);

    std::chrono::steady_clock::time_point Tbegin, Tend;

    
    while(cap.isOpened()) {
        Mat frame;
        Tbegin = std::chrono::steady_clock::now();
        cap >> frame;

        if(low_quality_mode == true)
            resize(frame, frame, Size(), 1.0f / 2.0f, 1.0f / 2.0f, INTER_AREA);

        Tend = std::chrono::steady_clock::now();
        float fps = std::chrono::duration_cast<std::chrono::milliseconds>(Tend - Tbegin).count();
      
        putText(frame, cv::format("FPS %0.2f", 1000.0/fps), cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 0, 255));
#ifdef __x86_64__
        imshow("frame", frame);
        const int key = cv::waitKey(1);
#endif
        std::vector<uchar> buff_bgr;
        cv::imencode(".jpg", frame, buff_bgr, params);
        streamer.publish("/frame", std::string(buff_bgr.begin(), buff_bgr.end()));
    }
    streamer.stop();
    return 0;
}
```


## ビルド

Ubuntu用のバイナリをcmakeでコンパイルして、生成されたバイナリを実行します。

```
% cmake -B build/x64 -DTARGET_COMPILE=x64 .
% cmake --build build/x64
% ./bin/x64/mpegstreamer
```

UnitV2(arm環境)で動くバイナリをビルドする場合は、Ubuntu用のバイナリで作成されたキャッシュを削除した上で、以下のコマンドでビルドします。

```
% cmake -B build/arm -DTARGET_COMPILE=arm .
% cmake --build build/arm
% ./bin/x64/mpegstreamer
```


For M5UnitV2(ARM)


プログラムを動かし、"http://127.0.0.1:7778/frame"へアクセスすると、カメラからのキャプチャ画像をブラウザへ配信して確認することができます。

## プログラムの格納先

このプログラムは以下に格納しています。
https://github.com/nnn112358/MJPEGStreamer

## 参考資料

この記事を作成するにあたり、以下のウェブサイトを参考にしました。

[nadjieb/cpp-mjpeg-streamer](https://github.com/nadjieb/cpp-mjpeg-streamer/blob/master/single_include/nadjieb/mjpeg_streamer.hpp)