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.rhkb.cn/news/472271.html

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

相关文章

Android Settings 单元测试 | 如何运行单元测试?

背景 在Android Settings 单元测试 | Telephony Network 模块 APN 案例中粗略介绍了单元测试逻辑内容&#xff0c;但是在独立APK里面如何将单元测试跑起来还是有疑问&#xff0c;因为APP不能直接install&#xff0c;无法借助Android Studio直接Run&#xff0c;在安装的一步会报…

flutter字体大小切换案例 小字体,标准字体,大字体,超大字体案例

flutter字体大小切换案例 小字体&#xff0c;标准字体&#xff0c;大字体&#xff0c;超大字体案例 Android iOS设备带有选择记录 我的flutter项目版本 environment: sdk: ‘>3.4.4 <4.0.0’ 图片案例 pubspec.yaml 添加依赖 # 屏幕尺寸适配 https://github.com/OpenF…

编译原理(手绘)

大家好&#xff0c;今天给大家分享一下我自己对c语言编译链接的一点见解&#xff08;本人是学生&#xff0c;有记笔记的习惯&#xff09;&#xff0c;那么今天就给大家分享我的笔记。 以上只是我本人的一些见解&#xff0c;并非绝对&#xff0c;欢迎大家一起交流。 那么今天分…

跳房子(弱化版)

题目描述 跳房子&#xff0c;也叫跳飞机&#xff0c;是一种世界性的儿童游戏&#xff0c;也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下&#xff1a; 在地面上确定一个起点&#xff0c;然后在起点右侧画 n 个格子&#xff0c;这些格子都在同一条直线上。每个格子内…

初识Linux · 共享内存

目录 理解共享内存 Shared memmory code 理解共享内存 前文介绍的管道方式的通信&#xff0c;本文介绍的是进程通信的另外一种方式&#xff0c;即共享内存。但是这种通信方式的特点是只能本地通信&#xff0c;并且不像管道那样有保护机制&#xff0c;这里是没有的。 我们通…

机器学习day5-随机森林和线性代数1

十 集成学习方法之随机森林 集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。大致可以分为&#xff1a;Bagging&#xff0c;Boosting 和 Stacking 三大类型。 &#xff08;1&#xff09;每次有放回地从训练集中取出 n 个训练样本&…

Essential Cell Biology--Fifth Edition--Chapter one (6)

1.1.4.4 Internal Membranes Create Intracellular Compartments with Different Functions [细胞膜形成具有不同功能的细胞内隔室] 细胞核、线粒体和叶绿体并不是真核细胞中唯一的膜包围细胞器。细胞质中含有大量的[ a profusion of]其他细胞器&#xff0c;这些细胞器被单层膜…

基于VUE实现语音通话:边录边转发送语言消息、 播放pcm 音频

文章目录 引言I 音频协议音频格式:音频协议:II 实现协议创建ws对象初始化边录边转发送语言消息 setupPCM按下通话按钮时开始讲话,松开后停止讲话播放pcm 音频III 第三库recorderplayer调试引言 需求:电台通讯网(电台远程遥控软件-超短波)该系统通过网络、超短波终端等无线…

政务数据治理专栏开搞!

写在前面 忙忙碌碌干了一年政务数据治理的工作&#xff0c;从法人数据到自然人&#xff0c;从交通到地理信息等等&#xff0c;突发想法开一个专栏讲一讲政务数据遇到的问题&#xff0c;以及治理的成效&#xff0c;或许有朋友爱看。 政务数据&#xff0c;又称之为政务数据资源&a…

CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法

已经执行了conda init&#xff0c;但是还是会报错CondaError: Run ‘conda init’ before ‘conda activate’ 原因&#xff1a;权限不够 解决办法&#xff1a;以管理员身份运行cmd&#xff0c;然后进入要操作的文件夹下&#xff0c;重新执行 conda init 和 conda activate 就可…

【全面系统性介绍】虚拟机VM中CentOS 7 安装和网络配置指南

一、CentOS 7下载源 华为源&#xff1a;https://mirrors.huaweicloud.com/centos/7/isos/x86_64/ 阿里云源&#xff1a;centos-vault-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云 百度网盘源&#xff1a;https://pan.baidu.com/s/1MjFPWS2P2pIRMLA2ioDlVg?pwdfudi &…

软考教材重点内容 信息安全工程师 第 4 章 网络安全体系与网络安全模型

4,1 网络安全体系的主要特征: (1)整体性。网络安全体系从全局、长远的角度实现安全保障&#xff0c;网络安全单元按照一定的规则&#xff0c;相互依赖、相互约束、相互作用而形成人机物一体化的网络安全保护方式。 (2)协同性。网络安全体系依赖于多种安全机制&#xff0c;通过各…

让空间计算触手可及,VR手套何以点石成金?

引言 如何让一位母亲与她去世的小女儿“重逢”&#xff1f;韩国MBC电视台《I Met You》节目实现了一个“不可能”心愿。 在空旷的绿幕中&#xff0c;母亲Jang Ji-sung透过VR头显&#xff0c;看到了三年前因白血病去世的女儿Nayeon。当她伸出双手&#xff0c;居然能摸到女儿的…

[Admin] Dashboard Filter for Mix Report Types

Background RevOps team has built a dashboard for sales team to track team members’ performance, but they’re blocked by how to provide a manager view based on sales’ hierarchy. Therefore, they seek for dev team’s help to clear their blocker. From foll…

WPF中如何使用区域导航

1.创建一个Prism框架的项目并设计好数据源 User如下&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace WPF练习17区域导航.Models {public class User{public int UserId { get; …

AR眼镜方案_AR智能眼镜阵列/衍射光波导显示方案

在当今AR智能眼镜的发展中&#xff0c;显示和光学组件成为了技术攻坚的主要领域。由于这些组件的高制造难度和成本&#xff0c;其光学显示模块在整个设备的成本中约占40%。 采用光波导技术的AR眼镜显示方案&#xff0c;核心结构通常由光机、波导和耦合器组成。光机内的微型显示…

一文学会docker中搭建kali

一文学会docker中搭建kali 本文环境&#xff1a;部署好docker的ubuntu系统主机一台 直接pull对应的镜像&#xff1a; docker pull kalilinux/kali-rolling 然后通过端口映射&#xff0c;将本地100端口映射到容器的22端口&#xff0c;就可以ssh了 docker run -it -p 100:22…

git上传文件到远程仓库

git上传项目到远程仓库 1. 生成SSH公钥(ssh-keygen),一直回车即可 2. 将公钥复制下来,粘贴至码云仓库 公钥默认地址: C:\Users\Administrator\.ssh3. 克隆项目到本地(复制SSH地址) 4. 上传文件到刚创建的项目(这里取名为test.py) 5. 上传需要做的几个步骤 (1) git add . 添…

STM32 串口输出调试信息

软硬件信息 CubeMX version 6.12.1Keil uVision V5.41.0.0 注意 串口有多种&#xff1a; TTL232485 串口的相关知识&#xff1a; 01-【HAL库】STM32实现串口打印&#xff08;printf方式) &#xff0c; 内含 TTL 和 232 区别。 我把 232 串口连进 STM32 串口助手收到的信息…

【计算机网络】TCP协议特点3

心跳机制 什么是心跳机制 心跳机制是在计算机系统、网络通信和许多其他技术领域广泛应用的一种机制&#xff0c;用于检测两个实体之间的连接是否仍然活跃&#xff0c;或者设备是否还在正常运行。就是每隔一段时间发送一个固定的消息给服务端&#xff0c;服务端回复一个固定…