2020年9月4日金曜日

C++からUnityへ共有メモリを介して画像を送る

boostの共有メモリで画像を受け渡しする」でも紹介したけど, 共有メモリを使ってプログラムを2つに分けるとライブラリの依存関係やバージョン管理がシンプルになる場合がある. 今回は,C++のライブラリのみが提供されているカメラの画像をUnityのプログラムに送信するために, C++のプログラムからC#のプログラムに画像を送信するサンプルコードを掲載. 送信側を起動してから受信側を起動すると正常に動作する. 画像サイズは640$\times$480で,BGRAの順にデータが格納されているとする.ただし,これはWindowsのAPIを用いているためboostを用いたものと違ってWindows限定.

送信側(C++)

#include <windows.h>
#include <string>
#include <opencv2/opencv.hpp>

int main(int argc, char* argv[])
{
    std:: string MemoryName = "COLOR_IMAGE640x480";
    int w = 640;
    int h = 480;
    int size = w * h * 4;

    HANDLE memory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, MemoryName.c_str());
    uchar * data = (uchar *)MapViewOfFile(memory, FILE_MAP_ALL_ACCESS, NULL, NULL, size);

    cv::Mat im = cv::imread("image.png");
    cv::convert(im, im, cv::COLOR_BGR2BGRA);
    memcpy(&data[0], im.data, size);

    while (true) {
        cv::imshow("cpp", im);
        if (cv::waitKey(0) == 27) break;
    }
}

受信側(Unity)

using System.Collections;
using UnityEngine;
using System.Runtime.InteropServices;
using System;
using Microsoft.Win32.SafeHandles;

public class LR_seperated : MonoBehaviour
{
    Texture2D texture;
    string MemoryName = "COLOR_IMAGE640x480";
    static int w = 640;
    static int h = 480;
    static UInt32 size = (UInt32)(w * h * 4);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern SafeFileHandle OpenFileMapping(
        uint dwDesiredAccess,
        bool bInheritHandle,
        string lpName);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr MapViewOfFile(
        SafeFileHandle hFileMappingObject,
        UInt32 dwDesiredAccess,
        UInt32 dwFileOffsetHigh,
        UInt32 dwFileOffsetLow,
        UIntPtr dwNumberOfBytesToMap);

    const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
    const UInt32 SECTION_QUERY = 0x0001;
    const UInt32 SECTION_MAP_WRITE = 0x0002;
    const UInt32 SECTION_MAP_READ = 0x0004;
    const UInt32 SECTION_MAP_EXECUTE = 0x0008;
    const UInt32 SECTION_EXTEND_SIZE = 0x0010;
    const UInt32 SECTION_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SECTION_QUERY |
        SECTION_MAP_WRITE |
        SECTION_MAP_READ |
        SECTION_MAP_EXECUTE |
        SECTION_EXTEND_SIZE);
    const UInt32 FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS;
    private SafeFileHandle handle;
    private IntPtr buffer;

    // Start is called before the first frame update
    void Start()
    {
        texture = new Texture2D(w, h, TextureFormat.BGRA32, false);
        handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, MemoryName);
        buffer = MapViewOfFile(sHandle, FILE_MAP_ALL_ACCESS, 0, 0, new UIntPtr(size));
    }
    // Update is called once per frame
    void Update()
    {
        texture.LoadRawTextureData(buffer, (int)size);
        texture.Apply();
        GetComponent<Renderer>().material.mainTexture= texture;
    }
}