opencv.js 透视变换
Posted
技术标签:
【中文标题】opencv.js 透视变换【英文标题】:opencv.js perspective transform 【发布时间】:2018-07-25 22:19:18 【问题描述】:我正在尝试使用 opencv.js 在提供的图像中查找文档(检测边缘、应用透视变换等。
我有一组合理的代码,可以(偶尔)检测文档的边缘并为此抓取边界框。但是,我正在努力执行透视变换步骤。有一些帮助者(不在 JS 中)here 和 here。
不幸的是,我遇到了一些简单的事情。我可以找到匹配的Mat
,它有 4 个边。显示表明它是准确的。但是,我不知道如何从 Mat
中获取一些简单的 X/Y 信息。我认为minMaxLoc()
会是一个不错的选择,但我在匹配的Mat
中不断收到错误消息。知道为什么我可以绘制 foundContour
并从中获取边界框信息,但我不能调用 minMaxLoc
吗?
代码:
//<Get Image>
//<Convert to Gray, do GaussianBlur, and do Canny edge detection>
let contours = new cv.MatVector();
cv.findContours(matDestEdged, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE);
//<Sort resulting contours by area to get largest>
let foundContour = null;
for (let sortableContour of sortableContours)
let peri = cv.arcLength(sortableContour.contour, true);
let approx = new cv.Mat();
cv.approxPolyDP(sortableContour.contour, approx, 0.1 * peri, true);
if (approx.rows == 4)
console.log('found it');
foundContour = approx
break;
else
approx.delete();
//<Draw foundContour and a bounding box to ensure it's accurate>
//TODO: Do a perspective transform
let result = cv.minMaxLoc(foundContour);
上面的最后一行导致运行时错误 (Uncaught (in promise): 6402256 - Exception catching is disabled
)。我可以在其他 Mat
对象上运行 minMaxLoc()
。
【问题讨论】:
不是很明白,但是发现上面的foundContour
是一个2通道的Mat
对象。看来minMaxLoc
只能在单通道Mat
上调用。我不确定如何转换它,但我能够从foundContour.data32S[]
数组中获取 X/Y 数据。可能有更好的方法,但这和我目前得到的一样好。
【参考方案1】:
对于希望在 OpenCV.JS 中执行此操作的其他人,我上面评论的内容似乎仍然是准确的。找到的轮廓不能和minMaxLoc
一起使用,但是可以从data32S[]
中拉出X/Y数据。这应该是进行这种透视变换所需的全部内容。部分代码如下。
//Find all contours
let contours = new cv.MatVector();
let hierarchy = new cv.Mat();
cv.findContours(matDest, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE);
//Get area for all contours so we can find the biggest
let sortableContours: SortableContour[] = [];
for (let i = 0; i < contours.size(); i++)
let cnt = contours.get(i);
let area = cv.contourArea(cnt, false);
let perim = cv.arcLength(cnt, false);
sortableContours.push(new SortableContour( areaSize: area, perimiterSize: perim, contour: cnt ));
//Sort 'em
sortableContours = sortableContours.sort((item1, item2) => return (item1.areaSize > item2.areaSize) ? -1 : (item1.areaSize < item2.areaSize) ? 1 : 0; ).slice(0, 5);
//Ensure the top area contour has 4 corners (NOTE: This is not a perfect science and likely needs more attention)
let approx = new cv.Mat();
cv.approxPolyDP(sortableContours[0].contour, approx, .05 * sortableContours[0].perimiterSize, true);
if (approx.rows == 4)
console.log('Found a 4-corner approx');
foundContour = approx;
else
console.log('No 4-corner large contour!');
return;
//Find the corners
//foundCountour has 2 channels (seemingly x/y), has a depth of 4, and a type of 12. Seems to show it's a CV_32S "type", so the valid data is in data32S??
let corner1 = new cv.Point(foundContour.data32S[0], foundContour.data32S[1]);
let corner2 = new cv.Point(foundContour.data32S[2], foundContour.data32S[3]);
let corner3 = new cv.Point(foundContour.data32S[4], foundContour.data32S[5]);
let corner4 = new cv.Point(foundContour.data32S[6], foundContour.data32S[7]);
//Order the corners
let cornerArray = [ corner: corner1 , corner: corner2 , corner: corner3 , corner: corner4 ];
//Sort by Y position (to get top-down)
cornerArray.sort((item1, item2) => return (item1.corner.y < item2.corner.y) ? -1 : (item1.corner.y > item2.corner.y) ? 1 : 0; ).slice(0, 5);
//Determine left/right based on x position of top and bottom 2
let tl = cornerArray[0].corner.x < cornerArray[1].corner.x ? cornerArray[0] : cornerArray[1];
let tr = cornerArray[0].corner.x > cornerArray[1].corner.x ? cornerArray[0] : cornerArray[1];
let bl = cornerArray[2].corner.x < cornerArray[3].corner.x ? cornerArray[2] : cornerArray[3];
let br = cornerArray[2].corner.x > cornerArray[3].corner.x ? cornerArray[2] : cornerArray[3];
//Calculate the max width/height
let widthBottom = Math.hypot(br.corner.x - bl.corner.x, br.corner.y - bl.corner.y);
let widthTop = Math.hypot(tr.corner.x - tl.corner.x, tr.corner.y - tl.corner.y);
let theWidth = (widthBottom > widthTop) ? widthBottom : widthTop;
let heightRight = Math.hypot(tr.corner.x - br.corner.x, tr.corner.y - br.corner.y);
let heightLeft = Math.hypot(tl.corner.x - bl.corner.x, tr.corner.y - bl.corner.y);
let theHeight = (heightRight > heightLeft) ? heightRight : heightLeft;
//Transform!
let finalDestCoords = cv.matFromArray(4, 1, cv.CV_32FC2, [0, 0, theWidth - 1, 0, theWidth - 1, theHeight - 1, 0, theHeight - 1]); //
let srcCoords = cv.matFromArray(4, 1, cv.CV_32FC2, [tl.corner.x, tl.corner.y, tr.corner.x, tr.corner.y, br.corner.x, br.corner.y, bl.corner.x, bl.corner.y]);
let dsize = new cv.Size(theWidth, theHeight);
let M = cv.getPerspectiveTransform(srcCoords, finalDestCoords)
cv.warpPerspective(matDestTransformed, finalDest, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
作为参考,这是我用于SortableContour
的类定义。上面的代码只是作为一个指南,而不是作为可以独立运行的东西。
export class SortableContour
perimiterSize: number;
areaSize: number;
contour: any;
constructor(fields: Partial<SortableContour>)
Object.assign(this, fields);
【讨论】:
在这种情况下matDestTransformed
是什么?是原图源吗?
我的名字不好。是的,那将是您要处理的图像源。我从一大块其他处理代码中获取了这个,在我的例子中,它是另一个进程的输出(但是这个进程的输入)。
我在 let sortableContours: sortableContour[] = [];错误:意外的令牌:??
@NccWarp9 - 我添加了我对 SortableContour
类型的定义。但是,请注意,代码的目的是作为讲述大部分故事的指南,而不是作为端到端运行的解决方案。
我认为代码有一个小错误 new sortableContour 应该是 new SortableContour以上是关于opencv.js 透视变换的主要内容,如果未能解决你的问题,请参考以下文章