vue 获取摄像头拍照,并旋转、裁剪生成新的图片

描述:
vue项目中,获取摄像头进行拍照,并对拍摄的图片进行旋转、裁剪等处理

html部分

<!-- 摄像头列表 -->
<el-select v-model="autoVal" size="small" @change="change('auto', true)"><el-optionv-for="item in vidList":key="item.deviceId":value="item.deviceId":label="item.label"/>
</el-select><!-- 拍照按钮 -->
<el-button size="small" type="primary" @click="getImg('auto')">拍照</el-button><!-- 拍照画面显示区 -->
<div id="right-bottom" v-loading="loading" class="right-bottom"><video v-show="videoFalg" id="video" ref="videoElement" autoplay :srcObject="videoSource" /><video v-show="false" id="videoElement" ref="video" autoplay :srcObject="videoSource" /><img id="img" src="" alt=""><div v-show="!a3Ora4 && !isNewModel" id="videoboder" class="videoboder" /><canvas v-show="false" id="canvas" /><!-- <video ref="videoElement" autoplay id="video"></video> -->
</div>

第一步:初始化

data() {resolutionRatio: '2592x1944',a3Ora4: false,videoSource: null,acrossOrvertical: true, // 横版autoVal: '',videoFalg: false,constraints: {},val: '',loading: false,imgLoading: false,autoSelectedIndex: '',vidList: [],
},
mounted() {this.getMediaInfo()
},
methods: {// 第一步getMediaInfo() {if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {this.constraints = {video: {width: { ideal: 2592 },height: { ideal: 1944 },deviceId: ''}}// 初始化this.getUserMedia(this.constraints, this.deSuccess, this.error, '0')} else {alert('不支持访问用户媒体')}},

第二步:getUserMedia

getUserMedia(constraints, success, error, type) {this.videoFalg = type !== '0'if (navigator.mediaDevices.getUserMedia) {// 最新的标准APInavigator.mediaDevices.getUserMedia(constraints).then(success).catch(error)} else if (navigator.webkitGetUserMedia) {// webkit核心浏览器navigator.webkitGetUserMedia(constraints, success, error)} else if (navigator.mozGetUserMedia) {// firfox浏览器navigator.mozGetUserMedia(constraints, success, error)} else if (navigator.getUserMedia) {// 旧版APInavigator.getUserMedia(constraints, success, error)}
},
success(stream) {const video = document.getElementById('video')const el = document.getElementById('videoElement')const videoElement = this.$refs.videoElement// 兼容webkit核心浏览器if (this.videoFalg) {this.videoSource = streamvideoElement.srcObject = streamel.srcObject = stream}this.loading = falsevideo.onloadedmetadata = (e) => {video.play()}
},
error(err) {console.log(err)
},
deSuccess(stream) {// 获取拍照设备列表this.getDevice()
}

第三步,获取拍照设备列表

getDevice() {if (!navigator.mediaDevices?.enumerateDevices) {console.log('不支持')} else {navigator.mediaDevices.enumerateDevices().then((devices) => {devices.forEach((device) => {if (device.kind === 'videoinput') {this.vidList.push(device)}})if (this.vidList.length && localStorage.getItem('videoSelectId')) {// 默认选中获取上次选择的设备this.val = localStorage.getItem('videoSelectId')this.change(this.val)}}).catch((err) => {console.error(`${err.name}: ${err.message}`)})}
},

切换摄像头时执行(一定要先释放上一次使用的摄像头,再切换新的)

async change() {// 如果 videoSource 不为空,则先释放摄像头!!!!(重点)if (this.videoSource !== null) {this.videoSource.getTracks().forEach(function (track) {track.stop()})this.videoSource = null}// 这里用定时器,是为了保证上次的摄像头已经完全释放,再切换到新的设备(重点)setTimeout(() => {const index = this.resolutionRatio.lastIndexOf('x')const srtSart = this.resolutionRatio.substring(0, index)const srtEnd = this.resolutionRatio.substring(index + 1, val.length)// 存储选中的设备id localStorage.setItem('videoSelectId', this.autoVal)this.constraints = {video: {width: { ideal: srtSart * 1 },height: { ideal: srtEnd * 1 },deviceId: { exact: this.autoVal }}}}this.loading = truethis.getUserMedia(this.constraints, this.success, this.error, '1')}, 1000)},

拍照,并将拍照的图片根据需要进行旋转,获得新的图片

注意:以下代码中包含多个业务逻辑,A3/A4、横版/竖版、旋转指定角度、自动裁剪(opencv.js)、自动裁剪识别失败后自动弹出手动裁剪弹窗(cropperjs)等,可按需获取, 此处只做简单记录

getImg(type) {if (!this.val && !this.autoVal) {this.$message.warning('请先选择设备')return}this.imgLoading = trueconst el = document.getElementById('videoElement')const canvas = document.getElementById('canvas')var dpr = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1let base64Url = ''const context = canvas.getContext('2d')// 清空画布context.clearRect(0, 0, canvas.width, canvas.height)context.scale(dpr, dpr)if (this.isNewModel || !this.isNewModel && this.a3Ora4) {console.log('进入a3')console.log('容器宽高')console.log('el.videoWidth', el.videoWidth)console.log('el.videoHeight', el.videoHeight)canvas.width = el.videoWidth * dprcanvas.height = el.videoHeight * dprconsole.log('容器宽高*dpr')console.log('canvas.width', canvas.width)console.log('canvas.height', canvas.height)console.log('el.videoWidth * dpr', el.videoWidth * dpr)console.log('el.videoHeight * dpr', el.videoHeight * dpr)context.drawImage(el, 0, 0, el.videoWidth, el.videoHeight, 0, 0, canvas.width, canvas.height)const imgdata = context.getImageData(0, 0, canvas.width, canvas.height)console.log('imgdata', imgdata)if (this.isNewModel) {// 新模式拍照  ---  为识别图片后自动裁剪逻辑,此处用到了opencv.js, 可根据需要忽略// eslint-disable-next-line no-undefconst sourceMat = cv.imread('canvas')try {// eslint-disable-next-line no-undefconst convertScaleAbsMat = getRect(sourceMat, 'canvas', this.currentScheme)// 有数据,表示识别成功if (convertScaleAbsMat) {// eslint-disable-next-line no-undefshowImage('canvas', convertScaleAbsMat)} else {// 识别失败,弹出图片裁剪弹窗手动裁剪,详见上一篇base64Url = canvas.toDataURL('image/jpeg')this.currentType = typethis.originBase64 = base64Urlthis.cropperVisible = truesetTimeout(() => {this.imgLoading = false}, 1000)return}// 防止内存泄漏if (!sourceMat.isDeleted()) {sourceMat.delete()}} catch (error) {console.error('error', error, sourceMat.isDeleted())setTimeout(() => {this.imgLoading = false}, 1000)if (!sourceMat.isDeleted()) {sourceMat.delete()}}} else {// 原模式a3拍照const d /* 图像的总通道*/ = imgdata.datafor (var ii = 0; ii < d.length; ii += 4) {const average = d[ii] * 0.1 + d[ii + 1] * 0.5 + d[ii + 2] * 0.9d[ii + 0] = average // 红d[ii + 1] = average // 绿d[ii + 2] = average // 蓝}// 4.把处理后的像素信息放回画布context.clearRect(0, 0, canvas.width, canvas.height)context.putImageData(imgdata, 0, 0)}base64Url = canvas.toDataURL('image/jpeg')if (!this.acrossOrvertical && this.isNewModel) {// 新模式且是竖版const image = new Image()image.src = base64Urlconst that = this// 以下为旋转图片逻辑image.onload = function() {const newCanvas = document.createElement('canvas')const newContext = newCanvas.getContext('2d')newCanvas.width = image.heightnewCanvas.height = image.widthnewContext.clearRect(0, 0, newCanvas.width, newCanvas.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)// rotateVal 为旋转的角度newContext.rotate(that.rotateVal * Math.PI / 180)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.drawImage(image, newCanvas.width / 2 - image.width / 2, newCanvas.height / 2 - image.height / 2, image.width, image.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)// rotateVal 为旋转的角度newContext.rotate(-that.rotateVal * Math.PI / 180)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.restore()base64Url = newCanvas.toDataURL('image/jpeg')const blob = that.convertBase64UrlToBlob(base64Url)const url = window.URL.createObjectURL(that.convertBase64UrlToBlob(base64Url))setTimeout(() => {that.imgLoading = false}, 1000)that.$emit('photograph', url, blob, type)return}} else {if (this.rotateVal === 180) {console.log('是A4/180')const image = new Image()image.src = base64Urlconst that = thisimage.onload = function() {// 旋转180度const newCanvas = document.createElement('canvas')const newContext = newCanvas.getContext('2d')newCanvas.width = image.widthnewCanvas.height = image.heightnewContext.clearRect(0, 0, newCanvas.width, newCanvas.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)newContext.rotate(Math.PI)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.drawImage(image, 0, 0, image.width, image.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)console.log('旋转180:', that.rotateVal, -that.rotateVal * Math.PI / 180)newContext.rotate(-Math.PI)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.restore()base64Url = newCanvas.toDataURL('image/jpeg')const blob = that.convertBase64UrlToBlob(base64Url)const url = window.URL.createObjectURL(that.convertBase64UrlToBlob(base64Url))setTimeout(() => {that.imgLoading = false}, 1000)that.$emit('photograph', url, blob, type)}} else {const blob = this.convertBase64UrlToBlob(base64Url)const url = window.URL.createObjectURL(this.convertBase64UrlToBlob(base64Url))setTimeout(() => {this.imgLoading = false}, 1000)this.$emit('photograph', url, blob, type)}}} else if (!this.isNewModel && !this.a3Ora4) {console.log('进入a4')// 横版if (this.acrossOrvertical) {canvas.width = el.videoWidth * dprcanvas.height = el.videoHeight * dprcontext.drawImage(el, 200, 200, el.videoWidth - 200, el.videoHeight - 200, 0, 0, (el.videoWidth + 170) * dpr, (el.videoHeight + 230) * dpr)const imgdata = context.getImageData(0, 0, canvas.width, canvas.height)const d /* 图像的总通道*/ = imgdata.data// 2.遍历每一个像素for (let i = 0; i < d.length; i += 4) {const average = d[i] * 0.1 + d[i + 1] * 0.5 + d[i + 2] * 0.9d[i + 0] = average // 红d[i + 1] = average // 绿d[i + 2] = average // 蓝}// 4.把处理后的像素信息放回画布context.clearRect(0, 0, canvas.width, canvas.height)context.putImageData(imgdata, 0, 0)base64Url = canvas.toDataURL('image/jpeg')if (this.rotateVal === 180) {console.log('是A4/180')const image = new Image()image.src = base64Urlconst that = thisimage.onload = function() {// 旋转180度const newCanvas = document.createElement('canvas')const newContext = newCanvas.getContext('2d')newCanvas.width = image.widthnewCanvas.height = image.heightnewContext.clearRect(0, 0, newCanvas.width, newCanvas.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)newContext.rotate(Math.PI)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.drawImage(image, 0, 0, image.width, image.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)console.log('旋转180:', that.rotateVal, -that.rotateVal * Math.PI / 180)newContext.rotate(-Math.PI)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.restore()base64Url = newCanvas.toDataURL('image/jpeg')const blob = that.convertBase64UrlToBlob(base64Url)const url = window.URL.createObjectURL(that.convertBase64UrlToBlob(base64Url))setTimeout(() => {that.imgLoading = false}, 1000)that.$emit('photograph', url, blob, type)}} else {const blob = this.convertBase64UrlToBlob(base64Url)const url = window.URL.createObjectURL(this.convertBase64UrlToBlob(base64Url))setTimeout(() => {this.imgLoading = false}, 1000)this.$emit('photograph', url, blob, type)}} else {// 竖版canvas.width = el.videoWidth * dprcanvas.height = el.videoHeight * dprcontext.drawImage(el, 200, 200, el.videoWidth - 200, el.videoHeight - 200, 0, 0, (el.videoWidth + 170) * dpr, (el.videoHeight + 230) * dpr)const imgdata = context.getImageData(0, 0, canvas.width, canvas.height)const d /* 图像的总通道*/ = imgdata.data// 2.遍历每一个像素for (let i = 0; i < d.length; i += 4) {const average = d[i] * 0.1 + d[i + 1] * 0.5 + d[i + 2] * 0.9d[i + 0] = average // 红d[i + 1] = average // 绿d[i + 2] = average // 蓝}// 4.把处理后的像素信息放回画布context.clearRect(0, 0, canvas.width, canvas.height)context.putImageData(imgdata, 0, 0)base64Url = canvas.toDataURL('image/jpeg')const image = new Image()image.src = base64Urlconst that = thisimage.onload = function() {// 旋转90度const newCanvas = document.createElement('canvas')const newContext = newCanvas.getContext('2d')newCanvas.width = image.heightnewCanvas.height = image.widthnewContext.clearRect(0, 0, newCanvas.width, newCanvas.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)newContext.rotate(that.rotateVal * Math.PI / 180)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.drawImage(image, newCanvas.width / 2 - image.width / 2, newCanvas.height / 2 - image.height / 2, image.width, image.height)newContext.translate(newCanvas.width / 2, newCanvas.height / 2)newContext.rotate(-that.rotateVal * Math.PI / 180)newContext.translate(-newCanvas.width / 2, -newCanvas.height / 2)newContext.restore()base64Url = newCanvas.toDataURL('image/jpeg')const blob = that.convertBase64UrlToBlob(base64Url)const url = window.URL.createObjectURL(that.convertBase64UrlToBlob(base64Url))setTimeout(() => {that.imgLoading = false}, 1000)that.$emit('photograph', url, blob, type)}}}},

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

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

相关文章

【C++】list 类深度解析:探索双向链表的奇妙世界

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 如果你对string&#xff0c;vector还存在疑惑&#xff0c;欢迎阅读我之前的作品 &#xff1a; 之前文章&#x1f525;&#x1…

uniapp如何i18n国际化

1、正常情况下项目在代码生成的时候就已经有i18n的相关依赖&#xff0c;如果没有可以自行使用如下命令下载&#xff1a; npm install vue-i18n --save 2、创建相关文件 en文件下&#xff1a; zh文件下&#xff1a; index文件下&#xff1a; 3、在main.js中注册&#xff1a…

VScode-Java开发常用插件

中文——界面易读 字体主题——代码可观 头注释——项目信息明了 java开发包——java必备 git协作开发——版本控制

前端(3)——快速入门JaveScript

参考&#xff1a; 罗大富 JavaScript 教程 | 菜鸟教程 JavaScript 教程 1. JaveScript JavaScript 简称 JS JavaScript 是一种轻量级、解释型、面向对象的脚本语言。它主要被设计用于在网页上实现动态效果&#xff0c;增加用户与网页的交互性。作为一种客户端脚本语言&#…

FRP 实现内网穿透

如何通过 FRP 实现内网穿透&#xff1a;群晖 NAS 的 Gitea 和 GitLab 访问配置指南 在自建服务的过程中&#xff0c;经常会遇到内网访问受限的问题。本文将介绍如何利用 FRP&#xff08;Fast Reverse Proxy&#xff09;来实现内网穿透&#xff0c;以便在外网访问群晖 NAS 上的…

我们来学mysql -- EXPLAIN之select_type(原理篇)

EXPLAIN之select_type 题记select_type 题记 书接上文《 EXPLAIN之ID》2024美国大选已定&#xff0c;川普剑登上铁王座&#xff0c;在此过程中出谋划策的幕僚很重要&#xff0c;是他们决定了最终的执行计划在《查询成本之索引选择》中提到&#xff0c;explain的输出&#xff0…

uni-app快速入门(五)--判断运行环境及针对不同平台的条件编译

一、判断运行环境 在实际项目开发中&#xff0c;经常需要进行开发环境和生产环境的切换&#xff0c;uni-app可根据process.env.NODE_ENV判断当前运行环境是开发环境和生产环境&#xff0c;根据不同的环境调用不同的后台接口&#xff0c;具体实现方式: 在项目的static目录下建…

北京大学c++程序设计听课笔记101

基本概念 程序运行期间&#xff0c;每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址&#xff08;也称“入口地址”&#xff09;。我们可以将函数的入口地址赋给一个指针变量&#xff0c;使该指针变量指向该函数。然后通过指针变量就可以调用这个…

构建客服知识库:企业效率提升的关键步骤

客服知识库是企业提升客户服务效率和质量的重要工具。它不仅帮助客服团队快速准确地回答客户问题&#xff0c;还能通过数据分析来优化服务流程和提升客户满意度。 1. 明确知识库的目标和范围 构建客服知识库的第一步是明确其目标和范围。这包括确定知识库的主要用户群体、需要…

Linux运维工程师推荐学习的开发语言

前言&#xff1a;会开发的运维和不会开发的运维可以说是两个世界的运维。 个人推荐python和go&#xff0c;前者可以做自动化运维&#xff0c;后者可以深挖k8s&#xff1b;最近就不先演示运维服务技术的部署和架构搭建了&#xff0c;在深挖自动化运维&#xff0c;为了让现在的工…

整合seata遇到的问题

自己遇到的问题&#xff0c;记录一下。 1、版本问题 我seata用的是1.7&#xff0c; 数据库驱动是 <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.31</version><scope>…

从零到一:利用 AI 开发 iOS App 《震感》的编程之旅

在网上看到一篇关于使用AI开发的编程经历&#xff0c;分享给大家 作者是如何在没有 iOS 开发经验的情况下&#xff0c;借助 AI&#xff08;如 Claude 3 模型&#xff09;成功开发并发布《震感》iOS 应用。 正文开始 2022 年 11 月&#xff0c;ChatGPT 诞生并迅速引发全球关注。…

.netcore + postgis 保存地图围栏数据

一、数据库字段 字段类型选择(Type) 设置对象类型为&#xff1a;geometry 二、前端传递的Json格式转换 前端传递围栏的各个坐标点数据如下&#xff1a; {"AreaRange": [{"lat": 30.123456,"lng": 120.123456},{"lat": 30.123456…

系统掌握大语言模型提示词 - 从理论到实践

以下是我目前的一些主要个人标签&#xff1a; 6 年多头部大厂软件开发经验&#xff1b;1 年多 AI 业务应用经验&#xff0c;拥有丰富的业务提示词调优经验和模型微调经验。信仰 AGI&#xff0c;已经将 AI 通过自定义 Chatbot /搭建 Agent 融合到我的工作流中。头部大厂技术大学…

k8clone二进制工具迁移k8s中的无状态应用

1 概述 k8clone是一个简便的Kubernetes元数据克隆工具&#xff0c;它可以将Kubernetes元数据&#xff08;对象&#xff09;保存为本地压缩包&#xff0c;在恢复时可将这些元数据恢复到目标集群中&#xff08;已存在的资源不会被覆盖&#xff09;。它不依赖远程存储&#xff0c…

IDC 报告:百度智能云 VectorDB 优势数量 TOP 1

近日&#xff0c;IDC 发布了《RAG 与向量数据库市场前景预测》报告&#xff0c;深入剖析了检索增强生成&#xff08;RAG&#xff09;技术和向量数据库市场的发展趋势。报告不仅绘制了 RAG 技术的发展蓝图&#xff0c;还评估了市场上的主要厂商。在这一评估中&#xff0c;百度智…

计算机毕业设计Python+CNN卷积神经网络股票预测系统 股票推荐系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Nginx SSL+tomcat,使用request.getScheme() 取到https协议

架构上使用了 Nginx tomcat 集群, 且nginx下配置了SSL,tomcat no SSL,项目使用https和http协议。 发现 request.getScheme() //总是 http&#xff0c;而不是实际的http或https request.isSecure() //总是false&#xff08;因为总是http&#xff09; request.getRemoteAddr(…

机器学习 ---线性回归

目录 摘要&#xff1a; 一、简单线性回归与多元线性回归 1、简单线性回归 2、多元线性回归 3、残差 二、线性回归的正规方程解 1、线性回归训练流程 2、线性回归的正规方程解 &#xff08;1&#xff09;适用场景 &#xff08;2&#xff09;正规方程解的公式 三、衡量…

蓝桥杯c++算法学习【3】之思维与贪心(重复字符串、翻硬币、乘积最大、皮亚诺曲线距离【难】:::非常典型的必刷例题!!!)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 关注博主&#xff0c;更多蓝桥杯nice题目静待更新:) 思维与贪心 一、重复字符串 【问题描述】 如果一个字符串S恰好可以由某个字符串重复K次得到&#xff0c;我们就称S是K次重复字 符串…