How to perform math operations with OpenCV Matrix and a point-like value in Javascript?

I want to scale a contour in OpenCV.js. I have a valid contour in cnt variable of type cv.Mat (verified it by using drawContours).

I found a function in Python that does everything I need but I have problems converting it to Javascript.

Python version:

def scale_contour(cnt, scale):
    M = cv2.moments(cnt)
    cx = int(M['m10']/M['m00'])
    cy = int(M['m01']/M['m00'])

    cnt_norm = cnt - [cx, cy]
    cnt_scaled = cnt_norm * scale
    cnt_scaled = cnt_scaled + [cx, cy]
    cnt_scaled = cnt_scaled.astype(np.int32)

    return cnt_scaled

Here’s what I started for Javascript:

function scaleContour(cnt, scale) {
    console.log("cnt", cnt.data32S, cnt.rows, cnt.cols, cnt.type());

    const M = cv.moments(cnt);
    const cx = M['m10']/M['m00'];
    const cy = M['m01']/M['m00'];
    const offset = [Math.ceil(cx), Math.ceil(cy)];
    console.log("Offset", offset);

    // cannot use convenient Python arithmetics here,
    // have to call functions
    // although technically we have 1 row 2 cols for a point, but the cnt type is 2-channel CV_32SC2 (12)
    // therefore keeping the size 1,1 and leave the second dimension as a channel to be compatible with the contour format
    const pointMat = cv.matFromArray(1, 1, cnt.type(), offset);
    console.log("pointMat", pointMat.data32S);

    const cntNorm = new cv.Mat(cnt.rows, cnt.cols, cnt.type());
    cv.subtract(cnt, pointMat, cntNorm); <-- my app crashes here with an exception that has only some random number - OpenCV seems to always do that when I'm doing something wrong or it's out of memory
    console.log("ctnorm", cntNorm.data32S);

Unfortunately, I cannot find a good example on Python-like matrix operations in the official OpenCV.js documentation on basic data structures. It just shows how to create matrices but does not explain how to perform simple math operations with a matrix and a point-like value.

Also, I’m not sure when I need new cv.Mat(cnt.rows, cnt.cols, cnt.type()); and when new cv.Mat() is enough. The documentation has both but does not answer what is the rule of thumb to use an empty Mat and when it must be configured with row/col/type.

And the log output for cnt cols and rows is confusing, it prints 75 rows and 1 col, but the data is Int32Array(150). I found that sometimes the second layer of values are designated by type and not cols/rows. That’s confusing. How should we know when to use rows=1,cols=2 and when rows=1,cols=2 and a type with 2 channels?

Ok, got something working. A bit ugly, not sure why I could not find a simpler way to multiply matrix with a constant. And also the fact that the point is treated as a 1,1 dimension element with 2 channels to make it match the contour format CV_32SC2 is very unintuitive.

function scaleContour(cnt, scale) {
    const M = cv.moments(cnt);
    const cx = M['m10']/M['m00'];
    const cy = M['m01']/M['m00'];
    const offset = [Math.ceil(cx), Math.ceil(cy)];

    // cannot use convenient Python arithmetics here,
    // have to call functions
    // although we have 1,2 but the type is 2-channel CV_32SC2 (12)
    // therefore keep the size 1,1 and leave the second dimension as channel
    const pointMat = cv.matFromArray(1, 1, cnt.type(), offset);
    // both matrices have to be the same size, so we repeat our point for every pair
    let pointRepeat = new cv.Mat();
    cv.repeat(pointMat, cnt.rows, 1, pointRepeat);

    let cntNorm = new cv.Mat();
    cv.subtract(cnt, pointRepeat, cntNorm);
 
    //console.log("cntNorm", cntNorm.size(), cntNorm.data32S);

    // let cntScaled = cntNorm.mulConstant(scale); // mulConstant is not a function - maybe in future version?
    // awkward - to multiply with scalar, need mul with (1,1,...) and a factor
    // Mat.ones does not work because
    // In case of multi-channels type, only the first channel will be initialized with 1's, the others will be set to 0's.
    let ones = new cv.Mat(cnt.rows, cnt.cols, cnt.type(), cv.Scalar.all(1));
    let cntScaled = cntNorm.mul(ones, scale);
    
    //console.log("cntScaled", cntScaled.size(), cntScaled.data32S);

    ones.delete();

    // reuse the same
    cntNorm.delete();
    cntNorm = new cv.Mat();
    cv.add(cntScaled, pointRepeat, cntNorm);

    cntScaled.delete();pointMat.delete();pointRepeat.delete();

    return cntNorm;
}
1 Like