描述:
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)}}}},