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);


上图中一共有5条直线, 直线数据如下:

[ { 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 } ] 


上面按照distance排序, 前两个为一组, 后两个为一组

假设四个点是p1, p2, p3, p4, 观察可知

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

那么就可以对四个点排序了

假设p1是左上角,

那么p2是右上角

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) 设计将要显示的扑克尺寸

百度得知中国扑克尺寸是57:87的比例, 我们放大6倍

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) 按比例扩张区域

四个顶点按比例扩张, 结果如下:

图片有一点变形, 基本达到预期


名人名言
思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问 --- 牙叔教程


声明
部分内容来自网络 本教程仅用于学习, 禁止用于其他用途














相关文章
|
机器学习/深度学习 算法 数据可视化
智能扑克牌识别软件(Python+YOLOv5深度学习模型+清新界面)
智能扑克牌识别软件(Python+YOLOv5深度学习模型+清新界面)
1694 0
|
1月前
|
JavaScript API 数据安全/隐私保护
5 大主流电商商品详情解析实战手册:淘宝 / 京东 / 拼多多 / 1688 / 唯品会核心字段提取 + 反爬应对 + 代码示例
本文详解淘宝、京东、拼多多、1688、唯品会五大电商平台商品详情页的数据解析逻辑,涵盖价格、SKU、库存、供应商等核心字段提取,针对各平台动态渲染、字体加密、API调用、反爬机制等难点提供完整代码与应对策略,助力开发者高效实现电商数据采集与分析。
|
8月前
|
自然语言处理 JavaScript 前端开发
《深度剖析:开发鸿蒙原生应用,为何ArkTS是最优之选》
ArkTS 是鸿蒙原生应用开发的核心语言,基于 TypeScript 深度扩展,具备强大的静态检查和类型系统,有效提升代码稳定性。其声明式语法简洁高效,助力快速构建复杂用户界面;多维度状态管理机制灵活掌控应用状态,支持全局与跨设备数据同步。此外,ArkTS 与 ArkUI 深度集成,优化分布式场景下的多设备协同开发体验,并通过完善工具链降低开发门槛。随着持续演进,ArkTS 将进一步推动鸿蒙生态繁荣,为开发者带来更高效的解决方案。
313 0
|
10月前
|
人工智能 自然语言处理 API
UI-TARS:字节跳动开源专注于多平台 GUI 自动化交互的视觉语言模型
UI-TARS 是字节跳动推出的新一代原生图形用户界面(GUI)代理模型,支持跨平台自动化交互,具备强大的感知、推理、行动和记忆能力,能够通过自然语言指令完成复杂任务。
2665 16
UI-TARS:字节跳动开源专注于多平台 GUI 自动化交互的视觉语言模型
|
9月前
|
存储 弹性计算 关系型数据库
【赵渝强老师】达梦数据库的产品系列
达梦数据库是达梦公司推出的新一代自研数据库,融合分布式、弹性计算与云计算优势,支持超大规模并发事务处理和HTAP混合业务。产品体系包括DM8、DMDSC、DM DataWatch、DMMPP和DMRWC,分别适用于通用关系型数据库、共享存储集群、数据守护集群、大规模数据分析及读写分离场景,满足不同需求并保障高可用性和安全性。
448 36
|
11月前
|
存储 安全 UED
插上U盘后提示格式化怎么解决?4个方法帮你
在使用u盘的时候,很多人都可能遇到过电脑插入U盘后系统弹出提示要求将U盘格式化。面对这个问题,如果U盘里有重要的数据,会让人感到不知所措。今天的内容就和大家一起讨论一下这个问题的原因和解决方法,并提供恢复U盘数据的方法。
|
监控 搜索推荐 语音技术
测试使用SenseVoice大模型测评
测试使用SenseVoice大模型测评
350 4
|
图形学 数据可视化 开发者
超实用Unity Shader Graph教程:从零开始打造令人惊叹的游戏视觉特效,让你的作品瞬间高大上,附带示例代码与详细步骤解析!
【8月更文挑战第31天】Unity Shader Graph 是 Unity 引擎中的强大工具,通过可视化编程帮助开发者轻松创建复杂且炫酷的视觉效果。本文将指导你使用 Shader Graph 实现三种效果:彩虹色渐变着色器、动态光效和水波纹效果。首先确保安装最新版 Unity 并启用 Shader Graph。创建新材质和着色器图谱后,利用节点库中的预定义节点,在编辑区连接节点定义着色器行为。
1422 1
|
Ubuntu 开发工具 git
在Ubuntu 18.04上安装Git【快速入门】
在Ubuntu 18.04上安装Git【快速入门】
250 0
|
JavaScript
【vue2项目总结】——vant-ui的使用
【vue2项目总结】——vant-ui的使用
372 0