Canvas绘制图片和区域(前端使用Canvas绘制图片,并在图片上绘制区域)

简介:在Web开发中,有时候我们需要在图片上进行一些交互式操作,比如绘制区域、标记等。这种场景下,我们可以使用HTML5的<canvas>元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口,可以通过 JavaScript 在网页上绘制图形、动画和其他视觉效果。这里来记录一下,如何在一张图片上,绘制区域

先看一下效果:

如何使用Canvas在图片上绘制区域?


初始化Canvas

一. 首先,我们需要初始化三个canvas画布

initCanvas() {// 初始化canvas画布let canvasWrap = document.getElementsByClassName("canvas-wrap");this.wrapWidth = canvasWrap[0].clientWidth;this.wrapHeight = canvasWrap[0].clientHeight;this.imgCanvas = document.getElementById("imgCanvas");this.imgCtx = this.imgCanvas.getContext("2d");// 绘制canvasthis.drawCanvas = document.getElementById("drawCanvas");this.drawCtx = this.drawCanvas.getContext("2d");// 保存绘制区域 saveCanvasthis.saveCanvas = document.getElementById("saveCanvas");this.saveCtx = this.saveCanvas.getContext("2d");
}
  1. imgCanvas用于绘制原始图片
  2. drawCanvas用于临时绘制区域
  3. saveCanvas用于保存最终绘制的区域


二. 计算并设置canvas的宽高比例,以适应图片尺寸

initImgCanvas() {// 计算宽高比let ww = this.wrapWidth; // 画布宽度let wh = this.wrapHeight; // 画布高度 let iw = this.imgWidth; // 图片宽度let ih = this.imgHeight; // 图片高度if (iw / ih < ww / wh) {// 以高为主this.ratio = ih / wh;this.canvasHeight = wh;this.canvasWidth = (wh * iw) / ih;} else {// 以宽为主 this.ratio = iw / ww;this.canvasWidth = ww;this.canvasHeight = (ww * ih) / iw;}// 初始化画布大小this.imgCanvas.width = this.canvasWidth;this.imgCanvas.height = this.canvasHeight;this.drawCanvas.width = this.canvasWidth; this.drawCanvas.height = this.canvasHeight;this.saveCanvas.width = this.canvasWidth;this.saveCanvas.height = this.canvasHeight;// 图片加载绘制let img = document.createElement("img");img.src = this.imgUrl;img.onload = () => {console.log("图片已加载");this.imgCtx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight);this.renderDatas(); // 渲染原有数据};
}

这里先计算画布和图片的宽高比,根据比例关系决定以宽为主还是以高为主进行等比缩放。然后设置三个canvas的宽高,并在图片加载完成后将其绘制到imgCanvas上。renderDatas函数用于渲染已有的绘制数据(如果有的话)。


开始绘制

三. 绘制的主要逻辑

startDraw() {// 绘制区域if (this.isDrawing) return;this.isDrawing = true;// 绘制逻辑this.drawCanvas.addEventListener("click", this.drawImageClickFn);this.drawCanvas.addEventListener("dblclick", this.drawImageDblClickFn);this.drawCanvas.addEventListener("mousemove", this.drawImageMoveFn);
}

我们在drawCanvas上监听clickdblclickmousemove事件,分别对应点击、双击和鼠标移动三种绘制交互。


四. 点击事件用于开始一个新的区域绘制

drawImageClickFn(e) {let drawCtx = this.drawCtx;if (e.offsetX || e.layerX) {let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {this.drawingPoints.push([pointX, pointY]);}}
}

这里获取鼠标点击的坐标,并将其推入drawingPoints数组中,用于临时保存当前绘制区域的点坐标。


五. 鼠标移动事件用于实时绘制区域

drawImageMoveFn(e) {let drawCtx = this.drawCtx;if (e.offsetX || e.layerX) {let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;// 绘制drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);// 绘制点drawCtx.fillStyle = "blue";this.drawingPoints.forEach((item, i) => {drawCtx.beginPath();drawCtx.arc(...item, 6, 0, 180);drawCtx.fill(); //填充});// 绘制动态区域drawCtx.save();drawCtx.beginPath();this.drawingPoints.forEach((item, i) => {drawCtx.lineTo(...item);});drawCtx.lineTo(pointX, pointY);drawCtx.lineWidth = "3";drawCtx.strokeStyle = "blue";drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";drawCtx.stroke();drawCtx.fill(); //填充drawCtx.restore();}
}

这里先清空drawCanvas,然后遍历drawingPoints数组,绘制已经点击的点。接着再绘制一个动态区域,即从第一个点开始,连线到当前鼠标位置,形成一个闭合多边形区域。


六. 双击事件用于完成当前区域的绘制

drawImageDblClickFn(e) {let drawCtx = this.drawCtx;let saveCtx = this.saveCtx;if (e.offsetX || e.layerX) {let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {this.drawingPoints.push([pointX, pointY]);}}// 清空绘制图层drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);// 绘制区域至保存图层this.drawSaveArea(this.drawingPoints);this.drawedPoints.push(this.drawingPoints);this.drawingPoints = [];this.isDrawing = false;// 绘制结束逻辑this.drawCanvas.removeEventListener("click", this.drawImageClickFn);this.drawCanvas.removeEventListener("dblclick", this.drawImageDblClickFn);this.drawCanvas.removeEventListener("mousemove", this.drawImageMoveFn);
}

双击时,先获取双击的坐标点,并将其推入drawingPoints数组中。然后清空drawCanvas,并调用drawSaveArea方法,将当前绘制区域渲染到saveCanvas上。


七. 遍历区域点坐标的方法

drawSaveArea(points) {if (points.length === 0) return;this.saveCtx.save();this.saveCtx.beginPath();points.forEach((item, i) => {this.saveCtx.lineTo(...item);});this.saveCtx.closePath();this.saveCtx.lineWidth = "2";this.saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";this.saveCtx.strokeStyle = "red";this.saveCtx.stroke();this.saveCtx.fill(); this.saveCtx.restore();
}

drawSaveArea方法会遍历当前区域的所有点坐标,并在saveCanvas上绘制一个闭合的多边形区域,边框为红色,填充为半透明的紫色。接下来,将当前绘制区域的点坐标数组drawingPoints推入drawedPoints数组中,用于保存所有已绘制的区域数据。然后,重置drawingPointsisDrawing的状态,并移除所有绘制事件的监听器。

至此,一个区域的绘制就完成了。如果需要继续绘制新的区域,只需再次调用startDraw方法即可。


保存和渲染数据

八. 保存数据:我们需要将绘制的区域数据保存下来,以及从已有数据中渲染出区域

savePoints() {// 将画布坐标数据转换成提交数据let objectPoints = [];objectPoints = this.drawedPoints.map((area) => {let polygon = {};area.forEach((point, i) => {polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio);polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio);});return {polygon: polygon,};});this.submitData = objectPoints;console.log("最终提交数据", objectPoints);
}

这里遍历所有已绘制的区域drawedPoints,将每个区域的点坐标根据ratio进行缩放(实际图片尺寸),并转换成一个polygon对象的形式,最终保存在submitData中。


九. 渲染数据

renderDatas() {// 将提交数据数据转换成画布坐标this.drawedPoints = this.submitData.map((item) => {let polygon = item.polygon;let points = [];for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {points.push([polygon[`x${i}`] / this.ratio,polygon[`y${i}`] / this.ratio,]);}}this.drawSaveArea(points);return points;});
}

渲染数据的逻辑是,遍历submitData中的每个polygon对象,根据ratio将其坐标值转换成canvas的坐标值,并调用drawSaveArea方法将其渲染到saveCanvas上。至此,我们就完成了在canvas上绘制图片区域的全部逻辑。可以根据具体需求进行相应的调整和扩展。


十. 执行过程

具体全部的执行顺序如下:

  1. 初始化Canvas
    • 调用initCanvas()方法初始化三个Canvas画布
    • 调用initImgCanvas()方法计算并设置画布宽高比例,加载并绘制图片
  2. 开始绘制
    • 调用startDraw()方法
    • 监听drawCanvasclickdblclickmousemove事件
    • 点击时,在drawImageClickFn中记录点坐标
    • 移动时,在drawImageMoveFn中实时绘制区域
    • 双击时,在drawImageDblClickFn中完成当前区域绘制,保存至saveCanvas
  3. 保存和渲染数据
    • 调用savePoints()方法,将绘制区域的点坐标数据转换并保存到submitData
    • 调用renderDatas()方法,将submitData中的数据转换并渲染到saveCanvas

简单来说,就是先初始化画布,然后开始绘制区域的交互,最后保存和渲染数据。


十一. 当然,如果想使用原生JS实现,可以改成像下面这样

let canvasWrap, wrapWidth, wrapHeight, imgCanvas, imgCtx, drawCanvas, drawCtx, saveCanvas, saveCtx;
let ratio, canvasWidth, canvasHeight, imgWidth, imgHeight, imgUrl;
let isDrawing = false;
let drawingPoints = [];
let drawedPoints = [];
let submitData = [];// 1. 初始化Canvas画布
function initCanvas() {// 获取canvas容器元素并设置宽高canvasWrap = document.getElementsByClassName("canvas-wrap")[0];wrapWidth = canvasWrap.clientWidth;wrapHeight = canvasWrap.clientHeight;// 获取canvas元素并获取2D绘图上下文imgCanvas = document.getElementById("imgCanvas");imgCtx = imgCanvas.getContext("2d");drawCanvas = document.getElementById("drawCanvas");drawCtx = drawCanvas.getContext("2d");saveCanvas = document.getElementById("saveCanvas");saveCtx = saveCanvas.getContext("2d");
}// 2. 初始化图片Canvas
function initImgCanvas() {// 计算画布和图片的宽高比let ww = wrapWidth;let wh = wrapHeight;let iw = imgWidth;let ih = imgHeight;if (iw / ih < ww / wh) {ratio = ih / wh;canvasHeight = wh;canvasWidth = (wh * iw) / ih;} else {ratio = iw / ww;canvasWidth = ww;canvasHeight = (ww * ih) / iw;}// 设置三个canvas的宽高imgCanvas.width = canvasWidth;imgCanvas.height = canvasHeight;drawCanvas.width = canvasWidth;drawCanvas.height = canvasHeight;saveCanvas.width = canvasWidth;saveCanvas.height = canvasHeight;// 加载图片并绘制到imgCanvas上let img = document.createElement("img");img.src = imgUrl;img.onload = () => {console.log("图片已加载");imgCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);renderDatas(); // 渲染已有数据};
}// 3. 开始绘制
function startDraw() {if (isDrawing) return;isDrawing = true;// 监听drawCanvas的click、dblclick和mousemove事件drawCanvas.addEventListener("click", drawImageClickFn);drawCanvas.addEventListener("dblclick", drawImageDblClickFn);drawCanvas.addEventListener("mousemove", drawImageMoveFn);
}// 4. 清空所有绘制区域
function clearAll() {saveCtx.clearRect(0, 0, canvasWidth, canvasHeight);drawedPoints = [];
}// 5. 获取并加载图片
function getImage() {imgUrl = "需要渲染的图片地址";imgWidth = 200;imgHeight = 300;imgUrl && initImgCanvas();
}// 6. 点击事件,记录点坐标
function drawImageClickFn(e) {if (e.offsetX || e.layerX) {let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;let lastPoint = drawingPoints[drawingPoints.length - 1] || [];if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {drawingPoints.push([pointX, pointY]);}}
}// 7. 鼠标移动事件,实时绘制区域
function drawImageMoveFn(e) {if (e.offsetX || e.layerX) {let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);drawCtx.fillStyle = "blue";drawingPoints.forEach((item, i) => {drawCtx.beginPath();drawCtx.arc(...item, 6, 0, 180);drawCtx.fill();});drawCtx.save();drawCtx.beginPath();drawingPoints.forEach((item, i) => {drawCtx.lineTo(...item);});drawCtx.lineTo(pointX, pointY);drawCtx.lineWidth = "3";drawCtx.strokeStyle = "blue";drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";drawCtx.stroke();drawCtx.fill();drawCtx.restore();}
}// 8. 双击事件,完成当前区域绘制
function drawImageDblClickFn(e) {if (e.offsetX || e.layerX) {let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;let lastPoint = drawingPoints[drawingPoints.length - 1] || [];if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {drawingPoints.push([pointX, pointY]);}}drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);drawSaveArea(drawingPoints);drawedPoints.push(drawingPoints);drawingPoints = [];isDrawing = false;drawCanvas.removeEventListener("click", drawImageClickFn);drawCanvas.removeEventListener("dblclick", drawImageDblClickFn);drawCanvas.removeEventListener("mousemove", drawImageMoveFn);
}// 9. 绘制区域到saveCanvas
function drawSaveArea(points) {if (points.length === 0) return;saveCtx.save();saveCtx.beginPath();points.forEach((item, i) => {saveCtx.lineTo(...item);});saveCtx.closePath();saveCtx.lineWidth = "2";saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";saveCtx.strokeStyle = "red";saveCtx.stroke();saveCtx.fill();saveCtx.restore();
}// 10. 保存绘制数据
function savePoints() {let objectPoints = [];objectPoints = drawedPoints.map((area) => {let polygon = {};area.forEach((point, i) => {polygon[`x${i + 1}`] = Math.round(point[0] * ratio);polygon[`y${i + 1}`] = Math.round(point[1] * ratio);});return {polygon: polygon,};});submitData = objectPoints;console.log("最终提交数据", objectPoints);
}// 11. 渲染已有数据
function renderDatas() {drawedPoints = submitData.map((item) => {let polygon = item.polygon;let points = [];for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {points.push([polygon[`x${i}`] / ratio, // 根据ratio换算canvas坐标polygon[`y${i}`] / ratio,]);}}drawSaveArea(points); // 调用drawSaveArea将区域绘制到saveCanvas上return points;});
}// 使用方式
initCanvas(); // 1. 初始化Canvas画布
getImage(); // 5. 获取并加载图片 
startDraw(); // 3. 开始绘制

renderDatas函数的作用是将已有的绘制数据(submitData)转换成canvas坐标,

并调用drawSaveArea方法将其渲染到saveCanvas上。

该函数遍历submitData中的每个polygon对象,

根据ratio将其坐标值转换成canvas的坐标值,

然后调用drawSaveArea方法绘制该区域。

最终返回一个包含所有区域点坐标的数组drawedPoints

最后,需要按顺序调用initCanvas() -> getImage() -> startDraw()等方法,分别完成初始化画布、加载图片和开始绘制的功能。

十二. 全部代码

全部的vuejs代码和原生js代码直接点我头像,私我,获取全部代码。
 

创作不易,感觉有用,就一键三连,感谢(●'◡'●)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/835832.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Agisoft Metashape Pro for Mac/win:开启三维建模新视界

在当今数字化的时代&#xff0c;三维建模技术正发挥着越来越重要的作用。而 Agisoft Metashape Pro for Mac/win 无疑是该领域的一颗璀璨明星。 这款强大的三维建模软件为专业人士和爱好者提供了无与伦比的工具和功能。无论你是从事建筑设计、考古研究、影视特效制作还是地理信…

智能体Agent笔记

智能体的定义&#xff1a; 1. 可以感受环境中的动态条件 2. 能采取动作影响环境 3. 能运用推理能力理解信息&#xff0c;解决问题&#xff0c;产生推断&#xff0c;决定动作。 我个人觉得智能体是个饼&#xff0c;最核心的问题是&#xff0c;你如何解决大模型的幻觉问题&…

笨方法自学python(三)-数学计算

数字和数学计算 这章练习里有很多的数学运算符号。我们来看一遍它们都叫什么名字 plus 加号-minus 减号/ slash 斜杠*asterisk 星号% percent 百分号< less-than 小于号greater-than 大于号< less-than-equal 小于等于号 greater-than-equal 大于等于号 print ("I …

20232906 2023-2024-2 《网络与系统攻防技术》第九次作业

20232906 2023-2024-2 《网络与系统攻防技术》第九次作业 1.实验内容 本次实践的对象是一个名为pwn1的linux可执行文件。 该程序正常执行流程是&#xff1a;main调用foo函数,foo函数会简单回显任何用户输入的字符串。 该程序同时包含另一个代码片段&#xff0c;getShell&am…

暗区突围哪里获得测试资格 暗区突围测试资格获取方法

在游戏业界的浩瀚星空中&#xff0c;《暗区突围》如同一颗璀璨新星&#xff0c;以其独树一帜的游戏模式和前所未有的沉浸式体验&#xff0c;迅速吸引了全球玩家的目光。它不仅仅是一款游戏&#xff0c;更像是一次对勇气、智慧与团队合作的深度探索。玩家在危机四伏的暗区中&…

程序人生 | 人生如棋,落子无悔

人生的开始&#xff0c;始于哭声&#xff0c;浮浮沉沉几十年。终了&#xff0c;一声长叹&#xff0c;在一片哭声中撒手离去。 人生的道路虽然漫长&#xff0c;但是关键就是那么几次机会的选择&#xff0c;可以决定此后几十年的光阴。 有个故事讲&#xff1a;古代有个人去砍柴…

干货教程【AI篇】| 目前全球最强AI换脸工具swapface详细图文教程及整合包下载

需要这个工具整合包的小伙伴可以关注一下文章底部公众号&#xff0c;回复关键词【swapface】即可获取。 从我们的链接下载&#xff0c;得到这个exe文件 双击运行即可进入安装界面 如下图所示已经在安装中啦 安装好之后我们根据上面的安装路径找到要执行的文件 双击红框中的…

三国杀背后的图形化编程 变量跟踪与吐槽的故事

在周末的公司里&#xff0c;卧龙凤雏等几位员工终于结束了加班任务&#xff0c;他们每个人都显现出些许疲惫之态&#xff0c;但心情还算较为轻松愉悦。突然&#xff0c;有人提议玩上几局三国杀&#xff0c;以此来让大家放松一下身心。于是乎&#xff0c;几人纷纷掏出手机&#…

MVC:一种设计模式而非软件架构

在软件开发领域&#xff0c;MVC&#xff08;Model-View-Controller&#xff09;经常被提及&#xff0c;但很多人对其定位存在误解。本文将澄清一个常见的误区&#xff1a;MVC是一种设计模式&#xff0c;而非软件架构。 一、MVC简介 MVC&#xff0c;即模型&#xff08;Model&a…

24寸2K显示器 - HKC G24H2

&#x1f525;&#x1f5a5;️ 嘿&#xff0c;大家好&#xff01;今天&#xff0c;我要给大家介绍一款超棒的显示器——HKCG24H2&#xff01;这款显示器可是个全能选手&#xff0c;无论你是工作狂人还是游戏迷&#xff0c;它都能满足你的需求&#xff01; &#x1f60e;&#x…

ICode国际青少年编程竞赛- Python-2级训练场-基础训练3

ICode国际青少年编程竞赛- Python-2级训练场-基础训练3 1、 d Item.x - Dev.x Dev.step(d)2、 d Spaceship.x - Item.x Spaceship.step(d)3、 d Item.y - Dev.y Dev.step(d)4、 for i in range(4):Spaceship.step(2)d Item[i].x - Dev.xDev.step(d)Dev.step(-d)5、…

VMware 不能拍摄快照

问题&#xff1a; 拍摄快照后&#xff0c;会出现这个弹窗&#xff0c;然后虚拟机就直接自动退出了&#xff0c;还会弹出一个框&#xff1a; 解决方法&#xff1a; 我用的是 window11 和 VMware16.0.0 这是因为VM16与window11&#xff0c;二者之间版本不兼容问题&#xff0c;可…

【CTF Web】XCTF GFSJ0477 backup Writeup(备份文件+源码泄漏+目录扫描)

backup X老师忘记删除备份文件&#xff0c;他派小宁同学去把备份文件找出来,一起来帮小宁同学吧&#xff01; 解法 使用 dirsearch 扫描目录。 dirsearch -u http://61.147.171.105:49361/下载&#xff1a; http://61.147.171.105:64289/index.php.bak打开 index.php.bak&am…

西蓝花病害检测(yolov8模型,从图像、视频和摄像头三种路径识别检测,包含登陆页面、注册页面和检测页面)

1.基于最新的YOLOv8训练的西蓝花病害检测模型&#xff0c;和基于PyQt5制作的可视化西兰花病害检测系统&#xff0c;包含登陆页面、注册页面和检测页面&#xff0c;该系统可自动检测和识别图片或视频当中出现的三类西兰花病害&#xff1a;Downy Mildew, Black Rot, Bacterial Sp…

vuex核心概念-mutations

目录 一、mutations基本认知 二、mutations的基本使用 三、mutations传参语法 四、注意 五、辅助函数&#xff1a;mapMutations 一、mutations基本认知 目标&#xff1a;明确vuex同样遵循单向数据流&#xff0c;组件中不能直接修改仓库的数据。 通过 strict:true可以开启…

Linux —— 信号(4)

Linux —— 信号&#xff08;4&#xff09; 信号的处理用户态和内核态 信号的捕捉sigaction sa_mask字段volatileSIGCHLD信号 我们今天接着来看信号&#xff1a; 信号的处理 信号的处理简单一句话就是在内核态处理的。 用户态和内核态 用户态和内核态是操作系统和计组中的概…

最新版Ceph( Reef版本)文件存储简单对接k8s(下集)

假如ceph集群已经创建 1.创建cephfs_pool存储池 ceph osd pool create fs_kube_data 16 162.创建cephfs_metadata存储池 ceph osd pool create fs_kube_metadata 16 163 创建cephfs ceph fs new cephfs01 fs_kube_metadata fs_kube_data4 设置最大活动数 ceph fs set cephfs01…

fatal: fetch-pack: invalid index-pack output

解决方案&#xff1a;git clone --depth1 要克隆的git地址 下载最近一次提交的代码 其他分支的内容都不下载 这样整体下载体量就变小了 执行命令&#xff1a;git clone --depth 1 https://gitlab.scm321.com/ufx/xxxx.git

Mac idea gradle解决异常: SSL peer shut down incorrectly

系统&#xff1a;mac 软件&#xff1a;idea 解决异常: SSL peer shut down incorrectly 查看有没有安装 gradle -v安装 根据项目gradle提示安装版本 brew install gradle7idea的配置 在settings搜索gradle&#xff0c;配置Local installation&#xff0c;选择自己的安装目录…

机器学习入门到放弃2:朴素贝叶斯

1. 算法介绍 1.1 算法定义 朴素贝叶斯分类&#xff08;NBC&#xff09;是以贝叶斯定理为基础并且假设特征条件之间相互独立的方法&#xff0c;先通过已给定的训练集&#xff0c;以特征词之间独立作为前提假设&#xff0c;学习从输入到输出的联合概率分布&#xff0c;再基于学习…