emotion.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <template>
  2. <div class="emotion">
  3. <el-switch v-model="value" size="large" inactive-text="打开摄像头" @change="change" />
  4. <el-row :gutter="20">
  5. <el-col :span="12">
  6. <p>网络摄像头:</p>
  7. <video ref="videoEl" style="width: 300px;height: 260px;" v-if="switchCamera"></video>
  8. <div v-else class="video_img">
  9. 请先打开摄像头
  10. </div>
  11. </el-col>
  12. <el-col :span="12">
  13. <p>最新的快照:</p>
  14. <canvas ref="canvasEl" v-if="switchCamera"></canvas>
  15. <div v-else class="video_img">
  16. 你的快照将会呈现在这里
  17. </div>
  18. </el-col>
  19. </el-row>
  20. <el-button @click="photoRecognition" style="margin-top: 10px;">拍照识别</el-button>
  21. <el-row class="emotion_result">
  22. <el-col :span="24">
  23. <p class="emotion_result_p">脸部分析结果:</p>
  24. </el-col>
  25. <el-col :span="12">
  26. <span class="emotion_result_span">年龄:</span>{{ resultEmotion.age || "未识别到结果" }}
  27. </el-col>
  28. <el-col :span="12">
  29. <span class="emotion_result_span">情绪:</span>{{ resultEmotion.emotion || "未识别到结果" }}
  30. </el-col>
  31. <el-col :span="12">
  32. <span class="emotion_result_span">性别:</span>{{ resultEmotion.gender || "未识别到结果" }}
  33. </el-col>
  34. <el-col :span="12">
  35. <span class="emotion_result_span">眼镜:</span>{{ resultEmotion.glasses || "未识别到结果" }}
  36. </el-col>
  37. <el-col :span="12">
  38. <span class="emotion_result_span">表情:</span>{{ resultEmotion.expression || "未识别到结果" }}
  39. </el-col>
  40. <p style="width:100%;border-bottom: 1px solid rgba(0, 0, 0, 0.4);margin: 10px 0;"></p>
  41. <el-col :span="24">
  42. <span class="emotion_result_span">表情:</span>smile(微笑)、laugh(大笑)、none(无)
  43. </el-col>
  44. <el-col :span="24">
  45. <span
  46. class="emotion_result_span">情绪:</span>angry(愤怒)、disgust(厌恶)、fear(恐惧)、happy(快乐)、sad(悲伤)、surprise(惊讶)、neutral(中性)、pouty(撅嘴)、grimace(鬼脸)
  47. </el-col>
  48. </el-row>
  49. <el-row>
  50. <el-switch v-model="cloudValue" size="large" inactive-text="是否发送到云端" @change="rendCloud" />
  51. <el-col>
  52. <p>请选择一个 CocoCloud 项目,分析后的数据将会发送至该项目
  53. <img src="../../assets/img/刷新.png" @click="refreshCloud" alt="">
  54. </p>
  55. </el-col>
  56. <el-col>
  57. <el-select name="select" id="" v-model="apiMode" @change="getApiKey">
  58. <!-- <option value="" v-for="item in cloudList" v-model="item.url">{{ item.value }}</option> -->
  59. <el-option v-for="item in cloudList" :key="item.url" :label="item.value" :value="item.url" />
  60. </el-select>
  61. </el-col>
  62. </el-row>
  63. </div>
  64. </template>
  65. <script setup>
  66. import { ref, onMounted } from 'vue';
  67. import axios from 'axios';
  68. import userInfo from '@/stores/modules/userInfo';
  69. const value = ref(false);
  70. const videoEl = ref(HTMLVideoElement); // 摄像头video
  71. const canvasEl = ref(HTMLCanvasElement); // 画布canvas
  72. const switchCamera = ref(false);
  73. const yekReverse = "NmJhZjc4NzUzMmVmNGVlYzhiYzkzYzg4NTE4Yjg5MTY="
  74. const hans = {
  75. "angry": "angry(愤怒)",
  76. "disgust": "disgust(厌恶)",
  77. "fear": "fear(恐惧)",
  78. "happy": "happy(快乐)",
  79. "sad": "sad(悲伤)",
  80. "surprise": "surprise(惊讶)",
  81. "neutral": "neutral(中性)",
  82. "pouty": "pouty(撅嘴)",
  83. "grimace": "grimace(鬼脸)"
  84. }
  85. const hant = {
  86. "angry": "angry(憤怒)",
  87. "disgust": "disgust(厭惡)",
  88. "fear": "fear(恐懼)",
  89. "happy": "happy(快樂)",
  90. "sad": "sad(悲傷)",
  91. "surprise": "surprise(驚訝)",
  92. "neutral": "neutral(中性)",
  93. "pouty": "pouty(撅嘴)",
  94. "grimace": "grimace(鬼臉)"
  95. }
  96. const cloudList = ref();
  97. const resultEmotion = ref({});
  98. const cloudValue = ref(false);
  99. const apiMode = ref("");
  100. const rendCloud = (val) => {
  101. if (val) {
  102. cloudValue.value = true;
  103. } else {
  104. cloudValue.value = false;
  105. }
  106. }
  107. const change = (val) => {
  108. if (val) {
  109. getCamera();
  110. switchCamera.value = true;
  111. } else {
  112. videoEl.value.srcObject = null;
  113. switchCamera.value = false;
  114. }
  115. }
  116. onMounted(() => {
  117. console.log(userInfo().userInfo)
  118. if (userInfo().userInfo) {
  119. refreshCloud()
  120. }
  121. })
  122. // 打开摄像头
  123. const getCamera = async () => {
  124. const navigator = window.navigator.mediaDevices;
  125. // 获取所有设备
  126. const devices = await navigator.enumerateDevices();
  127. if (devices) {
  128. const stream = await navigator.getUserMedia({
  129. audio: false,
  130. video: {
  131. width: 300, // 设置视频宽度
  132. height: 260, // 设置视频高度
  133. facingMode: "user", // 使用前置摄像头
  134. },
  135. });
  136. if (videoEl.value) {
  137. videoEl.value.srcObject = stream;
  138. videoEl.value.play();
  139. }
  140. }
  141. }
  142. const photoRecognition = () => {
  143. // 检查视频元素和canvas 是否存在
  144. if (videoEl.value && canvasEl.value) {
  145. console.log(videoEl.value);
  146. canvasEl.value.width = 300;
  147. canvasEl.value.height = 260;
  148. // 获取画布上下文对象
  149. const ctx = canvasEl.value.getContext('2d');
  150. // 绘制图像到画布上
  151. ctx?.drawImage(videoEl.value, 0, 0, 300, 260);
  152. // 将画布内容转换为base64格式
  153. const imagebase64 = canvasEl.value.toDataURL('image/jpeg', 1).split(";");
  154. var contentType = imagebase64[0].split(":")[1];
  155. var realData = imagebase64[1].split(",")[1];
  156. const blob = b64toBlob(realData, contentType);
  157. const formData = new FormData();
  158. formData.append('image', blob);
  159. // https://ai-api.cocorobo.cn/face 人脸识别接口
  160. const headers = {
  161. 'Ocp-Apim-Subscription-Key': atob(yekReverse)
  162. }
  163. axios.post('https://ai-api.cocorobo.cn/face', {
  164. image: realData
  165. }, { headers }).then(res => {
  166. console.log(res.data.data);
  167. const datas = res.data.data;
  168. if (datas.error_code == 0) {
  169. let data = datas.result
  170. let obj = {
  171. emotion: hans[data.face_list[0].emotion.type],
  172. age: data.face_list[0].age,
  173. gender: data.face_list[0].gender.type + (data.face_list[0].gender.type == "male" ? "(男)" : "(女)"),
  174. glasses: data.face_list[0].glasses.type + (data.face_list[0].glasses.type == "common" ? "(有)" : "(无)"),
  175. expression: data.face_list[0].expression.type + (data.face_list[0].expression.type == "smile" ? "(微笑)" : data.face_list[0].expression.type == "smile" ? "(大笑)" : "(无)")
  176. }
  177. resultEmotion.value = obj
  178. if (cloudValue.value) {
  179. sendCloud()
  180. }
  181. }
  182. // if (res.status == 200) {
  183. // console.log(res.data);
  184. // }
  185. })
  186. }
  187. }
  188. const b64toBlob = (b64Data, contentType, sliceSize = 512) => {
  189. contentType = contentType || '';
  190. sliceSize = sliceSize || 512;
  191. var byteCharacters = atob(b64Data);
  192. var byteArrays = [];
  193. for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
  194. var slice = byteCharacters.slice(offset, offset + sliceSize);
  195. var byteNumbers = new Array(slice.length);
  196. for (var i = 0; i < slice.length; i++) {
  197. byteNumbers[i] = slice.charCodeAt(i);
  198. }
  199. var byteArray = new Uint8Array(byteNumbers);
  200. byteArrays.push(byteArray);
  201. }
  202. var blob = new Blob(byteArrays, { type: contentType });
  203. return blob;
  204. }
  205. const refreshCloud = () => {
  206. const apiKey = userInfo().userInfo.apiKey
  207. const event = window.location.host.indexOf("cocorobo.hk") > -1 ? `https://api.cocorobo.hk/iot/data/apikey/${apiKey.value}/event/` : `https://api.cocorobo.cn/iot/data/apikey/${apiKey}/event/`
  208. axios.get(event).then(res => {
  209. // console.log(res)
  210. if (res.data.length > 0) {
  211. let list = []
  212. res.data.map(x => {
  213. let obj = {
  214. id: x.eventAPIKey,
  215. value: x.name,
  216. url: x.url
  217. }
  218. list.push(obj)
  219. return x
  220. })
  221. apiMode.value = list[0].value
  222. apiKeyModel.value = list[0].id
  223. apiUrl.value = list[0].url
  224. cloudList.value = list
  225. }
  226. })
  227. }
  228. const getApiKey = (e) => {
  229. apiMode.value = e
  230. }
  231. const sendCloud = () => {
  232. console.log(apiMode.value)
  233. let data = resultEmotion.value
  234. axios.post(apiMode.value, JSON.stringify(data)).then(res => {
  235. console.log(res)
  236. })
  237. }
  238. </script>
  239. <style scoped lang="scss">
  240. .emotion {
  241. .video_img {
  242. width: 300px;
  243. height: 260px;
  244. background: #f0f0f0;
  245. border-radius: 6px;
  246. text-align: center;
  247. line-height: 260px;
  248. color: rgba(0, 0, 0, .4);
  249. }
  250. .emotion_result {
  251. text-align: left;
  252. border-radius: 6px;
  253. border: 1px solid rgba(0, 0, 0, .075);
  254. padding: 15px;
  255. background-color: rgba(0, 0, 0, .02);
  256. margin-bottom: 15px;
  257. margin-top: 10px;
  258. color: rgba(0, 0, 0, .4);
  259. .emotion_result_p {
  260. font-weight: 200;
  261. margin-bottom: 10px;
  262. color: #000;
  263. }
  264. .emotion_result_span {
  265. font-weight: 600;
  266. color: #000;
  267. }
  268. }
  269. select {
  270. margin: 10px 0;
  271. width: 100%;
  272. padding: 8px;
  273. border-radius: 5px;
  274. }
  275. img {
  276. width: 25px;
  277. float: right;
  278. cursor: pointer;
  279. }
  280. }
  281. </style>