Hi, I hope Javascript and Java are falling the the same category.
Using OpenCV.js I’m trying to identify the corners of a card mask in a picture. I asked 3 different LLM to generate the code for that but they all fail. Every time, it returns the outer picture (640x640). The card mask is represented as a quadrilateral. Not a rectangle, as it might have some slight inclination.
Here is an example.
All the solutions where based on findContours and approxPolyDP but none are working correctly. So I have decided to do it manually.
I’m wondering what will be the best logic here. I looked at approxPolyDP but I’m not sure it will work. The breaks on the contour will cause issues. It seems to also cause troubles to findContours. I tried to make the lines bigger, to fill the gaps, but some gaps are so big that it takes huge lines and they start touching the sides.
Is there any recommended approach?
- Connect the lines
- find the contours
Below is what I have so far, but it doesn’t work.
function approximateCardQuad(maskTensor, debugCanvas = null) {
const height = maskTensor.shape[0];
const width = maskTensor.shape[1];
// Convert tensor to binary mask mat
const maskData = tf.tidy(() => maskTensor.mul(255).toInt().dataSync());
const maskUint8 = new Uint8Array(maskData);
const maskMat = cv.matFromArray(height, width, cv.CV_8UC1, maskUint8);
const thresholdMat = new cv.Mat();
cv.threshold(maskMat, thresholdMat, 127, 255, cv.THRESH_BINARY);
const contours = new cv.MatVector();
const hierarchy = new cv.Mat();
cv.findContours(thresholdMat, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE);
console.log(`Found ${contours.size()} contours`);
console.log(`--- Logging Contours (${new Date().toLocaleString("en-CA", {timeZone: "America/Toronto"})}) ---`); // Laval is EST/EDT
console.log(`Total contours found: ${contours.size()}`);
// Iterate through each contour found
for (let i = 0; i < contours.size(); ++i) {
const contourMat = contours.get(i); // Get the cv.Mat for the i-th contour
// Get the number of points in this contour
const numPoints = contourMat.rows; // For contour Mats, rows usually equals number of points
console.log(`Contour ${i} has ${numPoints} points:`);
if (numPoints > 0) {
const points = [];
// Access the contour point data. It's typically stored as signed 32-bit integers.
// The layout is usually [x1, y1, x2, y2, x3, y3, ...] in the data32S array.
const contourData = contourMat.data32S;
for (let j = 0; j < numPoints; ++j) {
const x = contourData[j * 2]; // Get the x-coordinate
const y = contourData[j * 2 + 1]; // Get the y-coordinate
points.push({ x: x, y: y }); // Add point object to array
// --- OR --- Log each point individually (can be very verbose)
// console.log(` Point ${j}: (x: ${x}, y: ${y})`);
}
// Log the array of points for the current contour
// Using JSON.stringify can be helpful if contours have many points,
// otherwise, logging the array directly gives an expandable object.
// console.log(points);
console.log(JSON.stringify(points)); // More compact console output
} else {
console.log(" (Contour is empty)");
}
// IMPORTANT: Do NOT delete contourMat here if you got it using contours.get(i).
// The MatVector 'contours' owns these Mats. Deleting the MatVector later
// in your 'finally' block will handle cleanup.
// If you had *cloned* the contour, you would delete the clone.
} // End loop through contours
console.log("--- Finished Logging Contours ---");
let bestQuad = null;
let maxArea = 0;
// Optional debug draw
let debugMat;
if (debugCanvas) {
debugMat = new cv.Mat.zeros(height, width, cv.CV_8UC3);
}
for (let i = 0; i < contours.size(); i++) {
const cnt = contours.get(i);
const area = cv.contourArea(cnt);
if (area < 1000) continue; // Skip tiny noise
// Approximate with a polygon
const perimeter = cv.arcLength(cnt, true);
const approx = new cv.Mat();
cv.approxPolyDP(cnt, approx, 0.02 * perimeter, true);
console.log(`Contour ${i}: area=${area}, approxPts=${approx.rows}`);
// Optional draw for debug
if (debugCanvas) {
const color = new cv.Scalar(255, 0, 255); // Magenta
cv.drawContours(debugMat, contours, i, color, 1, cv.LINE_8, hierarchy, 100);
}
// Prefer 4-point polygon if possible
if (approx.rows === 4 && area > maxArea) {
maxArea = area;
bestQuad = approx.clone();
}
cnt.delete();
approx.delete();
}
// Show debug canvas
if (debugCanvas && debugMat) {
cv.imshow(debugCanvas, debugMat);
debugMat.delete();
}
thresholdMat.delete();
maskMat.delete();
contours.delete();
hierarchy.delete();
if (bestQuad) {
const result = [];
for (let i = 0; i < 4; i++) {
result.push({
x: bestQuad.intPtr(i, 0)[0],
y: bestQuad.intPtr(i, 0)[1]
});
}
bestQuad.delete();
return result;
}
console.warn("No valid quadrilateral found.");
return null;
}
Thaks,
JMS