@@ -5,6 +5,7 @@ | |||||
"iconify", | "iconify", | ||||
"intlify", | "intlify", | ||||
"mingcute", | "mingcute", | ||||
"onnx", | |||||
"pinia", | "pinia", | ||||
"pnpm", | "pnpm", | ||||
"pyinstaller", | "pyinstaller", | ||||
@@ -1,9 +1,118 @@ | |||||
from api.model.model import PicoDet | |||||
import time | |||||
import cv2 | |||||
import json | |||||
import datetime | |||||
import os | |||||
import sys | |||||
def getFile(ruleFile): | |||||
if getattr(sys, 'frozen', False): | |||||
absPath = os.path.dirname(os.path.abspath(sys.executable)) | |||||
elif __file__: | |||||
absPath = os.path.dirname(os.path.abspath(__file__)) | |||||
else: | |||||
absPath = '' | |||||
return os.path.join(absPath,ruleFile) | |||||
def get_settings_status_name(data, settings,setting_name): | |||||
# 访问指定 "setting" 的信息 | |||||
for setting in data[settings]: | |||||
if setting["name"] == setting_name: | |||||
# 使用 filter() 和 map() 函数提取信息 | |||||
names = list(map(lambda x: x["name"], filter(lambda x: x["status"], setting["status"]))) | |||||
return names[0] | |||||
def get_setting_status(data, settings,setting_name): | |||||
# 使用 filter() 和 map() 函数提取信息 | |||||
status = list(map(lambda x: x["status"], filter(lambda x: x["name"] == setting_name, data[settings]))) | |||||
return status[0] | |||||
def get_photo(cap): | |||||
# cap = cv2.VideoCapture(0) # 开启摄像头 | |||||
f, frame = cap.read() # 将摄像头中的一帧图片数据保存 | |||||
return frame | |||||
# print('开始咯!') | |||||
# while cv2.waitKey(1)==-1: | |||||
# #计时 | |||||
def get_photo_detect(cap,net): | |||||
''' | |||||
输入{图像,网络模型},输出[预测时间,预测结果] | |||||
''' | |||||
result = net.detect_img(get_photo(cap),show_result=False) | |||||
return result | |||||
def get_photo_detect_img(cap,net): | |||||
''' | |||||
输入{图像,网络模型},输出[预测时间,预测图片] | |||||
''' | |||||
start = time.time() | |||||
[result,img] = net.detect_img(get_photo(cap),show_result=True) | |||||
# cv2.imshow('Video Cam', result) | |||||
# # print(result) | |||||
end = time.time() | |||||
# print('time:',end-start) | |||||
# cap.release() # 关闭摄像头 | |||||
# # cv2.destroyAllWindows() | |||||
return [end-start,result,img] | |||||
def get_photo_base64(cap): | |||||
import base64 | |||||
# import numpy as np | |||||
img = get_photo(cap) | |||||
image = cv2.imencode('.jpg',img)[1] | |||||
image_code = str(base64.b64encode(image))[2:-1] | |||||
return image_code | |||||
def mat2base64(frame): | |||||
import base64 | |||||
# import numpy as np | |||||
image = cv2.imencode('.jpg',frame)[1] | |||||
image_code = str(base64.b64encode(image))[2:-1] | |||||
return image_code | |||||
class API: | class API: | ||||
'''本地API,供前端JS调用''' | '''本地API,供前端JS调用''' | ||||
window = None | window = None | ||||
net = None | |||||
cap = None | |||||
args = None | |||||
def getOwner(self): | |||||
def __init__(self): | |||||
with open(getFile("config.json"),'r',encoding='utf8')as fp: | |||||
self.args = json.load(fp) | |||||
self.net = PicoDet( | |||||
get_settings_status_name(self.args,"ModelSetting","模型版本设置"), | |||||
self.args['classfile'], | |||||
prob_threshold=get_setting_status(self.args,"ModelSetting",'confThreshold'), | |||||
iou_threshold=get_setting_status(self.args,"ModelSetting",'nmsThreshold')) | |||||
# net.detect_folder(args['img_fold'], args['result_fold']) | |||||
# 调用摄像头拍摄照片 | |||||
self.cap = cv2.VideoCapture(0) # 开启摄像头 | |||||
# cv2.namedWindow('Video Cam', cv2.WINDOW_NORMAL) | |||||
def getPrimaryImg(self): | |||||
self.window.evaluate_js('getPythonData()') | self.window.evaluate_js('getPythonData()') | ||||
return '我是Python' | |||||
return get_photo_base64(self.cap) | |||||
def getDetectImg(self): | |||||
[time,result,img] = get_photo_detect_img(self.cap,self.net) | |||||
return [time,result,mat2base64(img)]#AttributeError: 'InferenceSession' object has no attribute 'detect' | |||||
def getIndexSetting(self): | |||||
toggle = self.args['toggle'] | |||||
tip = self.args['tip'] | |||||
control = self.args['control'] | |||||
return [toggle,tip,control] | |||||
def getAdvanceSetting(self): | |||||
return [self.args['ControlSetting'], self.args['ModelSetting'], self.args['otherSetting']] | |||||
def changeSetting(self,data): | |||||
self.args.update(data) | |||||
with open(getFile("config.json"),'w',encoding='utf8')as fp: | |||||
json.dump(self.args,fp,ensure_ascii=False,indent=4) | |||||
@@ -0,0 +1,111 @@ | |||||
{ | |||||
"classfile": "coco_label.txt", | |||||
"img_fold": "./imgs", | |||||
"result_fold": "results", | |||||
"toggle": false, | |||||
"tip": false, | |||||
"control": [ | |||||
{ | |||||
"name": "嘴控", | |||||
"status": false | |||||
}, | |||||
{ | |||||
"name": "眼控", | |||||
"status": false | |||||
}, | |||||
{ | |||||
"name": "嘴/眼控", | |||||
"status": true | |||||
} | |||||
], | |||||
"ControlSetting": [ | |||||
{ | |||||
"name": "控制设置", | |||||
"status": [ | |||||
{ | |||||
"name": "开/关/开", | |||||
"status": true | |||||
}, | |||||
{ | |||||
"name": "关/开/关", | |||||
"status": false | |||||
}, | |||||
{ | |||||
"name": "闭上持续2S", | |||||
"status": false | |||||
}, | |||||
{ | |||||
"name": "张开持续2S", | |||||
"status": false | |||||
} | |||||
], | |||||
"description": "控制设置" | |||||
}, | |||||
{ | |||||
"name": "控制功能", | |||||
"status": [ | |||||
{ | |||||
"name": "悬停鼠标", | |||||
"status": true | |||||
}, | |||||
{ | |||||
"name": "单击按钮", | |||||
"status": false | |||||
} | |||||
] | |||||
} | |||||
], | |||||
"ModelSetting": [ | |||||
{ | |||||
"name": "模型版本设置", | |||||
"status": [ | |||||
{ | |||||
"name": "model_1.0.onnx", | |||||
"status": true | |||||
} | |||||
], | |||||
"description": "目前仅有官方提供模型,可通过个性化设置进行修改", | |||||
"link": "/Personalization" | |||||
}, | |||||
{ | |||||
"name": "置信度", | |||||
"status": 0.5 | |||||
}, | |||||
{ | |||||
"name": "confThreshold", | |||||
"status": 0.5 | |||||
}, | |||||
{ | |||||
"name": "nmsThreshold", | |||||
"status": 0.6 | |||||
}, | |||||
{ | |||||
"name": "图片载入方式", | |||||
"status": [ | |||||
{ | |||||
"name": "不裁剪", | |||||
"status": true | |||||
}, | |||||
{ | |||||
"name": "居中裁剪成224*224", | |||||
"status": false | |||||
}, | |||||
{ | |||||
"name": "居中裁剪成320*320", | |||||
"status": false | |||||
} | |||||
] | |||||
} | |||||
], | |||||
"otherSetting": [ | |||||
{ | |||||
"name": "系统摄像头选择", | |||||
"status": [ | |||||
{ | |||||
"name": "摄像头0", | |||||
"status": true | |||||
} | |||||
] | |||||
} | |||||
] | |||||
} |
@@ -0,0 +1,4 @@ | |||||
closed_eye | |||||
closed_mouth | |||||
open_eye | |||||
open_mouth |
@@ -0,0 +1,218 @@ | |||||
import cv2 | |||||
import numpy as np | |||||
import argparse | |||||
import onnxruntime as ort | |||||
from pathlib import Path | |||||
from tqdm import tqdm | |||||
import time | |||||
import sys | |||||
import os | |||||
def getFile(ruleFile): | |||||
if getattr(sys, 'frozen', False): | |||||
absPath = os.path.dirname(os.path.abspath(sys.executable)) | |||||
elif __file__: | |||||
absPath = os.path.dirname(os.path.abspath(__file__)) | |||||
else: | |||||
absPath = '' | |||||
return os.path.join(absPath,ruleFile) | |||||
class PicoDet(): | |||||
def __init__(self, | |||||
model_pb_path, | |||||
label_path, | |||||
prob_threshold=0.4, | |||||
iou_threshold=0.3): | |||||
self.classes = list( | |||||
map(lambda x: x.strip(), open(getFile(label_path), 'r').readlines())) | |||||
self.num_classes = len(self.classes) | |||||
self.prob_threshold = prob_threshold | |||||
self.iou_threshold = iou_threshold | |||||
self.mean = np.array( | |||||
[103.53, 116.28, 123.675], dtype=np.float32).reshape(1, 1, 3) | |||||
self.std = np.array( | |||||
[57.375, 57.12, 58.395], dtype=np.float32).reshape(1, 1, 3) | |||||
so = ort.SessionOptions() | |||||
so.log_severity_level = 3 | |||||
self.net = ort.InferenceSession(getFile(model_pb_path), so) | |||||
inputs_name = [a.name for a in self.net.get_inputs()] | |||||
inputs_shape = { | |||||
k: v.shape | |||||
for k, v in zip(inputs_name, self.net.get_inputs()) | |||||
} | |||||
self.input_shape = inputs_shape['image'][2:] | |||||
def _normalize(self, img): | |||||
img = img.astype(np.float32) | |||||
img = (img / 255.0 - self.mean / 255.0) / (self.std / 255.0) | |||||
return img | |||||
def resize_image(self, srcimg, keep_ratio=False): | |||||
top, left, newh, neww = 0, 0, self.input_shape[0], self.input_shape[1] | |||||
origin_shape = srcimg.shape[:2] | |||||
im_scale_y = newh / float(origin_shape[0]) | |||||
im_scale_x = neww / float(origin_shape[1]) | |||||
img_shape = np.array([ | |||||
[float(self.input_shape[0]), float(self.input_shape[1])] | |||||
]).astype('float32') | |||||
scale_factor = np.array([[im_scale_y, im_scale_x]]).astype('float32') | |||||
if keep_ratio and srcimg.shape[0] != srcimg.shape[1]: | |||||
hw_scale = srcimg.shape[0] / srcimg.shape[1] | |||||
if hw_scale > 1: | |||||
newh, neww = self.input_shape[0], int(self.input_shape[1] / | |||||
hw_scale) | |||||
img = cv2.resize( | |||||
srcimg, (neww, newh), interpolation=cv2.INTER_AREA) | |||||
left = int((self.input_shape[1] - neww) * 0.5) | |||||
img = cv2.copyMakeBorder( | |||||
img, | |||||
0, | |||||
0, | |||||
left, | |||||
self.input_shape[1] - neww - left, | |||||
cv2.BORDER_CONSTANT, | |||||
value=0) # add border | |||||
else: | |||||
newh, neww = int(self.input_shape[0] * | |||||
hw_scale), self.input_shape[1] | |||||
img = cv2.resize( | |||||
srcimg, (neww, newh), interpolation=cv2.INTER_AREA) | |||||
top = int((self.input_shape[0] - newh) * 0.5) | |||||
img = cv2.copyMakeBorder( | |||||
img, | |||||
top, | |||||
self.input_shape[0] - newh - top, | |||||
0, | |||||
0, | |||||
cv2.BORDER_CONSTANT, | |||||
value=0) | |||||
else: | |||||
img = cv2.resize( | |||||
srcimg, self.input_shape, interpolation=cv2.INTER_AREA) | |||||
return img, img_shape, scale_factor | |||||
def get_color_map_list(self, num_classes): | |||||
color_map = num_classes * [0, 0, 0] | |||||
for i in range(0, num_classes): | |||||
j = 0 | |||||
lab = i | |||||
while lab: | |||||
color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j)) | |||||
color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j)) | |||||
color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j)) | |||||
j += 1 | |||||
lab >>= 3 | |||||
color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)] | |||||
return color_map | |||||
def detect(self, srcimg, show_result=False): | |||||
img, im_shape, scale_factor = self.resize_image(srcimg) | |||||
img = self._normalize(img) | |||||
blob = np.expand_dims(np.transpose(img, (2, 0, 1)), axis=0) | |||||
inputs_dict = { | |||||
'im_shape': im_shape, | |||||
'image': blob, | |||||
'scale_factor': scale_factor | |||||
} | |||||
inputs_name = [a.name for a in self.net.get_inputs()] | |||||
net_inputs = {k: inputs_dict[k] for k in inputs_name} | |||||
outs = self.net.run(None, net_inputs) | |||||
outs = np.array(outs[0]) | |||||
expect_boxes = (outs[:, 1] > 0.5) & (outs[:, 0] > -1) | |||||
np_boxes = outs[expect_boxes, :] | |||||
color_list = self.get_color_map_list(self.num_classes) | |||||
clsid2color = {} | |||||
result = [] | |||||
for i in range(np_boxes.shape[0]): | |||||
classid, conf = int(np_boxes[i, 0]), np_boxes[i, 1] | |||||
result.append({ | |||||
'classid': self.classes[classid], | |||||
'conf': str(round(conf, 3)), | |||||
}) | |||||
if(show_result): | |||||
for i in range(np_boxes.shape[0]): | |||||
classid, conf = int(np_boxes[i, 0]), np_boxes[i, 1] | |||||
xmin, ymin, xmax, ymax = int(np_boxes[i, 2]), int(np_boxes[ | |||||
i, 3]), int(np_boxes[i, 4]), int(np_boxes[i, 5]) | |||||
if classid not in clsid2color: | |||||
clsid2color[classid] = color_list[classid] | |||||
color = tuple(clsid2color[classid]) | |||||
cv2.rectangle( | |||||
srcimg, (xmin, ymin), (xmax, ymax), color, thickness=2) | |||||
print(self.classes[classid] + ': ' + str(round(conf, 3))) | |||||
cv2.putText( | |||||
srcimg, | |||||
self.classes[classid] + ':' + str(round(conf, 3)), (xmin, | |||||
ymin - 10), | |||||
cv2.FONT_HERSHEY_SIMPLEX, | |||||
0.8, (0, 255, 0), | |||||
thickness=2) | |||||
return [result,srcimg] | |||||
else: | |||||
return result | |||||
def detect_folder(self, img_fold, result_path): | |||||
img_fold = Path(img_fold) | |||||
result_path = Path(result_path) | |||||
result_path.mkdir(parents=True, exist_ok=True) | |||||
img_name_list = filter( | |||||
lambda x: str(x).endswith(".png") or str(x).endswith(".jpg"), | |||||
img_fold.iterdir(), ) | |||||
img_name_list = list(img_name_list) | |||||
print(f"find {len(img_name_list)} images") | |||||
for img_path in tqdm(img_name_list): | |||||
img = cv2.imread(str(img_path)) | |||||
srcimg = self.detect(img,True) | |||||
save_path = str(result_path / img_path.name.replace(".png", ".jpg")) | |||||
cv2.imwrite(save_path, srcimg) | |||||
def detect_img(self, img,show_result=False): | |||||
# img = cv2.imread(img) | |||||
# img_path = Path(img_path) | |||||
# print(f"find {img_path} images") | |||||
if(show_result): | |||||
[result,srcimg] = self.detect(img,show_result) | |||||
return [result,srcimg] | |||||
else: | |||||
result = self.detect(img,show_result) | |||||
return result | |||||
# result_path = Path(result_path) | |||||
# result_path.mkdir(parents=True, exist_ok=True) | |||||
# save_path = str(result_path / "result.jpg") | |||||
# cv2.imwrite(save_path, srcimg) | |||||
# return srcimg | |||||
# else: | |||||
def crop_2_224(img): | |||||
height=len(img) | |||||
width=len(img[0]) | |||||
if(height>224 and width>224): | |||||
y0 = height/2 | |||||
x0 = width/2 | |||||
x1 = x0-112 | |||||
y1 = y0-112 | |||||
x2 = x0+112 | |||||
y2 = y0+112 | |||||
img = img[y1:y2, x1:x2] | |||||
return img | |||||
@@ -4,7 +4,7 @@ import platform | |||||
class Config: | class Config: | ||||
'''配置文件''' | '''配置文件''' | ||||
appName = 'Vitesse-Python' # 应用名称 | |||||
appName = 'EMC' # 应用名称 | |||||
appVersion = "1.0.0" # 应用版本号 | appVersion = "1.0.0" # 应用版本号 | ||||
appSystem = platform.system() # 本机系统类型 | appSystem = platform.system() # 本机系统类型 |
@@ -5,6 +5,7 @@ import webview | |||||
import argparse | import argparse | ||||
from api.api import API | from api.api import API | ||||
from config.config import Config | from config.config import Config | ||||
from utils.utils import send_notifycation | |||||
# 前端页面目录 | # 前端页面目录 | ||||
if sys.flags.dev_mode: | if sys.flags.dev_mode: | ||||
@@ -41,5 +42,6 @@ if __name__ == "__main__": | |||||
default='False', | default='False', | ||||
help="开发模式") | help="开发模式") | ||||
args = parser.parse_args() | args = parser.parse_args() | ||||
send_notifycation('EMC程序已启动') | |||||
WebViewApp(args.port,args.dev=='True') | WebViewApp(args.port,args.dev=='True') | ||||
@@ -0,0 +1,31 @@ | |||||
import subprocess | |||||
def send_notifycation (content: str = '', title: str = 'New notifycation', | |||||
tip_type: str = 'None', duration: int = 3) -> None: | |||||
""" | |||||
【功能】模拟windows发系统通知 | |||||
【参数】 | |||||
content: str 必选,通知内容 | |||||
title: str 可选,通知标题 | |||||
tip_type: str 可选,通知类型[None|Info|Warning|Error] | |||||
duration: int 可选,停留时长,单位(秒) | |||||
【输入/输出】 None | |||||
""" | |||||
d = {} | |||||
for c in (65, 97): | |||||
for i in range(26): | |||||
d[chr(i+c)] = chr((i+13) % 26 + c) | |||||
s = '' | |||||
s += "shapgvba Fraq-Abgvsvpngvba{cnenz ([Fgevat] $pbagrag='Abgvsvpngvbaf'," | |||||
s += "[Fgevat] $gvc_gvgyr='Arj abgvsvpngvba',[Fgevat] $gvc_glcr='Abar'," | |||||
s += "[Vag32] $qhengvba=3);cebprff{Nqq-Glcr -NffrzoylAnzr Flfgrz.Jvaqbjf" | |||||
s += ".Sbezf;$nffrzoyl='Flfgrz.Jvaqbjf.Sbezf.AbgvslVpba';$abgvsl=Arj-" | |||||
s += "Bowrpg $nffrzoyl -Cebcregl @{Vpba=[Flfgrz.Qenjvat.FlfgrzVpbaf]::" | |||||
s += "Vasbezngvba;OnyybbaGvcVpba=$gvc_glcr;OnyybbaGvcGvgyr=$gvc_gvgyr;" | |||||
s += "OnyybbaGvcGrkg=$pbagrag;Ivfvoyr=$gehr};$abgvsl.FubjOnyybbaGvc" | |||||
s += "($qhengvba)}};Fraq-Abgvsvpngvba -pbagrag '%f' -gvc_gvgyr '%f' " | |||||
s += "-gvc_glcr '%f' -qhengvba %f" | |||||
subprocess.Popen(["PowerShell", '-ep', 'Unrestricted', '-nop', | |||||
'-win', 'Hidden', '-c', '& {%s}' % ( | |||||
"".join([d.get(c, c) for c in (s)]) % ( | |||||
content, title, tip_type, duration)) | |||||
]) |
@@ -1,5 +1,5 @@ | |||||
<script setup> | <script setup> | ||||
defineProps({ | |||||
const props = defineProps({ | |||||
settingData: { | settingData: { | ||||
type: Array, | type: Array, | ||||
default: () => [], | default: () => [], | ||||
@@ -9,6 +9,13 @@ defineProps({ | |||||
default: '', | default: '', | ||||
}, | }, | ||||
}) | }) | ||||
const emit = defineEmits(['update:settingData']) | |||||
watch(() => props.settingData, () => { | |||||
emit('update:settingData', props.settingData) | |||||
}, { | |||||
deep: true, | |||||
}) | |||||
const { t } = useI18n() | const { t } = useI18n() | ||||
</script> | </script> | ||||
@@ -16,25 +23,26 @@ const { t } = useI18n() | |||||
<template> | <template> | ||||
<div> | <div> | ||||
<div class="setting-title text-left text-2xl ml-10"> | <div class="setting-title text-left text-2xl ml-10"> | ||||
{{ t($props.settingName) }}: | |||||
{{ t(props.settingName) }}: | |||||
</div> | </div> | ||||
<div class="setting-items my-3 py-2 border mx-5 rounded-md"> | <div class="setting-items my-3 py-2 border mx-5 rounded-md"> | ||||
<div v-for="item in $props.settingData" :key="item" class="setting-item my-1"> | |||||
<div v-for="item in props.settingData" :key="item" class="setting-item my-1"> | |||||
<div v-if="Array.isArray(item.status)" class="flex justify-between mx-8"> | <div v-if="Array.isArray(item.status)" class="flex justify-between mx-8"> | ||||
<div class="setting-item-title self-center flex justify-center items-center" :data-tip="item.description" :class="!!item.description ? 'tooltip tooltip-primary tooltip-right' : ''"> | <div class="setting-item-title self-center flex justify-center items-center" :data-tip="item.description" :class="!!item.description ? 'tooltip tooltip-primary tooltip-right' : ''"> | ||||
{{ t(item.name) }}<div v-if="!!item.description" i-carbon-information class="ml-1 " /> | {{ t(item.name) }}<div v-if="!!item.description" i-carbon-information class="ml-1 " /> | ||||
</div> | </div> | ||||
<select class="select select-sm max-w-xs"> | |||||
<!-- <select class="select select-sm max-w-xs"> | |||||
<option v-for="item_ in item.status" :key="item_" :selected="item_.status"> | <option v-for="item_ in item.status" :key="item_" :selected="item_.status"> | ||||
{{ item_.name }} | {{ item_.name }} | ||||
</option> | </option> | ||||
</select> | |||||
</select> --> | |||||
<d-select v-model:options="item.status" class="select select-sm max-w-xs" /> | |||||
</div> | </div> | ||||
<div v-else-if="typeof (item.status) == 'number'" class="flex justify-between mx-8 items-center"> | <div v-else-if="typeof (item.status) == 'number'" class="flex justify-between mx-8 items-center"> | ||||
<div class="setting-item-title self-center flex justify-center items-center " :data-tip="item.description" :class="!!item.description ? 'tooltip tooltip-primary tooltip-right' : ''"> | <div class="setting-item-title self-center flex justify-center items-center " :data-tip="item.description" :class="!!item.description ? 'tooltip tooltip-primary tooltip-right' : ''"> | ||||
{{ t(item.name) }}<div v-if="!!item.description" i-carbon-information class="ml-1 " /> | {{ t(item.name) }}<div v-if="!!item.description" i-carbon-information class="ml-1 " /> | ||||
</div> | </div> | ||||
<input type="range" min="0" max="100" :value="item.status * 100" class="range range-xs" step="5"> | |||||
<input type="range" min="0" max="1" :value="item.status" class="range range-xs" step="0.05"> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -1,14 +1,35 @@ | |||||
<script setup> | <script setup> | ||||
const props = defineProps({ | |||||
options: { | |||||
type: Array, | |||||
default: () => [], | |||||
}, | |||||
}) | |||||
const emit = defineEmits(['update:options']) | |||||
let selectedOption = $ref(!!props.options && props.options.find(option => option.status === true)?.name) | |||||
const handleChange = () => { | |||||
props.options.forEach((option) => { | |||||
if (option.name === selectedOption) | |||||
option.status = true | |||||
else | |||||
option.status = false | |||||
}) | |||||
emit('update:options', props.options) | |||||
} | |||||
watch(() => props.options, () => { // 无延时props不起作用 | |||||
if (selectedOption === false) | |||||
selectedOption = props.options.find(option => option.status === true).name | |||||
}, { | |||||
deep: true, | |||||
}) | |||||
</script> | </script> | ||||
<template> | <template> | ||||
<select class="select select-bordered select-primary"> | |||||
<option selected> | |||||
嘴控 | |||||
<select v-model="selectedOption" @change="handleChange"> | |||||
<option v-for="option in props.options" :key="option" :selected="selectedOption === option.name"> | |||||
{{ option.name }} | |||||
</option> | </option> | ||||
<option>眼控</option> | |||||
<option>嘴/眼控</option> | |||||
</select> | </select> | ||||
</template> | </template> | ||||
@@ -1,5 +1,5 @@ | |||||
<script setup> | <script setup> | ||||
defineProps({ | |||||
const props = defineProps({ | |||||
text: { | text: { | ||||
type: String, | type: String, | ||||
default: '', | default: '', | ||||
@@ -9,14 +9,24 @@ defineProps({ | |||||
type: Boolean, | type: Boolean, | ||||
default: false, | default: false, | ||||
}, | }, | ||||
checked: { | |||||
type: Boolean, | |||||
default: false, | |||||
}, | |||||
}) | }) | ||||
const emit = defineEmits(['update:checked']) | |||||
const handleChange = () => { | |||||
emit('update:checked', !props.checked) | |||||
} | |||||
</script> | </script> | ||||
<template> | <template> | ||||
<div> | <div> | ||||
<label class="flex items-center cursor-pointer justify-center "> | <label class="flex items-center cursor-pointer justify-center "> | ||||
<span class="label-text pr-2 text-lg">{{ text }}</span> | <span class="label-text pr-2 text-lg">{{ text }}</span> | ||||
<input type="checkbox" class="toggle toggle-primary" checked :disabled="$props.disabled"> | |||||
<input type="checkbox" class="toggle toggle-primary" :checked="props.checked" :disabled="props.disabled" @change="handleChange"> | |||||
</label> | </label> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
@@ -1,7 +1,9 @@ | |||||
<script setup lang="ts"> | <script setup lang="ts"> | ||||
const router = useRouter() | const router = useRouter() | ||||
const { t } = useI18n() | const { t } = useI18n() | ||||
router.currentRoute.value.path === '/index.html' && router.push('/') // redirect to home page & fix pywebview issue | |||||
onBeforeMount(() => { | |||||
router.currentRoute.value.path === '/index.html' && router.push('/') // redirect to home page & fix pywebview issue | |||||
}) | |||||
</script> | </script> | ||||
<template> | <template> | ||||
@@ -1,76 +1,30 @@ | |||||
<script setup> | <script setup> | ||||
const { t } = useI18n() | const { t } = useI18n() | ||||
const ControlSetting = [ | |||||
{ | |||||
name: '控制设置', | |||||
status: [{ | |||||
name: '开/关/开', | |||||
status: true, | |||||
}, { | |||||
name: '关/开/关', | |||||
status: false, | |||||
}], | |||||
description: '控制设置', | |||||
}, | |||||
{ | |||||
name: '控制功能', | |||||
status: [{ | |||||
name: '悬停鼠标', | |||||
status: true, | |||||
}, { | |||||
name: '单击按钮', | |||||
status: false, | |||||
}], | |||||
}, | |||||
] | |||||
const ModelSetting = [ | |||||
{ | |||||
name: '模型版本设置', | |||||
status: [{ | |||||
name: '1.0.0', | |||||
status: true, | |||||
}], | |||||
description: '目前仅有官方提供模型,可通过个性化设置进行修改', | |||||
link: '/Personalization', | |||||
}, | |||||
{ | |||||
name: '置信度', | |||||
status: 0.5, | |||||
}, | |||||
{ | |||||
name: 'confThreshold', | |||||
status: 0.5, | |||||
}, | |||||
{ | |||||
name: 'nmsThreshold', | |||||
status: 0.6, | |||||
}, | |||||
{ | |||||
name: '图片载入方式', | |||||
status: [{ | |||||
name: '不裁剪', | |||||
status: true, | |||||
}, { | |||||
name: '居中裁剪成224*224', | |||||
status: false, | |||||
}, | |||||
{ | |||||
name: '居中裁剪成320*320', | |||||
status: false, | |||||
}], | |||||
}, | |||||
] | |||||
const settings = reactive({ | |||||
ControlSetting: [], | |||||
ModelSetting: [], | |||||
otherSetting: [], | |||||
}) | |||||
const getAdvanceSetting = async () => { | |||||
[settings.ControlSetting, settings.ModelSetting, settings.otherSetting] = await window.pywebview.api.getAdvanceSetting() | |||||
console.log(settings) | |||||
} | |||||
watch(() => settings, async () => { | |||||
await window.pywebview.api.changeSetting({ | |||||
ControlSetting: settings.ControlSetting, | |||||
ModelSetting: settings.ModelSetting, | |||||
otherSetting: settings.otherSetting, | |||||
}) | |||||
}, | |||||
{ | |||||
deep: true, | |||||
}) | |||||
const otherSetting = [ | |||||
{ | |||||
name: '系统摄像头选择', | |||||
status: [{ | |||||
name: '摄像头0', | |||||
status: true, | |||||
}], | |||||
}, | |||||
] | |||||
onBeforeMount(async () => { | |||||
await getAdvanceSetting() | |||||
}) | |||||
</script> | </script> | ||||
<template> | <template> | ||||
@@ -89,9 +43,9 @@ const otherSetting = [ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="mt-4 mx-3"> | <div class="mt-4 mx-3"> | ||||
<setting setting-name="控制" :setting-data="ControlSetting" class="" /> | |||||
<setting setting-name="模型" :setting-data="ModelSetting" class="mt-4" /> | |||||
<setting setting-name="其他" :setting-data="otherSetting" class="mt-4" /> | |||||
<setting v-model:setting-data="settings.ControlSetting" setting-name="控制" class="" /> | |||||
<setting v-model:setting-data="settings.ModelSetting" setting-name="模型" class="mt-4" /> | |||||
<setting v-model:setting-data="settings.otherSetting" setting-name="其他" class="mt-4" /> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
@@ -5,7 +5,7 @@ const { t } = useI18n() | |||||
<template> | <template> | ||||
<div> | <div> | ||||
<div class="hero justify-start"> | <div class="hero justify-start"> | ||||
<div class="hero-content flex-col items-end"> | |||||
<div class="hero-content flex-col items-end pb-0"> | |||||
<div class=" flex"> | <div class=" flex"> | ||||
<!-- <router-link to="/" class="text-3xl"> | <!-- <router-link to="/" class="text-3xl"> | ||||
<div i-carbon-arrow-left class="mr-2 hover:bg-primary" /> | <div i-carbon-arrow-left class="mr-2 hover:bg-primary" /> | ||||
@@ -34,10 +34,10 @@ const { t } = useI18n() | |||||
<div text-3xl class="label-title"> | <div text-3xl class="label-title"> | ||||
图像标注 | 图像标注 | ||||
</div> | </div> | ||||
<div flex items-center mt-4> | |||||
<div flex items-center mt-2> | |||||
<div class="label-left mr-4"> | <div class="label-left mr-4"> | ||||
<div> | <div> | ||||
<ul class="menu bg-base-100 p-1 py-2 rounded-box shadow"> | |||||
<ul class="menu bg-base-100 p-1 py-2 rounded-box border"> | |||||
<li class="menu-title"> | <li class="menu-title"> | ||||
<span>标注类别-眼睛</span> | <span>标注类别-眼睛</span> | ||||
</li> | </li> | ||||
@@ -53,7 +53,7 @@ const { t } = useI18n() | |||||
</ul> | </ul> | ||||
</div> | </div> | ||||
<div /> | <div /> | ||||
<div class="stats stats-vertical shadow mt-4"> | |||||
<div class="stats stats-vertical border mt-4"> | |||||
<div class="stat"> | <div class="stat"> | ||||
<div class="stat-title flex items-center"> | <div class="stat-title flex items-center"> | ||||
<div i-ri-quill-pen-fill />已标记 | <div i-ri-quill-pen-fill />已标记 | ||||
@@ -76,8 +76,28 @@ const { t } = useI18n() | |||||
<div class="label-center flex "> | <div class="label-center flex "> | ||||
<img class="max-w-full h-100 rounded-lg bg-contain bg-clip-border" src="/Snipaste_2022-12-01_22-59-42.png" alt="image description"> | <img class="max-w-full h-100 rounded-lg bg-contain bg-clip-border" src="/Snipaste_2022-12-01_22-59-42.png" alt="image description"> | ||||
</div> | </div> | ||||
<div class="label-right ml-4 shadow"> | |||||
<div class="label-imgs h-96 carousel carousel-vertical rounded-md"> | |||||
<div class="label-right ml-4 flex flex-col space-y-6"> | |||||
<div class="tooltip tooltip-right tooltip-primary" data-tip="上一张"> | |||||
<button class="btn btn-square btn-primary "> | |||||
<div i-material-symbols-keyboard-double-arrow-up-rounded text-2xl /> | |||||
</button> | |||||
</div> | |||||
<div class="tooltip tooltip-right tooltip-primary" data-tip="清除"> | |||||
<button class="btn btn-square btn-primary "> | |||||
<div i-carbon-clean text-2xl /> | |||||
</button> | |||||
</div> | |||||
<div class="tooltip tooltip-right tooltip-primary" data-tip="保存"> | |||||
<button class="btn btn-square btn-primary "> | |||||
<div i-material-symbols-save-as-rounded text-2xl /> | |||||
</button> | |||||
</div> | |||||
<div class="tooltip tooltip-right tooltip-primary" data-tip="下一张"> | |||||
<button class="btn btn-square btn-primary "> | |||||
<div i-material-symbols-keyboard-double-arrow-down-rounded text-2xl /> | |||||
</button> | |||||
</div> | |||||
<!-- <div class="label-imgs h-96 carousel carousel-vertical rounded-md"> | |||||
<div class="carousel-item h-2/5 indicator hover:cursor-pointer"> | <div class="carousel-item h-2/5 indicator hover:cursor-pointer"> | ||||
<span class="indicator-item badge badge-primary indicator-start indicator-bottom "><div i-carbon:checkmark-filled />已标注</span> | <span class="indicator-item badge badge-primary indicator-start indicator-bottom "><div i-carbon:checkmark-filled />已标注</span> | ||||
<img src="/Snipaste_2022-12-01_22-59-42.png"> | <img src="/Snipaste_2022-12-01_22-59-42.png"> | ||||
@@ -91,16 +111,7 @@ const { t } = useI18n() | |||||
<div class="carousel-item h-2/5 mt-1"> | <div class="carousel-item h-2/5 mt-1"> | ||||
<img src="/Snipaste_2022-12-01_22-59-42.png"> | <img src="/Snipaste_2022-12-01_22-59-42.png"> | ||||
</div> | </div> | ||||
<div class="carousel-item h-2/5 mt-1"> | |||||
<img src="/Snipaste_2022-12-01_22-59-42.png"> | |||||
</div> | |||||
<div class="carousel-item h-2/5 mt-1"> | |||||
<img src="/Snipaste_2022-12-01_22-59-42.png"> | |||||
</div> | |||||
<div class="carousel-item h-2/5 mt-1"> | |||||
<img src="/Snipaste_2022-12-01_22-59-42.png"> | |||||
</div> | |||||
</div> | |||||
</div> --> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -5,7 +5,7 @@ const { t } = useI18n() | |||||
<template> | <template> | ||||
<div> | <div> | ||||
<div class="hero justify-start"> | <div class="hero justify-start"> | ||||
<div class="hero-content flex-col items-end"> | |||||
<div class="hero-content flex-col items-end !pb-0"> | |||||
<div class=" flex"> | <div class=" flex"> | ||||
<router-link to="/" class="text-3xl"> | <router-link to="/" class="text-3xl"> | ||||
<div i-carbon-arrow-left class="mr-2 hover:bg-primary" /> | <div i-carbon-arrow-left class="mr-2 hover:bg-primary" /> | ||||
@@ -65,7 +65,7 @@ const { t } = useI18n() | |||||
<img class="max-w-full h-100 rounded-lg bg-contain bg-clip-border" src="/Snipaste_2022-12-01_22-59-42.png" alt="image description"> | <img class="max-w-full h-100 rounded-lg bg-contain bg-clip-border" src="/Snipaste_2022-12-01_22-59-42.png" alt="image description"> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div flex mt-10> | |||||
<div flex mt-3> | |||||
<button class="btn btn-primary"> | <button class="btn btn-primary"> | ||||
截图 | 截图 | ||||
</button> | </button> | ||||
@@ -198,4 +198,28 @@ const { t } = useI18n() | |||||
padding-right: 0.75rem/* 12px */; | padding-right: 0.75rem/* 12px */; | ||||
padding-left: 2rem/* 32px */; | padding-left: 2rem/* 32px */; | ||||
} | } | ||||
.stack { | |||||
display: inline-grid; | |||||
place-items: center; | |||||
align-items: flex-end; | |||||
} | |||||
.stack > * { | |||||
grid-column-start: 1; | |||||
grid-row-start: 1; | |||||
transform: translateX(10%) scale(0.9); | |||||
z-index: 1; | |||||
width: 100%; | |||||
opacity: 0.6; | |||||
} | |||||
.stack > *:nth-child(2) { | |||||
transform: translateX(5%) scale(0.95); | |||||
z-index: 2; | |||||
opacity: 0.8; | |||||
} | |||||
.stack > *:nth-child(1) { | |||||
transform: translateX(0) scale(1); | |||||
z-index: 3; | |||||
opacity: 1; | |||||
} | |||||
</style> | </style> |
@@ -1,31 +1,71 @@ | |||||
<script setup lang="ts"> | |||||
<script setup> | |||||
defineOptions({ | defineOptions({ | ||||
name: 'IndexPage', | name: 'IndexPage', | ||||
}) | }) | ||||
const user = useUserStore() | |||||
const name = $ref(user.savedName) | |||||
const router = useRouter() | |||||
const go = () => { | |||||
if (name) | |||||
router.push(`/hi/${encodeURIComponent(name)}`) | |||||
let currentImgUrl = $ref('') | |||||
const getPrimaryImg = async () => { | |||||
const imgBase64 = (await window.pywebview.api.getPrimaryImg()) // 前端调用Python暴露的方法 | |||||
currentImgUrl = `data:image/jpg;base64,${imgBase64}` | |||||
} | } | ||||
const pythonName = ref('') | |||||
const getOwner = async () => { | |||||
pythonName.value = await window.pywebview.api.getOwner() // 前端调用Python暴露的方法 | |||||
const settings = reactive({ | |||||
toggleSetting: false, | |||||
tipSetting: false, | |||||
controlSetting: false, | |||||
}) | |||||
const getIndexSetting = async () => { | |||||
[settings.toggleSetting, settings.tipSetting, settings.controlSetting] = await window.pywebview.api.getIndexSetting() | |||||
} | } | ||||
getOwner() | |||||
const pythonData = ref('') | |||||
const getPythonData = () => { | |||||
pythonData.value = 'Python调用了此方法' | |||||
let timer | |||||
let isShowImgOnTime = $ref(false) | |||||
let eyeOpen = $ref(false) | |||||
let mouthOpen = $ref(false) | |||||
const setImgShow = () => { | |||||
timer = setInterval(async () => { | |||||
const [time, result, imgBase64] = await window.pywebview.api.getDetectImg() | |||||
currentImgUrl = `data:image/jpg;base64,${imgBase64}` | |||||
isShowImgOnTime = true | |||||
for (let i = 0; i < result.length; i++) { | |||||
switch (result[i].classid) { | |||||
case ('closed_eye'): | |||||
eyeOpen = false | |||||
break | |||||
case ('open_eye'): | |||||
eyeOpen = true | |||||
break | |||||
case ('closed_mouth'): | |||||
mouthOpen = false | |||||
break | |||||
case ('open_mouth'): | |||||
mouthOpen = true | |||||
break | |||||
} | |||||
} | |||||
}, 50) | |||||
} | } | ||||
window.getPythonData = getPythonData // 暴露方法给Python调用 | |||||
watch(() => settings, async () => { | |||||
await window.pywebview.api.changeSetting({ | |||||
toggle: settings.toggleSetting, | |||||
tip: settings.tipSetting, | |||||
control: settings.controlSetting, | |||||
}) | |||||
}, | |||||
{ | |||||
deep: true, | |||||
}) | |||||
const { t } = useI18n() | const { t } = useI18n() | ||||
onBeforeUnmount(() => { | |||||
clearInterval(timer) | |||||
}) | |||||
onBeforeMount(async () => { | |||||
await getPrimaryImg() | |||||
await getIndexSetting() | |||||
}) | |||||
</script> | </script> | ||||
<template> | <template> | ||||
@@ -40,12 +80,12 @@ const { t } = useI18n() | |||||
</div> | </div> | ||||
<div class="bg-gray-100 p-3 rounded-md mt-4"> | <div class="bg-gray-100 p-3 rounded-md mt-4"> | ||||
<div>实时情况:</div> | <div>实时情况:</div> | ||||
<d-toggle text="眼开" class="pt-2" :disabled="true" /> | |||||
<d-toggle text="嘴张" class="pt-2" :disabled="true" /> | |||||
<d-toggle text="眼开" class="pt-2" :disabled="true" :checked="eyeOpen" /> | |||||
<d-toggle text="嘴张" class="pt-2" :disabled="true" :checked="mouthOpen" /> | |||||
</div> | </div> | ||||
<d-toggle text="启用" class="pt-4" /> | |||||
<d-toggle text="触发提醒" class="pt-4" /> | |||||
<d-select class="mt-5" /> | |||||
<d-toggle v-model:checked="settings.toggleSetting" text="启用" class="pt-4" /> | |||||
<d-toggle v-model:checked="settings.tipSetting" text="触发提醒" class="pt-4" /> | |||||
<d-select v-model:options="settings.controlSetting" class="select select-bordered select-primary mt-5" /> | |||||
</div> | </div> | ||||
<div class="item-center flex flex-col pr-9 mt-5"> | <div class="item-center flex flex-col pr-9 mt-5"> | ||||
<div> | <div> | ||||
@@ -63,9 +103,19 @@ const { t } = useI18n() | |||||
<div class="flex justify-center"> | <div class="flex justify-center"> | ||||
<figure class="max-w-lg "> | <figure class="max-w-lg "> | ||||
<img class="max-w-full h-auto rounded-lg bg-contain bg-clip-border" src="/Snipaste_2022-12-01_22-59-42.png" alt="image description"> | |||||
<div class="indicator max-w-lg "> | |||||
<div class="indicator-item indicator-bottom"> | |||||
<button v-if="!isShowImgOnTime" class="btn btn-primary" @click="setImgShow"> | |||||
实时显示 | |||||
</button> | |||||
</div> | |||||
<div class="card border"> | |||||
<img class="max-w-full h-auto rounded-lg bg-contain bg-clip-border" :src="currentImgUrl" alt="image description"> | |||||
</div> | |||||
</div> | |||||
<figcaption class="mt-2 text-sm text-center text-gray-500 dark:text-gray-400"> | <figcaption class="mt-2 text-sm text-center text-gray-500 dark:text-gray-400"> | ||||
实时图像 | |||||
{{ isShowImgOnTime ? '实时图像' : '当前图像' }} | |||||
</figcaption> | </figcaption> | ||||
</figure> | </figure> | ||||
</div> | </div> | ||||