Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

My purpose is to write a website (html/js/wasm) in which the user provides two jpg which are "compared" using OpenCV in wasm. By "compared", I mean : compute the key points, the descriptors, make a BF match and print the distances.

What I'm able to do is a pure c++ program reading two files on the disk and compare them. It's okay. With my two test image, the best matches have distances around 100 and the 10 best are bellow 200.

It turns out that, when trying in wasm, the keypoints are correctly computed, but the matches are bad. The best ones have distance around 200.

Here is my (minimal?) example.

The html part creates two canvas in which the images are stored.

<!doctype html>
<html>
<body>
<head>
<meta charset="utf-8">
<title>c++ generated</title>
</head>


<canvas id="viewport1"></canvas>
<input type='file' id='inputimage1' accept='image/*'>

<canvas id="viewport2"></canvas>
<input type='file' id='inputimage2' accept='image/*'>

<script>
var Module = {
  onRuntimeInitialized: function() {
    createQuery();
  }
};
</script>

<script src="josef.js"></script>
<script src="main.js"></script>

</body>
</html>

The JS part contains canvas manipulations and calls to c++. Two calls :

  • for the first image we call the function recordImage to store the cv::Mat in a global variable
  • for the second image, we call the function compare which compares the two images.
/* global Module */

function putOnHeap(imgData, wasmModule) {
    const uint8ArrData = new Uint8Array(imgData.data);
    const numBytes = uint8ArrData.length * uint8ArrData.BYTES_PER_ELEMENT;

    const dataPtr = wasmModule._malloc(numBytes);
    const dataOnHeap = new Uint8Array(wasmModule.HEAPU8.buffer, dataPtr, numBytes);
    dataOnHeap.set(uint8ArrData);

    answer = {"byteOffset": dataOnHeap.byteOffset,
            "length":uint8ArrData.length,
            "dataPtr": dataPtr,
            "dataOnHeap": dataOnHeap,
            "wasmModule":wasmModule,
            };
    return answer;
}

/*
    Call the c++ function on the given image.
*/
async function toCpp(canvas, wasmModule, functionName)
{
    const context = canvas.getContext('2d');
    const imgData = context.getImageData(0, 0,
                                         canvas.width, canvas.height);

    const heapInfo = putOnHeap(imgData, wasmModule);

    const func = wasmModule[functionName];
    func(heapInfo.byteOffset, heapInfo.length,
                            canvas.width, canvas.height);

    wasmModule._free(heapInfo.dataPtr);
    return answer;
}

/*
    Record the image on the wasm side.
*/
async function recordImage(canvas, wasmModule) {
    await toCpp(canvas, wasmModule, "recordImage");
}

/*
    Compare the image with the recorded one.
*/
async function compare(canvas, wasmModule) {
    await toCpp(canvas, wasmModule, "compare");
}


/* When the user provides the first image, wasm stores it
 * in a cv::Mat.
*/
async function doFirstCanvas() {
    const canvas = document.getElementById('viewport1');
    const width = this.width;
    const height = this.height;

    canvas.width = width;
    canvas.height = height;
    const context = canvas.getContext('2d');
    let imageData = context.getImageData(0, 0, width, height);
   context.putImageData(imageData, 0, 0);
    context.drawImage(this, 0, 0);

    await recordImage(canvas, Module);
}

/*
 * When the user provides the second image, wasm compares
 * that image with the first one.
 * 
*/
async function doSecondCanvas() {
    const canvas = document.getElementById('viewport2');
    const width = this.width;
    const height = this.height;
    canvas.width = width;
    canvas.height = height;
    const context = canvas.getContext('2d');
    let imageData = context.getImageData(0, 0, width, height);
   context.putImageData(imageData, 0, 0);
    context.drawImage(this, 0, 0);

    compare(canvas, Module);
}


function failed() { }


function createQuery() {
    document.getElementById('inputimage1').onchange = function () {
        console.log('First image received');
        const img = new Image();
        img.onload = doFirstCanvas;
        img.onerror = failed;
        img.src = URL.createObjectURL(this.files[0]);
    };

    document.getElementById('inputimage2').onchange = function () {
        console.log('Second image received');
        const img = new Image();
        img.onload = doSecondCanvas;
        img.onerror = failed;
        img.src = URL.createObjectURL(this.files[0]);
    };
}

The c++ part contains in particular the function get_image which reads the heap and create the corresponding cv::Mat.

#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/opencv.hpp>

// The first image is recorded in `recordedImage`
cv::Mat recordedImage;


/*
 * `get_image` reads the part of the heap indicated by JS.
 * Then it interprets it as a cv::Mat.
 * */
cv::Mat get_image(int offset, size_t size, int width, int height) {
  uint8_t* pos;
  pos = reinterpret_cast<uint8_t*>(offset);
  auto gotMatrix = cv::Mat(width, height, CV_8UC4, pos);
  return gotMatrix.clone();
}

/*The first image is recorded in `recordedImage`.*/
void recordImage(int offset, size_t size, int width, int height) {
    recordedImage = get_image(offset, size, width, height);
}


/*The second image is compared to the first one.*/
void compare(int offset, size_t size, int width, int height) {
  std::cout << "===== c++ comparing the images  ======" << std::endl; 

  auto image1 = recordedImage;
  auto image2 = get_image(offset, size, width, height);
  
    auto orbDetector = cv::ORB::create(500);
    cv::BFMatcher matcher(cv::NORM_L2);

    std::vector<cv::KeyPoint> kpts1;
    std::vector<cv::KeyPoint> kpts2;
    cv::Mat descr1;
    cv::Mat descr2;

    orbDetector -> detectAndCompute(image1, cv::noArray(), kpts1, descr1);
    orbDetector -> detectAndCompute(image2, cv::noArray(), kpts2, descr2);

    std::vector<cv::DMatch> matches;
    matcher.match(descr1, descr2, matches);

    for (auto & match : matches) {
        std::cout << match.distance << std::endl;
    }
}


int main() {
  std::cout << "Run main in c++." << std::endl;
  return 0;
}

// Export the functions.
EMSCRIPTEN_BINDINGS(my_module) {
  emscripten::function("main", &main);
  emscripten::function("compare", &compare);
  emscripten::function("recordImage", &recordImage);
}

I already tried to print the cv::Mat and, indeed, the images I have in this version are different than the ones in pure c++. The difference is that canvas adds a "alpha" channel and OpenCV permutes the R and B channels. I already made some pre-manipulations in JS to fix these differences, and checked that the matrices are the exact same matrices in the html/js/wasm version as in the c++ version (as far as I can check). I did not included these manipulations in the example here.

My question : the values of match.distance are much larger than the values obtained with the same image using a pure c++ program reading on the disk (cv::imread). Why ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
124 views
Welcome To Ask or Share your Answers For Others

1 Answer

The function "get_image" is broken for several reasons. Here is my solution

cv::Mat get_image(int offset, size_t size, int width, int height) {
  // Some comments about the implementation
  // - People in the doc do not need the cast trick
  //   for the position. Why ???
  // - The inversion height <--> width is crucial
  // - I'm not sure how useful the line `cvtColor` is.
  // - The `clone` trick is crucial. If not, the returned matrix is 
  //   filled with references to the heap. In that case, if one "send"
  //   a second matrix from js to wasm, the second matrix "erases" the
  //   first one.
  uint8_t* position = reinterpret_cast<uint8_t*>(offset);
  auto gotMatrix = cv::Mat(height, width, CV_8UC4, position);
  cv::Mat newImage;
  cv::cvtColor(gotMatrix, newImage, cv::COLOR_RGBA2BGR );
  return newImage.clone();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...