# autojs摆正扑克用opencv

+关注继续查看

## 步骤

1) 高斯滤波平滑图像, 图像基本没变化

Imgproc.GaussianBlur(img.mat, gaussianBlurMat, Size(5, 5), 0);

2) 转灰度图

Imgproc.cvtColor(gaussianBlurMat, grayMat, Imgproc.COLOR_RGBA2GRAY);

3) Canny 边缘检测

Imgproc.Canny(grayMat, cannyMat, lowThreshold, lowThreshold * ratio, kernel_size, false);

4) 开运算

Imgproc.morphologyEx(
cannyMat,
closeMat,
Imgproc.MORPH_CLOSE,
Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(7, 7))
);

5) 画轮廓

Imgproc.findContours(closeMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, Point());
Imgproc.drawContours(img.mat, contours, -1, Scalar(0, 255, 0, 255), -1, 8);

6) 在灰度图上填充轮廓

Imgproc.drawContours(closeMat, contours, -1, Scalar(255), -1, 8);

7) 开运算, 只剩下最大的轮廓了

Imgproc.morphologyEx(
closeMat,
closeMat,
Imgproc.MORPH_OPEN,
Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(55, 55))
);

8) 最小外接矩形

for (let j = 0; j < 4; j++) {
Imgproc.line(img.mat, rect[j], rect[(j + 1) % 4], Scalar(0, 0, 255, 255), 5, 8);
}

9) 我决定倒退至 6)在灰度图上填充轮廓

10)除了最大的轮廓, 其他小轮廓都填充为黑色

// 先排序轮廓
contours.sort(
new Comparator({
compare: function (a1, a2) {
if (Imgproc.contourArea(a1) > Imgproc.contourArea(a2)) {
return 1;
}
if (Imgproc.contourArea(a1) < Imgproc.contourArea(a2)) {
return -1;
}
return 0;
},
})
);
// 再更改颜色
Imgproc.drawContours(closeMat, contours, -1, Scalar(0), -1, 8);
Imgproc.drawContours(closeMat, contours, contours.length - 1, Scalar(255), -1, 8);

11) Canny 边缘检测

12) 绘制四条边

// 霍夫变换直线检测
Imgproc.HoughLinesP(mat, lines, 1, Math.PI / 180, threshold, minLineSize, lineGap);
// 绘制直线
Imgproc.line(mat, startPoint, endPoint, new Scalar(blue, green, red, 255), 33);

[ { angle: 31.947479458708028,
distance: 209.77368757782756,
x1: 284,
y1: 235,
x2: 462,
y2: 124 },
{ angle: 32.005383208083494,
distance: 311.3213773578679,
x1: 285,
y1: 234,
x2: 549,
y2: 69 },
{ angle: 34.07719528013074,
distance: 328.3900120283807,
x1: 428,
y1: 332,
x2: 700,
y2: 148 },
{ angle: 327.89875110416443,
distance: 190.057885919001,
x1: 292,
y1: 245,
x2: 453,
y2: 346 },
{ angle: 333.96422503312334,
distance: 193.65174928205528,
x1: 527,
y1: 59,
x2: 701,
y2: 144 } ] 

1. 角度小于90度为一组, 有3个
2. 角度大于90度左右为一组, 有2个

13) 通过四条直线, 计算四个顶点

function getFocusCoordinatesOfTwoLines(line1, line2) {
var x1 = line1.x1;
var y1 = line1.y1;
var x2 = line1.x2;
var y2 = line1.y2;
var x3 = line2.x1;
var y3 = line2.y1;
var x4 = line2.x2;
var y4 = line2.y2;
var x =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) /
((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
var y =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) /
((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
return { x: x, y: y };
}

[
{ x: 279.72287662740234, y: 237.29820210787352 },
{ x: 548.3290322580646, y: 69.41935483870968 },
{ x: 429.2910186352809, y: 331.12666386436877 },
{ x: 703.8528728961114, y: 145.39364480557168 },
];

14) 确认四个顶点的上下左右

function getDistance(pointA, pointB) {
return Math.sqrt(Math.pow(pointA.x - pointB.x, 2) + Math.pow(pointA.y - pointB.y, 2));
}

[ { x: 279.72287662740234,
y: 237.29820210787352,
num: 0,
distance: 0 },
{ x: 429.2910186352809,
y: 331.12666386436877,
num: 2,
distance: 176.5627631729834 },
{ x: 548.3290322580646,
y: 69.41935483870968,
num: 1,
distance: 316.75317552174084 },
{ x: 703.8528728961114,
y: 145.39364480557168,
num: 3,
distance: 433.973157450812 } ] 

p1在p2的左侧, 那么扑克是对称的, 现在p3也在p4的左侧,

p3是左下角, p4是右下角

{
"topLeft": { "x": 279.72287662740234, "y": 237.29820210787352, "num": 0, "distance": 0 },
"topRight": { "x": 429.2910186352809, "y": 331.12666386436877, "num": 2, "distance": 176.5627631729834 },
"bottomLeft": { "x": 548.3290322580646, "y": 69.41935483870968, "num": 1, "distance": 316.75317552174084 },
"bottomRight": { "x": 703.8528728961114, "y": 145.39364480557168, "num": 3, "distance": 433.973157450812 }
}

15) 设计将要显示的扑克尺寸

let poker = {
width: 57,
height: 87,
scale: 6,
};
poker.width *= poker.scale;
poker.height *= poker.scale;

16) 透视变换

points = {
topLeft: new Point(0, 0),
topRight: new Point(poker.width, 0),
bottomLeft: new Point(0, poker.height),
bottomRight: new Point(poker.width, poker.height),
};
let endM = service.getEndM(points);
let perspectiveTransform = Imgproc.getPerspectiveTransform(startM, endM);
Imgproc.warpPerspective(img.mat, pokerAfterStraightening, perspectiveTransform, new Size(poker.width, poker.height));

Imgproc.cvtColor(pokerAfterStraightening, pokerAfterStraightening, Imgproc.COLOR_BGR2RGB);

17) 现在只显示了扑克内部的矩形, 我们把四个顶点向外扩张一点, 显示完整的扑克;

function extendEndPoint(startPoint, endPoint, distance) {
var A = [startPoint.x, startPoint.y];
var B = [endPoint.x, endPoint.y];
var aToB = [B[0] - A[0], B[1] - A[1]];
var aToBDistance = Math.sqrt(aToB[0] * aToB[0] + aToB[1] * aToB[1]);
var atoBNormalization = [aToB[0] / aToBDistance, aToB[1] / aToBDistance];
var newEndPoint = {
x: atoBNormalization[0] * distance + B[0],
y: atoBNormalization[1] * distance + B[1],
};
return newEndPoint;
}

18) 按比例扩张区域

OpenCV 形态学操作：膨胀与腐蚀
OpenCV 形态学操作：膨胀与腐蚀
14 0

36 0

98 0

1148 0
opencv的CMakeLists.txt与makefile写法
1264 0
opencv透视变换
opencv透视变换 实现透视变换 目标： 在这篇教程中你将学到： 1、如何进行透视变化 2、如何生存透视变换矩阵 理论： 什么是透视变换： 1、透视变换(Perspective Transformation)是将图片投影到一个新的视平面(Viewing Plane)，也称作投影映射(Projective Mapping)。
1442 0
OpenCV矩阵运算

1063 0
+关注
329

0