Browse Source

1244

master
lichong 8 months ago
parent
commit
d189cefddf
  1. BIN
      api/exam.pt
  2. 46
      api/inference.py
  3. 125
      api/main.py
  4. BIN
      api/yolov8n-cls.pt
  5. BIN
      api/yolov8n-pose.pt
  6. 9
      front/public/config.json
  7. 3
      front/src/assets/css/base.css
  8. 2
      front/src/component/table.vue
  9. 89
      front/src/views/student.vue
  10. 47
      front/src/views/teacher.vue

BIN
api/exam.pt

Binary file not shown.

46
api/inference.py

@ -0,0 +1,46 @@
from ultralytics import YOLO
import cv2
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
det_model = YOLO(os.path.join(current_dir, "yolov8n-pose.pt"))
cls_model = YOLO(os.path.join(current_dir, "yolov8n-cls.pt"))
# det_model = YOLO('yolov8n-pose.pt')
# cls_model = YOLO('yolov8n-cls.pt')
class_name = ['normal', 'raise_hand', 'speak', 'stand', 'turn_head', 'use_phone']
def infer(image):
det_results = det_model(image, conf=0.5, iou=0.25)
for r in det_results:
if len(r) == 0:
return 'leave'
box = r.boxes.xyxy
if len(box) == 1:
crop_image = image[int(box[0][1]):int(box[0][3]), int(box[0][0]):int(box[0][2])]
cls_results = cls_model(crop_image)
return class_name[cls_results[0].probs.top1]
else:
return 'many_humans'
# image_path = os.path.join(current_dir, "frames/video1_1.png")
# result = infer(image_path)
# print(result)
cap = cv2.VideoCapture(0)
if cap.isOpened():
while True:
ret, frame = cap.read()
if ret:
result = infer(frame)
print(result)
# 设置要添加的文本内容和位置
org = (50, 50) # 文本起始位置
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
color = (255, 0, 0) # 文本颜色,BGR格式
thickness = 2 # 文本字体粗细
# 添加文本到图像
cv2.putText(frame, result, org, font, font_scale, color, thickness, cv2.LINE_AA)
cv2.imshow('test.png', frame)
cv2.waitKey(1000)

125
api/main.py

@ -1,9 +1,8 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from pymongo import MongoClient
from ultralytics import YOLO
from PIL import Image
import base64
from io import BytesIO
from bson import ObjectId
import hashlib
import json
@ -11,25 +10,30 @@ import os
from flask import Flask, request
from flask_socketio import SocketIO, emit, join_room, leave_room
import time
import numpy as np
import cv2
app = Flask(__name__)
client = MongoClient("mongodb://localhost:27019/")
db = client["back"]
usercollection = db["users"]
zuobicollection = db["zuobi"]
# sockitIo解决跨域问题
app.config["SECRET_KEY"] = "secret!"
CORS(app, resources={r"/socket.io/*": {"origins": "*"}}) # 允许所有来源的请求
socketio = SocketIO(app, cors_allowed_origins="*")
# CORS(app)
# 存储连接的客户端的房间号
clients = []
adminSid = ""
# 初始化考试状态
studentStatus = {}
current_dir = os.path.dirname(os.path.abspath(__file__))
weights = os.path.join(current_dir, "exam.pt")
print(89744, weights)
model = YOLO(weights)
frame_rate_divider = 300 # 设置帧率除数
det_model = YOLO(os.path.join(current_dir, "yolov8n-pose.pt"))
cls_model = YOLO(os.path.join(current_dir, "yolov8n-cls.pt"))
class_name = ["normal", "raise_hand", "speak", "stand", "turn_head", "use_phone"]
frame_rate_divider = 90 # 设置帧率除数
frame_count = 0 # 初始化帧计数器
@ -43,7 +47,7 @@ def dealStudentStatus():
dealStudentStatus()
@socketio.on("connect", namespace="/ws/video")
@socketio.on("connect")
def on_connect():
global clients
global adminSid
@ -53,11 +57,10 @@ def on_connect():
isAdmin = request.args.get("isAdmin")
if isAdmin == "1":
adminSid = sid
print(87874, adminSid)
clients.append({"id": id, "sid": sid, "isAdmin": isAdmin})
@socketio.on("disconnect", namespace="/ws/video")
@socketio.on("disconnect")
def on_disconnect():
global clients
for item in clients:
@ -66,19 +69,16 @@ def on_disconnect():
print(f"Client disconnected with id {item['id']}")
@socketio.on("video", namespace="/ws/video")
@socketio.on("video")
def handle_video_frame(message):
# 广播视频帧给老师
global adminSid
global studentStatus
global model
global frame_rate_divider
global frame_count
# print(f"Received video frame from {message['userId']}")
socketio.emit(
"teacherVideo" + message["userId"], message, to=adminSid, namespace="/ws/video"
)
frame_count += 1 # 更新帧计数器
frame_count %= frame_rate_divider # 更新帧计数器
if frame_count % frame_rate_divider == 0:
# 这里省略了实际的base64数据
base64_str = message["data"]
@ -86,38 +86,61 @@ def handle_video_frame(message):
_, img_data = base64_str.split(",")
img_data = base64.b64decode(img_data)
# 将字节流转换为PIL图像对象
image = Image.open(BytesIO(img_data))
results = model.predict(source=image, iou=0.5, conf=0.25)
det = results[0]
checkVedioResult(message, det)
frame_count += 1 # 更新帧计数器
frame_count %= frame_rate_divider # 更新帧计数器
# print(99777,frame_count)
# if studentStatus[message["userId"]]:
# print(f"学生{message['userId']}已作弊", f"{studentStatus[message['userId']]}")
# 将字节数据转换为NumPy数组
np_data = np.frombuffer(img_data, dtype=np.uint8)
# 使用cv2.imdecode将数组解码为图像
image = cv2.imdecode(np_data, cv2.IMREAD_COLOR)
checkVedioResult(message, image)
socketio.emit(
"teacherVideo" + message["userId"], message, to=adminSid
)
def checkVedioResult(message, det):
def checkVedioResult(message, image):
# 如果有有效的检测结果
# {"cheating": "疑似作弊", "good": "良好", "normal": "正常"}
if det is not None and len(det):
results = [] # 初始化结果列表
for res in det.boxes:
for box in res:
# 提前计算并转换数据类型
class_id = int(box.cls.cpu())
bbox = box.xyxy.cpu().squeeze().tolist()
bbox = [int(coord) for coord in bbox] # 转换边界框坐标为整数
result = {
"bbox": bbox, # 边界框
"score": box.conf.cpu().squeeze().item(), # 置信度
"class_id": class_id, # 类别ID
}
results.append(result) # 将结果添加到列表
global det_model
global cls_model
global class_name
global clients
global adminSid
det_results = det_model(image,conf=0.5, iou=0.25)
type = ""
for r in det_results:
if len(r) == 0:
type = "leave"
break
# print(57841,r.boxes)
box = r.boxes.xyxy
if len(box) == 1:
crop_image = image[
int(box[0][1]) : int(box[0][3]), int(box[0][0]) : int(box[0][2])
]
# cv2.imshow('test.png', crop_image)
# cv2.waitKey(0)
cls_results = cls_model(crop_image)
type = class_name[cls_results[0].probs.top1]
else:
type = "many_humans"
message["type"] = type
socketio.emit(
"teacherTalk" + message["userId"],
message,
to=adminSid,
)
sid = ""
for item in clients:
if item["id"] == message["userId"]:
sid = item["sid"]
socketio.emit(
"studentMsg" + message["userId"], message, to=sid
)
# print(97444, type)
zuobiItem = {
"userId": message["userId"],
"msg": class_id,
"type": class_id,
"msg": message["data"],
"type": type,
"create_at": time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(time.time())
),
@ -129,13 +152,14 @@ def checkVedioResult(message, det):
addzuobi(zuobiItem)
@socketio.on("talk", namespace="/ws/video")
@socketio.on("talk")
def handle_talk(message):
# 说话实时传输给老师
global adminSid
# print(f"Received video frame from {message['userId']}")
message["type"] = "talk"
socketio.emit(
"teacherTalk" + message["userId"], message, to=adminSid, namespace="/ws/video"
"teacherTalk" + message["userId"], message, to=adminSid
)
zuobiItem = {
"userId": message["userId"],
@ -148,13 +172,13 @@ def handle_talk(message):
addzuobi(zuobiItem)
@socketio.on("sendMsg", namespace="/ws/video")
@socketio.on("sendMsg")
def handle_msg(message):
# 说话实时传输给老师
global adminSid
# print(f"Received video frame from {message['userId']}")
socketio.emit(
"teacherMsg" + message["userId"], message, to=adminSid, namespace="/ws/video"
"teacherMsg" + message["userId"], message, to=adminSid
)
zuobiItem = {
"userId": message["userId"],
@ -167,14 +191,15 @@ def handle_msg(message):
addzuobi(zuobiItem)
@socketio.on("answerMsg", namespace="/ws/video")
@socketio.on("answerMsg")
def handle_answer_msg(message):
sid = ""
for item in clients:
if item["id"] == message["userId"]:
sid = item["sid"]
message["type"] = "answer"
socketio.emit(
"studentMsg" + message["userId"], message, to=sid, namespace="/ws/video"
"studentMsg" + message["userId"], message, to=sid
)
zuobiItem = {
"userId": message["userId"],
@ -208,9 +233,9 @@ def initData():
"nianji": "",
"banji": "",
"xueshengxingming": "老师",
"xuehao": "0000",
"xuehao": "admin",
"kaoshengtupian": "",
"mima": md5_encrypt("0000"),
"mima": md5_encrypt("admin"),
"chengji": "",
"kaoshileixing": "",
"kaoshikemu": "",
@ -476,4 +501,4 @@ def getzuobi():
if __name__ == "__main__":
initData()
# app.run(debug=True)
socketio.run(app, debug=True)
socketio.run(app, host='0.0.0.0', port=5000, debug=True)

BIN
api/yolov8n-cls.pt

Binary file not shown.

BIN
api/yolov8n-pose.pt

Binary file not shown.

9
front/public/config.json

@ -1,9 +0,0 @@
{
"title": "可视化预警平台",
"jianceduixiang1path": "@/assets/img/jianceduixiang1.gif",
"jianceduixiang2path": "@/assets/img/jianceduixiang2.jpg",
"jianceduixiang3path": "@/assets/img/jianceduixiang3.png",
"jianceduixiang4path": "@/assets/img/jianceduixiang4.png",
"jianceduixiang5path": "@/assets/img/jianceduixiang5.gif",
"jianceduixiang6path": "@/assets/img/jianceduixiang6.gif"
}

3
front/src/assets/css/base.css

@ -5,3 +5,6 @@ html,body {
.el-form-item__content{
width: 250px;
}
.mx-4{
margin: 8px 8px;
}

2
front/src/component/table.vue

@ -1,5 +1,5 @@
<template>
<el-table :ref="refName" :data="tableData" class="mainTable" show-overflow-tooltip>
<el-table :ref="refName" border :data="tableData" class="mainTable" show-overflow-tooltip>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column type="index" width="66" label="order">
<template #default="scope">

89
front/src/views/student.vue

@ -1,13 +1,13 @@
<template>
<el-row :gutter="20">
<el-col :span="6">
<el-card>
<el-col :span="8">
<el-card class="mx-4">
<template #header>
<div>
<span>考生信息</span>
</div>
</template>
<p v-for="(userItem, userIndex) in userInfo" :key="userIndex">
<div v-for="(userItem, userIndex) in userInfo" :key="userIndex" class="mx-4">
<template v-if="userItem.type === 'datetimerange'">
<div>
<span>{{ userItem.label }}</span>
@ -30,7 +30,7 @@
<span v-else></span>
</div>
</template>
</p>
</div>
<template #footer>
<el-button type="primary" @click="connectTeacher">联系老师</el-button>
<el-button type="primary" @click="startExam">考前检测</el-button>
@ -39,35 +39,35 @@
</template>
</el-card>
</el-col>
<el-col :span="6">
<el-card>
<el-col :span="8">
<el-card class="mx-4">
<template #header>
<div>
<span>实时作弊信息</span>
<!-- <el-button type="primary" @click="getzuobiList">获取作弊信息</el-button> -->
</div>
</template>
<p v-for="(zuobiItem, zuobiIndex) in zuobiPList" :key="zuobiIndex">
<el-tag v-for="(value, key) in zuobiObj" :key="key" type="primary" class="mx-4">
<span>
{{ zuobiItem.label }}
{{ key }}
</span>:
<span>
{{ (zuobiObj[zuobiItem.prop] || []).length }}
{{ value.length }}
</span>
</p>
</el-tag>
</el-card>
<el-card>
<el-card class="mx-4">
<template #header>
<div>
<span>作弊饼状图</span>
<span>Cheat Pie Chart</span>
<!-- <el-button type="primary" @click="getzuobiList">获取作弊信息</el-button> -->
</div>
</template>
<div id="studentZuobiEcharts"></div>
</el-card>
</el-col>
<el-col :span="12">
<video ref="videoEL" class="canvasClass" playsinline></video>
<el-col :span="7">
<video ref="videoEL" class="canvasClass mx-4" playsinline></video>
</el-col>
</el-row>
</template>
@ -92,62 +92,62 @@ export default {
// ,
userInfo: [
{
label: "学校名称",
label: "School Name",
prop: "xuexiaomingcheng",
type: "text"
},
{
label: "学校代号",
label: "School Code",
prop: "xuexiaodaihao",
type: "text"
},
{
label: "专业名称",
label: "professional title",
prop: "zhuanyemingcheng",
type: "text"
},
{
label: "专业代号",
label: "Professional code",
prop: "zhuanyedaihao",
type: "text"
},
{
label: "年级",
label: "grade",
prop: "nianji",
type: "text"
},
{
label: "班级",
label: "class",
prop: "banji",
type: "text"
},
{
label: "学生姓名",
label: "student name",
prop: "xueshengxingming",
type: "text"
},
{
label: "学号",
label: "student ID",
prop: "xuehao",
type: "text"
},
{
label: "考生图片",
label: "Student pictures",
prop: "kaoshengtupian",
type: "img"
},
{
label: "考试类型",
label: "Exam Type",
prop: "kaoshileixing",
type: "text"
},
{
label: "考试科目",
label: "exam subjects",
prop: "kaoshikemu",
type: "text"
},
{
label: "考试时间段",
label: "Exam time period",
prop: "kaoshishijianduan",
type: "datetimerange"
},
@ -188,7 +188,7 @@ export default {
this.studentEcharts = echarts.init(echartsDom)
this.zuobiInterval = setInterval(async () => {
await this.getzuobiList()
}, 3000);
}, 1000 * 10);
},
methods: {
@ -199,7 +199,13 @@ export default {
this.userData = res.list[0]
},
async initWebSocket() {
this.socket = io('http://localhost:5000/ws/video', { query: { id: this.userId, isAdmin: "0" } }); //
this.socket = io('http://127.0.0.1:5000',
{
secure: true,
rejectUnauthorized: false, //
query: { id: this.userId, isAdmin: "0" }
}
); //
this.socket.on('connect', () => {
console.log('Connected to server');
});
@ -208,10 +214,27 @@ export default {
console.log(78875454477, data)
let msg = _.get(data, ['data'], "")
let answer = _.split(msg, "@@@")
let type = _.get(data, ['type'], "")
if (type === "answer") {
ElMessage({
message: `老师回答:${answer[1]}`,
type: "success",
});
} else {
if (type !== 'normal') {
ElMessage({
message: `请不要${type}`,
type: "error",
});
}else{
ElMessage({
message: `${type}`,
type: "success",
});
}
}
//
});
},
@ -258,12 +281,14 @@ export default {
recognition.onresult = function (event) {
let result = event.results[event.results.length - 1][0].transcript;
console.log('用户说了: ' + result);
if (result) {
ElMessage({
message: `您正在说说:${result}`,
type: "error",
});
that.socket.emit('talk', { userId: that.userId, data: result });
//
}
};
//
recognition.onerror = function (event) {
@ -351,7 +376,7 @@ export default {
{
name: '作弊次数',
type: 'pie',
radius: ['40%', '70%'],
radius: ['40%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
@ -389,12 +414,12 @@ export default {
<style scoped>
.canvasClass {
position: relative;
width: 600px;
height: 600px;
width: 400px;
height: 400px;
}
#studentZuobiEcharts {
width: 400px;
height: 400px;
height: 300px;
}
</style>

47
front/src/views/teacher.vue

@ -19,7 +19,7 @@
<el-card>
<template #header>
<div>
<span>作弊饼状图</span>
<span>Cheat Pie Chart</span>
<!-- <el-button type="primary" @click="getzuobiList">获取作弊信息</el-button> -->
</div>
</template>
@ -85,7 +85,7 @@ export default {
localComponent: [
{
prop: "xuexiaomingcheng",
label: "学校名称",
label: "School_name",
type: "text",
tableShow: false,
formShow: true
@ -157,7 +157,7 @@ export default {
prop: "chengji",
label: "成绩",
type: "text",
tableShow: true,
tableShow: false,
formShow: true
},
{
@ -246,7 +246,7 @@ export default {
this.teacherEcharts = echarts.init(echartsDom)
this.zuobiInterval = setInterval(async () => {
await this.getzuobiList()
}, 3000);
}, 1000 * 10);
// getip((ip) => { console.log(777, ip) })
}
},
@ -399,8 +399,12 @@ export default {
},
//
async initWebSocket() {
this.socket = io('http://localhost:5000/ws/video',
{ query: { id: localStorage.getItem('userId'), isAdmin: "1" } }
this.socket = io('http://127.0.0.1:5000',
{
secure: true,
rejectUnauthorized: false, //
query: { id: localStorage.getItem('userId'), isAdmin: "1" }
}
); //
this.socket.on('connect', () => {
console.log('Connected to server');
@ -428,11 +432,28 @@ export default {
this.socket.on(`teacherTalk${element._id}`, (data) => {
let userId = _.get(data, ['userId'], "")
let msg = _.get(data, ['data'], "")
let type = _.get(data, ['type'], "")
let stedentItem = _.find(this.tableData, o => o._id === userId)
if (type === "talk") {
ElMessage({
message: `${stedentItem.xueshengxingming}说:${msg}`,
type: "error",
});
} else {
if (type === "normal") {
ElMessage({
message: `${stedentItem.xueshengxingming}${type}`,
type: "success",
});
} else {
ElMessage({
message: `${stedentItem.xueshengxingming}正在:${type}`,
type: "error",
});
}
}
console.log(78744, stedentItem, msg)
//
});
@ -500,12 +521,12 @@ export default {
let zuobiList = res.list
let zuobiObj = _.groupBy(zuobiList, 'userId')
let allZuobi = {}
console.log(78444, zuobiObj)
let dataLocal = []
for (let key in zuobiObj) {
let value = zuobiObj[key]
let valueObj = _.groupBy(value, 'type')
let studentItem = _.find(this.tableData, o => o._id === key)
if (studentItem) {
allZuobi[key] = valueObj
dataLocal.push({
value: value.length,
@ -513,11 +534,11 @@ export default {
_id: key
})
}
}
let option = {
tooltip: {
trigger: 'item',
formatter: function (params) {
console.log(6887, params, allZuobi)
let msg = ""
for (let key1 in allZuobi[params.data._id]) {
let value1 = allZuobi[params.data._id][key1]
@ -534,7 +555,7 @@ export default {
{
name: '作弊次数',
type: 'pie',
radius: ['40%', '70%'],
radius: ['30%', '80%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
@ -572,14 +593,14 @@ export default {
<style scoped>
.canvasClass {
position: relative;
width: 390px;
height: 390px;
width: 270px;
height: 270px;
background-color: aliceblue
}
#teacherZuobiEcharts {
width: 400px;
height: 400px;
width: 300px;
height: 300px;
position: relative;
left: calc(50% - 200px);
}

Loading…
Cancel
Save