原理

通过调用系统的相机,进行拍照,将图片传递给后端比对

注意点

媒体权限属于隐私权限,网址协议必须是https,否则无法拉起相机进行拍照

代码

<template>
  <view class="takePhotoView">
    <video
      ref="video"
      style="object-fit: fill"
      :show-center-play-btn="false"
      muted
      preload
      :width="videoWidth"
      :height="videoHeight"
    ></video>
    <div
      :device-position="device"
      flash="off"
      @error="error"
      style="width: 100%; height: 100vh; position: fixed; top: 0; left: 0"
    >
      <cover-view
        style="
          position: relative;
          width: 100vw;
          height: 100vh;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
        "
      >
        <cover-view
          style="
            position: absolute;
            width: 100%;
            height: 100%;
            z-index: -9;
            top:0;
            left:0;
          "
        >
          <cover-image
            class="coverImage"
            style="width: 100vw; height: 100vh"
            src="https://www.douhuikao.cn/img/mall/face_bg2.png"
          >
          </cover-image>
        </cover-view>
        <!-- <cover-view
          style="flex: 1;width: 100vw;display: flex;align-items: center;center;background:rgba(0,0,0,1);"
        >
          <cover-image
            class="coverImage"
            @click="close"
            style="
              z-index: 1001;
              width: 60rpx;
              height: 60rpx;
              margin-left: 50rpx;
            "
            src="https://www.douhuikao.cn/img/mall/close.png"
          ></cover-image>
        </cover-view> -->
        <!-- <cover-view
          :style="'width: 100vw; height:' + scale * 100 + 'vw;'"
        ></cover-view> -->
        <!-- <cover-view
          style="flex: 1;width: 100vw;height:100rpx; ;   position: fixed;bottom:20rpx;display: flex;justify-content: space-around;align-items: center;center;background:rgba(0,0,0,1);"
        >
          <cover-image
            class="coverImage"
            @click="deviceQH"
            :style="
              'width: 60rpx;height: 60rpx;z-index: 1001;' +
              (hasSwitch ? '' : 'opacity: 0;')
            "
            src="https://www.douhuikao.cn/img/mall/ph.png"
          ></cover-image>
          <cover-image
            class="coverImage"
            @click="photoShoot"
            style="width: 100rpx; height: 100rpx; z-index: 1001"
            src="https://www.douhuikao.cn/img/mall/pz.png"
          ></cover-image>
          <cover-image
            class="coverImage"
            @click="chooseImage"
            :style="
              'width: 60rpx;height: 60rpx;z-index: 1001;' +
              (hasAlbum ? '' : 'opacity: 0;')
            "
            src="https://www.douhuikao.cn/img/mall/tk.png"
          ></cover-image>
        </cover-view> -->
        <!-- <cover-view
          style="
            position: absolute;
            z-index: 1001;
            width: 100%;
            bottom: 250rpx;
            font-size: 66upx;
            text-align: center;
            color: #000000;
            font-weight: bold;
          "
        >
          避免头发口罩遮挡
        </cover-view> -->
      </cover-view>
    </div>
  </view>
</template>

<script>
import axios from "axios";
import {
  requestGet,
  requestPost,
  requestGetUrl,
  requestPostUrl,
} from "@/service/request.js";
import { getLoginToken, setFaceFlag } from "@/service/localStorage";
import { faceCompare } from "@/service/api/login.js";
import { uploadImage } from "@/service/api/mall/user.js";
// import config from '@/config'
const baseUrl = "https://www.douhuikao.cn/api";
const constraints = {
  audio: false,
  video: {
    width: 600,
    height: 400,
    facingMode: "user",
  },
};
export default {
  data() {
    return {
      hasSwitch: true,
      hasAlbum: true,
      initScale: 1.277,
      original: true,
      canvasW: 0,
      canvasH: 0,
      device: "front",
      videoShow: false,
      pictureShow: false,
      // 图片地址
      picture: "",
      // 用于视频识别的节点
      canvas: null,
      video: null,
      image: null,
      timeout: 0,
      // 模型识别的条件
      options: "",
      // 提示控制
      noOne: "",
      moreThanOne: "",
      // 不是通过Https访问提示
      httpsAlert: "",
      videoWidth: 600,
      videoHeight: 400,
      useFrontCamera: true,
    };
  },
  mounted() {
    this.video = this.$refs.video;
    uni.getSystemInfo({
      success: function (res) {
        this.videoWidth = Math.max(res.windowWidth, res.windowHeight) - 120;
        constraints.video.width = this.videoWidth;
        this.videoHeight = Math.min(res.windowWidth, res.windowHeight);
        constraints.video.height = this.videoHeight;
      },
    });
    // this.chooseImage()
    this.useCamera();
    this.deviceQH();
  },
  computed: {
    scale: function () {
      if (this.initScale > 1.77) return 1.77;
      if (this.initScale < 1.27) return 1.27;
      return this.initScale;
    },
  },
  methods: {
    /**
     * 使用摄像头
     */
    useCamera() {
      let that = this;

      if (window.stream) {
        window.stream.getTracks().forEach((track) => {
          track.stop();
        });
      }

      // 如果不是通过loacalhost或者通过https访问会将报错捕获并提示
      try {
        if (navigator.mediaDevices === undefined) {
          navigator.mediaDevices = {};
        }
        if (navigator.mediaDevices.getUserMedia === undefined) {
          navigator.mediaDevices.getUserMedia = function (constraints) {
            var getUserMedia =
              navigator.getUserMedia ||
              navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia ||
              navigator.msGetUserMedia ||
              navigator.oGetUserMedia;
            if (!getUserMedia) {
              return Promise.reject(
                new Error("getUserMedia is not implemented in this browser")
              );
            }
            return new Promise(function (resolve, reject) {
              getUserMedia.call(navigator, constraints, resolve, reject);
            });
          };
        }
        console.log(navigator, getUserMedia, "99");
        let promise = navigator.mediaDevices.getUserMedia(constraints);
        promise
          .then((stream) => {
            window.stream = stream;
            // 返回参数
            const video = document.querySelector("video");
            if ("srcObject" in video) {
              video.srcObject = stream;
            } else {
              // 防止在新的浏览器里使用它,应为它已经不再支持了
              video.src = window.URL.createObjectURL(stream);
            }
            video.onloadedmetadata = function (e) {
              video.play();
            };
          })
          .catch((error) => {
            console.log(error);
          });
      } catch (err) {
        this.httpsAlert = `您现在在使用非Https访问,
                    请先在chrome://flags/#unsafely-treat-insecure-origin-as-secure中修改配置,
                    添将当前链接${window.location.href}添加到列表,
                    并且将Insecure origins treated as secure修改为enabled,
                    修改完成后请重启浏览器后再次访问!`;
      }
    },
    // 关闭摄像头
    closeCamera() {
      if (!this.$refs["video"].srcObject) return;
      let stream = this.$refs["video"].srcObject;
      let tracks = stream.getTracks();
      tracks.forEach((track) => {
        track.stop();
      });
      this.$refs["video"].srcObject = null;
    },

    base64ToFile(base64, fileName) {
      // 提取 MIME 类型
      const parts = base64.split(";base64,");
      const mimeType = parts[0].split(":")[1];
      // 解码 Base64 字符串
      const byteCharacters = atob(parts[1]);
      const byteNumbers = new Array(byteCharacters.length);
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i);
      }
      const byteArray = new Uint8Array(byteNumbers);
      // 创建 Blob 对象
      const blob = new Blob([byteArray], { type: mimeType });
      // 创建 File 对象
      return new File([blob], fileName, { type: mimeType });
    },
    /**
     * 拍照上传
     */
    async photoShoot() {
      // 拿到图片的base64

      this.canvas = document.createElement("canvas");
      let context = this.canvas.getContext("2d");
      const video = document.querySelector("video");
      this.canvas.width = Math.min(video.videoWidth, video.videoHeight);
      this.canvas.height = Math.max(video.videoWidth, video.videoHeight);
      context.drawImage(video, 0, 0, this.canvas.width, this.canvas.height);
      let canvas = this.canvas.toDataURL("image/png");
      // let canvas = "data:image/png;base64,ivosawfawdqwqfqwfasfqwqwfqw";
      // alert(canvas);
      // 拍照以后将video隐藏
      // 停止摄像头成像
      // video.srcObject.getTracks()[0].stop();
      uni.showLoading({
        title: "正在校验中...",
        mask: true, // 显示透明蒙层,防止触摸穿透
      });
      video.pause();
      if (canvas) {
        // 拍照将base64转为file流文件
        let blob = this.dataURLtoBlob(canvas);
        let file = this.blobToFile(blob, "imgName.png");
        console.log(file, "22");
        // setTimeout(async () => {
        const formData = new FormData();
        formData.append("file", file);

        axios
          .post("https://www.douhuikao.cn/api/upload/image", formData, {
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: `Bearer ${getLoginToken()}`,
            },
          })
          .then(async (response) => {
            console.log(response.data.data.url, "22");

            const resul = await requestPost(faceCompare, {
              data: {
                photo: response.data.data.url,
              },
            });
            if (resul.status == 200) {
              uni.hideLoading();
              setFaceFlag("true");
              uni.showToast({
                title: "人脸识别成功",
                icon: "success",
                mask: true,
                duration: 2000,
              });
              setTimeout(() => {
                const entryUrl = uni.getStorageSync("entryUrl");
                if (entryUrl.includes("mall")) {
                  uni.reLaunch({
                    url: "/pages/mall/index/index",
                  });
                } else {
                  uni.reLaunch({
                    url: "/pages/index/index",
                  });
                }
              }, 2000);
            } else {
              // uni.showToast({
              //   title: resul.msg,
              //   icon: "none",
              //   mask: true,
              //   duration: 2000,
              // });
              setTimeout(() => {
                uni.navigateBack();
              }, 2000);
            }
            console.log("文件上传成功", response);
          })
          .catch((error) => {
            console.error("文件上传失败", error);
          });
        // let image = window.URL.createObjectURL(file);
        // console.log(image, "222");

        // this.uploadFilePromise(image);
      } else {
        console.log("canvas生成失败");
      }
    },
    /**
     * 将图片转为blob格式
     * dataurl 拿到的base64的数据
     */
    dataURLtoBlob(dataurl) {
      const arr = dataurl.split(",");
      const mime = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]);
      let n = bstr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    },
    /**
     * 生成文件信息
     * theBlob 文件
     * fileName 文件名字
     */
    blobToFile(theBlob, fileName) {
      return new File([theBlob], fileName, { type: theBlob.type });
    },
    deviceQH() {
      this.useFrontCamera = !this.useFrontCamera;
      if (window.stream) {
        window.stream.getTracks().forEach((track) => {
          track.stop();
        });
      }
      constraints.video.facingMode = "user";

      let promise = navigator.mediaDevices.getUserMedia(constraints); // 调用将询问用户是否允许访问摄像机。如果用户拒绝,它将引发异常并且不返回流。因此,必须在 try/catch 块内处理这种情况,它返回一个Promise,必须使用 async/await 或 then 块
      promise
        .then(async (stream) => {
          window.stream = stream;
          // 返回参数
          const video = document.querySelector("video");
          if ("srcObject" in video) {
            video.srcObject = stream;
          } else {
            // 防止在新的浏览器里使用它,应为它已经不再支持了
            video.src = window.URL.createObjectURL(stream);
          }
          if (this.useFrontCamera) {
            video.onloadedmetadata = () => {
              // 播放视频
              video.play();
            };

            // 调用拍照方法
            this.photoShoot();
          } else {
            setTimeout(() => {
              this.deviceQH();
            }, 100);
            setTimeout(() => {
              this.photoShoot();
            }, 2000);
          }
        })
        .catch((error) => {
          this.deviceQH();
          console.log(error);
        });
    },
    close() {
      this.$emit("close");
    },
    chooseImage() {
      let _this = this;
      if (!this.hasAlbum) {
        return;
      }
      if (window.stream) {
        window.stream.getTracks().forEach((track) => {
          track.stop();
        });
      }
      uni.chooseImage({
        count: 1, //默认9
        sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
        sourceType: ["camera"], //从相册选择
        success: (res) => {
          console.log(res.tempFilePaths[0], "img");

          _this.uploadFilePromise(res.tempFilePaths[0]);
        },
      });
    },
    error(e) {
      console.log(e);
    },
    async uploadFilePromise(url) {
      alert(url);
      //弹出照片核验的效果
      let _self = this;
      //   return new Promise((resolve, reject) => {
      const uploadRes = await uni.uploadFile({
        // url: baseUrl +'/file/uploadMinio',
        url: baseUrl + "/upload/image",
        filePath: url,
        name: "file",
        header: {
          Authorization: `Bearer ${getLoginToken()}`,
        },
        //  return

        //   success: async (res) => {
        //     alert(4);
        //     console.log(res, JSON.parse(res.data).data.url, "图片");

        //     const resul = await requestPost(faceCompare, {
        //       data: {
        //         photo: JSON.parse(res.data).data.url,
        //       },
        //     });
        //     //
        //     if (resul.status == 200) {
        //       alert(5);
        //       uni.showToast({
        //         title: "人脸识别成功",
        //         icon: "success",
        //         mask: true,
        //         duration: 2000,
        //       });
        //       setTimeout(() => {
        //         uni.navigateTo({
        //           url: `/pages/mall/index/index`,
        //         });
        //       }, 2000);
        //     } else {
        //       alert(6);
        //       uni.showToast({
        //         title: resul.msg,
        //         icon: "success",
        //         mask: true,
        //         duration: 2000,
        //       });
        //     }
        //     return;
        //     //弹出验证情况
        //     _self.$modal.loading("验证中,请耐心等待...");
        //     let result = JSON.parse(res.data);
        //     console.log(result);
        //     if (result.code == "" || result.code == null) {
        //       _self.$emit("takePhoto", null);
        //       return;
        //     }
        //     if (result.code != 200) {
        //       this.$tab.reLaunch(
        //         "/pages/face/faceValidate?code=" +
        //           result.code +
        //           "&msg=" +
        //           result.msg
        //       );
        //       return;
        //     }
        //     setTimeout(() => {
        //       let param = {};
        //       param.url = result.data.url;
        //       param.feature = result.data.feature;
        //       _self.$emit("takePhotoTwo", param);
        //     }, 500);
        //   },
      });
      //   alert(JSON.parse(uploadRes.data).data.url)
      const face_url = JSON.parse(uploadRes.data).data.url;
      if (uploadRes.statusCode == 200) {
        const resul = await requestPost(faceCompare, {
          data: {
            photo: face_url,
          },
        });
        if (resul.status == 200) {
          uni.showToast({
            title: "人脸识别成功",
            icon: "success",
            mask: true,
            duration: 2000,
          });
          //   setTimeout(() => {
          //     uni.navigateTo({
          //       url: `/pages/mall/index/index`,
          //     });
          //   }, 2000);
        } else {
          uni.showToast({
            title: resul.msg,
            icon: "success",
            mask: true,
            duration: 2000,
          });
        }
      } else {
      }
    },
  },
};
</script>

<style scoped lang="scss">
::v-deep {
  .uni-video-bar {
    display: none;
  }
}
.takePhotoView {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.canvas {
  opacity: 0;
  position: fixed;
  top: 100vh;
  z-index: -999;
}

.coverImage {
  z-index: 999;
}
uni-video {
  width: 100%;
  height: 100%;
}
</style>

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐