@ -0,0 +1,75 @@ |
|||
import torch |
|||
from flask import Flask, request |
|||
import torch.nn as nn |
|||
import pickle |
|||
import jieba |
|||
|
|||
app = Flask(__name__) |
|||
# 初始化模型 |
|||
result = "" |
|||
|
|||
|
|||
class Model(nn.Module): |
|||
def __init__(self): |
|||
super(Model, self).__init__() |
|||
self.embedding = nn.Embedding(10002, 300, padding_idx=10001) |
|||
self.lstm = nn.LSTM(300, 128, 2, bidirectional=True, batch_first=True, dropout=0.5) |
|||
self.fc = nn.Linear(128 * 2, 2) |
|||
|
|||
def forward(self, x): |
|||
x, _ = x |
|||
out = self.embedding(x) # [batch_size, seq_len, embeding]=[128, 32, 300] |
|||
out, _ = self.lstm(out) |
|||
out = self.fc(out[:, -1, :]) # 句子最后时刻的 hidden state |
|||
return out |
|||
|
|||
|
|||
model = Model() |
|||
model.load_state_dict(torch.load('./THUCNews/saved_dict/TextRNN.ckpt', map_location=torch.device("cpu"))) |
|||
model.eval() |
|||
stopwords = open('./THUCNews/data/hit_stopwords.txt', encoding='utf8').read().split('\n')[:-1] |
|||
|
|||
vocab = pickle.load(open('./THUCNews/data/vocab.pkl', 'rb')) |
|||
classes = ['negative', 'positive'] |
|||
|
|||
s = '空调吵,住在电梯旁,电梯门口放垃圾箱,极臭,布草间没关门,也臭,臭到房间里,门下塞毛巾也挡不住臭味,开窗外面吵,关窗空调吵,楼下早餐桌子上摆满垃圾没人整理,不能再差的体验了' |
|||
|
|||
|
|||
# s = '东东还算不错。重装系统时,网上查不到怎么修改BIOS,才能安装?问题请帮忙解决!' |
|||
|
|||
|
|||
@app.route('/api/content', methods=["POST"]) |
|||
def content(): |
|||
get_json = request.get_json() |
|||
global model |
|||
global result |
|||
global classes |
|||
result = "" |
|||
s = get_json.get("content")[0] |
|||
print(6777, s) |
|||
try: |
|||
s = list(jieba.lcut(s)) |
|||
s = [i for i in s if i not in stopwords] |
|||
s = [vocab.get(i, 10000) for i in s] |
|||
if len(s) > 64: |
|||
s = s[:64] |
|||
else: |
|||
for i in range(64 - len(s)): |
|||
s.append(vocab['<PAD>']) |
|||
|
|||
outputs = model((torch.LongTensor(s).unsqueeze(0), None)) |
|||
print(torch.argmax(outputs)) |
|||
result = classes[torch.argmax(outputs)] |
|||
except Exception as e: # 未捕获到异常,程序直接报错 |
|||
result = e |
|||
return "pridicting" |
|||
|
|||
|
|||
@app.route('/api/model_res', methods=['GET']) |
|||
def model_res(): |
|||
global result |
|||
return str(result) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
app.run(host="127.0.0.1", port=8006) |
@ -0,0 +1,24 @@ |
|||
# Logs |
|||
logs |
|||
*.log |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
pnpm-debug.log* |
|||
lerna-debug.log* |
|||
|
|||
node_modules |
|||
dist |
|||
dist-ssr |
|||
*.local |
|||
|
|||
# Editor directories and files |
|||
.vscode/* |
|||
!.vscode/extensions.json |
|||
.idea |
|||
.DS_Store |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw? |
@ -0,0 +1,7 @@ |
|||
# Vue 3 + Vite |
|||
|
|||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more. |
|||
|
|||
## Recommended IDE Setup |
|||
|
|||
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously Volar) and disable Vetur |
@ -0,0 +1,13 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>毛囊结果检测</title> |
|||
</head> |
|||
<body> |
|||
<div id="app"></div> |
|||
<script type="module" src="/src/main.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,23 @@ |
|||
{ |
|||
"name": "vitebase", |
|||
"private": true, |
|||
"version": "0.0.0", |
|||
"type": "module", |
|||
"scripts": { |
|||
"dev": "vite", |
|||
"build": "vite build", |
|||
"preview": "vite preview" |
|||
}, |
|||
"dependencies": { |
|||
"axios": "^1.6.8", |
|||
"element-plus": "^2.7.2", |
|||
"lodash": "^4.17.21", |
|||
"vue": "^3.4.21" |
|||
}, |
|||
"devDependencies": { |
|||
"@vitejs/plugin-vue": "^5.0.4", |
|||
"unplugin-auto-import": "^0.17.5", |
|||
"unplugin-vue-components": "^0.26.0", |
|||
"vite": "^5.2.0" |
|||
} |
|||
} |
After Width: | Height: | Size: 87 KiB |
@ -0,0 +1,169 @@ |
|||
<template> |
|||
<div class="bgimg"> |
|||
<el-row class="pa-2"> |
|||
<el-col :span="8" class="text-align"> |
|||
<div> |
|||
<el-input class="opacitybg" v-model="content" :rows="15" type="textarea" placeholder="请输入文本进行预测" /> |
|||
</div> |
|||
<div class="pa-2"> |
|||
<el-button type="success" @click="sendData" :disabled="loading" :loading="loading"> |
|||
开始预测 |
|||
</el-button> |
|||
</div> |
|||
</el-col> |
|||
<el-col :span="16" class="sendButton text-align"> |
|||
<div v-if="result"> |
|||
<el-tag type="success" v-if="result === 'positive'" class="tagClass"> |
|||
预测结果:{{ classZh[result] }} |
|||
<el-icon style="width: 48px;font-size: 1.6em;position: relative;top: 20px;"> |
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<path |
|||
d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M13,9.94L14.06,11L15.12,9.94L16.18,11L17.24,9.94L15.12,7.82L13,9.94M8.88,9.94L9.94,11L11,9.94L8.88,7.82L6.76,9.94L7.82,11L8.88,9.94M12,17.5C14.33,17.5 16.31,16.04 17.11,14H6.89C7.69,16.04 9.67,17.5 12,17.5Z" /> |
|||
</svg> |
|||
</el-icon> |
|||
</el-tag> |
|||
<el-tag type="danger" v-else-if="result === 'negative'" class="tagClass"> |
|||
预测结果:{{ classZh[result] }} |
|||
<el-icon style="width: 48px;font-size: 1.6em;position: relative;top: 20px;"> |
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|||
<path |
|||
d="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M15.5,8C16.3,8 17,8.7 17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M12,14C13.75,14 15.29,14.72 16.19,15.81L14.77,17.23C14.32,16.5 13.25,16 12,16C10.75,16 9.68,16.5 9.23,17.23L7.81,15.81C8.71,14.72 10.25,14 12,14Z" /> |
|||
</svg> |
|||
</el-icon> |
|||
</el-tag> |
|||
</div> |
|||
<div v-else> |
|||
<el-progress class="opacitybg" :percentage="percentage" :stroke-width="15" :striped="percentage !== 100" |
|||
:striped-flow="percentage !== 100" :duration="duration" :status="percentage === 100 ? 'success' : ''" |
|||
type="circle" /> |
|||
</div> |
|||
</el-col> |
|||
</el-row> |
|||
<el-row> |
|||
<el-col class="pa-2 text-align" :span="8" v-for="(item, index) in imgList" :index="index"> |
|||
<div class="pa-2">{{ item.label }}</div> |
|||
<div> |
|||
<el-image style="height: calc(100vh - 420px)" :src="item.imgPath" :zoom-rate="1.2" :max-scale="7" |
|||
:min-scale="0.2" :preview-src-list="[item.imgPath]" :initial-index="4" fit="cover" close-on-press-escape /> |
|||
</div> |
|||
</el-col> |
|||
</el-row> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { result } from "lodash"; |
|||
import imgPath from "./assets/config/imgconf.json"; |
|||
export default { |
|||
name: "app", |
|||
data() { |
|||
return { |
|||
result: "", |
|||
content: "", |
|||
percentage: 0, |
|||
isSend: false, |
|||
loading: false, |
|||
imgList: [], |
|||
sendmessage: null, |
|||
classZh: { |
|||
negative: "消极", |
|||
positive: "积极", |
|||
}, |
|||
}; |
|||
}, |
|||
methods: { |
|||
async sendData() { |
|||
this.percentage = 0; |
|||
this.isSend = true; |
|||
this.loading = true; |
|||
this.result = ""; |
|||
if (this.sendmessage) { |
|||
this.sendmessage.close() |
|||
} |
|||
this.sendmessage = this.$message({ |
|||
showClose: true, |
|||
repeatNum: 1, |
|||
duration: 0, |
|||
message: "王一鸣202207100038", |
|||
type: 'success', |
|||
}); |
|||
let data = { |
|||
content: [this.content], |
|||
}; |
|||
let res = await this.$axios.post("/api/content", data); |
|||
if (res.status === 200) { |
|||
this.isSend = false; |
|||
} |
|||
}, |
|||
}, |
|||
async mounted() { |
|||
this.imgList = imgPath; |
|||
for (let index = 0; index < this.imgList.length; index++) { |
|||
let element = this.imgList[index]; |
|||
element.imgPath = new URL(element.path, import.meta.url).href; |
|||
} |
|||
}, |
|||
watch: { |
|||
async isSend(nev, olv) { |
|||
let total = 96; |
|||
if (nev) { |
|||
while (this.percentage < total) { |
|||
if (this.percentage < total) { |
|||
let random = Math.floor(Math.random() * 2); |
|||
this.percentage += random; |
|||
} |
|||
await new Promise((resolve) => setTimeout(resolve, 50)); |
|||
} |
|||
if (this.percentage > total - 1) { |
|||
while (!this.result) { |
|||
let res = await this.$axios.get("/api/model_res"); |
|||
this.percentage = 100; |
|||
this.result = res.data |
|||
await new Promise((resolve) => setTimeout(resolve, 1000)); |
|||
} |
|||
} |
|||
} else { |
|||
} |
|||
}, |
|||
result(nev, olv) { |
|||
this.loading = !nev; |
|||
}, |
|||
}, |
|||
computed: { |
|||
duration() { |
|||
return Math.floor(this.percentage / 10); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.bgimg { |
|||
height: 100vh; |
|||
width: 100vw; |
|||
color: #fff; |
|||
background-image: url("@/assets/imgs/otherimgs/bg.jpg"); |
|||
} |
|||
|
|||
.sendButton { |
|||
align-content: center; |
|||
} |
|||
|
|||
.text-align { |
|||
text-align: center; |
|||
} |
|||
|
|||
.pa-2 { |
|||
padding: 4px; |
|||
} |
|||
|
|||
.tagClass { |
|||
height: 1.6em; |
|||
font-size: 3em; |
|||
} |
|||
|
|||
|
|||
.opacitybg { |
|||
opacity: 0.6; |
|||
} |
|||
</style> |
@ -0,0 +1,6 @@ |
|||
[ |
|||
{ |
|||
"label": "f1-score", |
|||
"path": "assets/imgs/bilstm/1.png" |
|||
} |
|||
] |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 383 KiB |
After Width: | Height: | Size: 87 KiB |
@ -0,0 +1,13 @@ |
|||
// main.ts
|
|||
import { createApp } from 'vue' |
|||
import ElementPlus from 'element-plus' |
|||
import 'element-plus/dist/index.css' |
|||
import App from './App.vue' |
|||
import './style.css' |
|||
import _ from 'lodash' |
|||
import axios from "axios" |
|||
const app = createApp(App) |
|||
app.config.globalProperties.$_ = _ |
|||
app.config.globalProperties.$axios = axios |
|||
app.use(ElementPlus) |
|||
app.mount('#app') |
@ -0,0 +1,5 @@ |
|||
html, |
|||
body { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
@ -0,0 +1,24 @@ |
|||
// vite.config.ts
|
|||
import { defineConfig } from 'vite' |
|||
import vue from '@vitejs/plugin-vue' |
|||
import { resolve } from 'path'; |
|||
|
|||
// https://vitejs.dev/config/
|
|||
export default defineConfig({ |
|||
plugins: [vue()], |
|||
server: { |
|||
proxy: { |
|||
'/api': { |
|||
target: 'http://127.0.0.1:8006/api', |
|||
changeOrigin: true, |
|||
rewrite: (path) => path.replace(/^\/api/, '') |
|||
} |
|||
} |
|||
}, |
|||
resolve: { |
|||
alias: { |
|||
// @ 替代为 src
|
|||
'@': resolve(__dirname, 'src'), |
|||
}, |
|||
}, |
|||
}) |
@ -0,0 +1,19 @@ |
|||
项目启用: |
|||
后端启用: |
|||
使用pycharm打开项目文件夹,打开app.py(需要将缺失的包安装:例如flask、numpy等),在app.py中运行即可 |
|||
前端启用: |
|||
使用vscode打开前端项目文件夹,在确保有node环境(百度搜索node,下载并安装18.20.0版本)后在终端(ctrl+shift+`)输入npm run dev,即可启用前端项目,如果缺失运行包的情况下,需要先npm install安装项目运行包。 |
|||
项目前端开发流程: |
|||
1.安装node18.20.0版本,安装VScode最新版本, |
|||
2.打算使用vite(打包工具)+vue(前端框架)进行开发 |
|||
3.在vscode终端中输入npm init vite@latest my-vue-app -- --template vue 搭建vue项目框架, |
|||
4.cd my-vue-app进入项目中,npm install安装项目运行包。 |
|||
5.在终端输入npm run dev,即可启动项目, |
|||
6.使用axios进行请求,与后端进行数据交互。 |
|||
7.使用element-plus进行前端页面开发。 |
|||
8.使用lodash进行数据处理。加快开发。 |
|||
9.开发的页面均在app.vue文件中。 |
|||
前端页面开发的思想: |
|||
1.需要一个输入框和按钮发送请求(在发送后未出结果前,按钮不能再次点击,防止请求次数过多,造成资源浪费),以及后端数据处理过程中,页面上需要给出提示,及时反馈给用户 |
|||
2.需要需要把训练的bilstm模型的参数放到页面上,便于用户查看,便于用户理解 |
|||
3.在后端数据处理完以后,请求处理后的数据,在页面上显示出来 |
After Width: | Height: | Size: 825 KiB |
@ -0,0 +1,81 @@ |
|||
import cv2 |
|||
import torch |
|||
import numpy as np |
|||
from PIL import Image |
|||
from ultralytics import YOLO |
|||
|
|||
|
|||
# 3个输入参数 |
|||
img_path = 'img0002.jpg' |
|||
iou = 0.1 |
|||
conf = 0.25 |
|||
|
|||
def split_image(img_path, size=(800, 800)): |
|||
img = cv2.imread(img_path) |
|||
height, width = img.shape[:2] |
|||
rows = (height + size[1] - 1) // size[1] |
|||
cols = (width + size[0] - 1) // size[0] |
|||
img_list = [] |
|||
indexes = [] |
|||
for r in range(rows): |
|||
for c in range(cols): |
|||
y1 = r * size[1] |
|||
y2 = min((r + 1) * size[1], height) |
|||
x1 = c * size[0] |
|||
x2 = min((c + 1) * size[0], width) |
|||
split = img[y1:y2, x1:x2] |
|||
img_list.append(split) |
|||
indexes.append((r, c)) |
|||
return img_list, indexes, (height, width) |
|||
|
|||
|
|||
def combine_images(pred_imgs, indexes, size=(800, 800), img_shape=(3000, 4000)): |
|||
combined_img = np.zeros((img_shape[0], img_shape[1], 3), dtype=np.uint8) |
|||
for idx, (r, c) in enumerate(indexes): |
|||
y1 = r * size[1] |
|||
y2 = min((r + 1) * size[1], img_shape[0]) |
|||
x1 = c * size[0] |
|||
x2 = min((c + 1) * size[0], img_shape[1]) |
|||
combined_img[y1:y2, x1:x2] = pred_imgs[idx][:y2 - y1, :x2 - x1] |
|||
return combined_img |
|||
|
|||
follicle_groups_detector = YOLO('follicle_groups.pt') |
|||
follicles_detector = YOLO('follicles.pt') |
|||
|
|||
results = follicle_groups_detector(img_path, iou=iou, conf=conf) |
|||
for r in results: |
|||
num_follicle_groups = len(r.boxes) |
|||
im_array = r.plot() |
|||
im = Image.fromarray(im_array[..., ::-1]) |
|||
im.save('results_1.jpg') #输出结果图1 |
|||
|
|||
img_list, indexes, (height, width) = split_image(img_path) |
|||
print(f"Number of image blocks: {len(img_list)}") |
|||
num_small_follicles = 0 |
|||
num_big_follicles = 0 |
|||
pred_imgs = [] |
|||
for img in img_list: |
|||
results = follicles_detector(img, iou=iou, conf=conf) |
|||
for r in results: |
|||
num_small_follicles += torch.sum(r.boxes.cls == 0).item() |
|||
num_big_follicles += torch.sum(r.boxes.cls == 1).item() |
|||
im_array = r.plot() |
|||
pred_imgs.append(im_array) |
|||
|
|||
# 输出的3个结果文本 |
|||
print('毛囊群数量:', num_follicle_groups) |
|||
print('大毛囊数量:', num_big_follicles) |
|||
print('小毛囊数量:', num_small_follicles) |
|||
|
|||
combined_img = combine_images(pred_imgs, indexes, size=(800, 800), img_shape=(height, width)) |
|||
combined_image_pil = Image.fromarray(combined_img[..., ::-1]) |
|||
combined_image_pil.save('results_2.jpg') #输出结果图2 |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
After Width: | Height: | Size: 481 KiB |
After Width: | Height: | Size: 723 KiB |