EXC在Note中的导入导出

本文介绍了一种使用Lotus Notes平台实现数据批量导入和导出的方法,包括CS和BS版本的不同实现方式,并探讨了利用XML格式进行数据导出以节省资源的策略。

导入:

CS版本

Dim ws As New notesuiworkspace
Dim ss As New notessession
Dim db As notesdatabase
Dim view As NotesView
Dim doc As NotesDocument
Set db = ss.CurrentDatabase

Dim i,sum_all,sum_success As Integer
Dim keyindex As String
Dim errorinfo As String

Set view=db.GetView("(viewforupdate)")

files = ws.openfiledialog(False,"请选择数据源","Excel file/*.xls","D:")

If files(0) = "" Then
	Exit Sub
End If
Set excelapplication = createobject("excel.application")
Set excelworkbook = excelapplication.workbooks.open(files)
Set excelsheet = excelworkbook.worksheets(1)

i = 2
sum_all = 0
sum_success = 0

keyindex = Lcase$(Trim(Cstr(excelsheet.cells(i,1).value)))

Do Until keyindex = ""
	
	Set doc = view.GetDocumentbykey(keyindex)
	
	If Not (doc Is Nothing) Then
		
		doc.Manager = excelsheet.cells(i,3).value
		doc.ManagerID = Lcase$(Trim(excelsheet.cells(i,2).value))
		doc.ManagerID_view = Lcase$(Trim(excelsheet.cells(i,2).value))
		doc.Manager1 = excelsheet.cells(i,5).value
		doc.ManagerID1 = Lcase$(Trim(excelsheet.cells(i,4).value))
		doc.ManagerID_view1 = Lcase$(Trim(excelsheet.cells(i,4).value))
		
		i = i+1
		sum_success = sum_success + 1
		sum_all = sum_all + 1
		Call doc.save(False,False)
	Else 
		i = i+1
		sum_all = sum_all + 1
		errorinfo = errorinfo + " -- " + keyindex
	End If
	
	keyindex = Lcase$(Trim(Cstr(excelsheet.cells(i,1).value)))
	
Loop

excelworkbook.close(False)
excelapplication.quit
Set excelapplication = Nothing

Messagebox "确认:共 "+Cstr(sum_all)+" 条记录,成功更新 "+Cstr(sum_success)+"条!"+"\r\n" + "错误的帐号为:"+errorinfo


 

 

BS版本的

	Dim ss As New NotesSession
	
	Dim db As NotesDatabase
	
	Dim dcctemp As NotesDocumentCollection
	
	Dim doc As NotesDocument
	Dim docTemp As NotesDocument 
	
	Dim vw As NotesView
	
	Set db = ss.CurrentDatabase
	Set doc = ss.DocumentContext	
	
	Set vw = db.GetView("Search_StudentByClassStage_View")
	If  vw Is Nothing Then
		Exit Sub
	End If
	
	'获得本期第一个学员信息(通过有多少门课,确定应该多少列)
	Set dccTemp = vw.GetAllDocumentsByKey(doc.BNType(0) + "#" + doc.ClassStage(0),True)
	Set docTemp = dccTemp.GetFirstDocument
	If docTemp Is Nothing Then
		Print |<script>alert("无对应日期的数据")</script>|
		Print |<script>history.back()</script>|
		Exit Sub
	End If
	
	'初始化Excel组件
	Print |<script lanuage="javascript">|
	Print |var varExcelApplication = new ActiveXObject("Excel.Application");|
	Print |var varWorkbook = varExcelApplication.Workbooks.Add();|
	Print |var varWorkSheet = varWorkbook.Worksheets(1)|
	Print |varWorkSheet.Name = "sheet1"|
	Print |varWorkSheet.Rows("1:1").Select()|
	Print |var varSelection = varExcelApplication.Selection|
	Print |varSelection.Font.Name = "宋体"|
	Print |varSelection.Font.Size = 10|
	Print |varSelection.Font.Bold = true|
	Print |varSelection.HorizontalAlignment = -4108|
	Print |varWorkSheet.Columns("A:Z").Select|
	Print |var varSelection = varExcelApplication.Selection|
	Print |varSelection.NumberFormatLocal = "@"|		
	
	'打印表头start-----------------
	strColName = "序号;工号"
	For i = 1 To 30
		strNum = Cstr(i)
		If docTemp.HasItem("Course_"+strNum) Then
			strColName = strColName + ";课程" + strNum + ";分数" + strNum + ";评语" + strNum
		End If
	Next
	
	varColName = Split(strColName,";")	
	For i = 0 To Ubound(varColName)
		Print |varWorkSheet.Range("| + Chr(65 + i) + |1").FormulaR1C1 = "| + varColName(i) + |"|
	Next	
	'打印表头end-------------------------------------------------------------------------------------
	
	'根据文档中Couse的具体情况导出各个列:课程名称##分数##评语
	intRow = 2
	For i = 1 To dccTemp.Count
		Set docTemp = dccTemp.GetNthDocument(i)
		If Not docTemp Is Nothing Then
			Print |varWorkSheet.Range("| + Chr(65 + 0) + Cstr(intRow) + |").FormulaR1C1 = "| + Cstr(i) + |"|		'序号
			Print |varWorkSheet.Range("| + Chr(65 + 1) + Cstr(intRow) + |").FormulaR1C1 = "| + docTemp.ID(0) + |"|	'工号
			
			'开始插入的列数
			intCol = 2
			
			'最多取30门课程
			For m = 1 To 30
				If docTemp.HasItem("Course_"+Cstr(m)) Then
					strCouse = docTemp.GetFirstItem("Course_"+Cstr(m)).values(0)
					strCouse = Replace(strCouse,"####","## ##")
					varCouseList = Split(strCouse,"##")
					Print |varWorkSheet.Range("| + Chr(65 + intCol + 0) + Cstr(intRow) + |").FormulaR1C1 = "| + replaceSpecailChr(varCouseList(0)) + |"|
					Print |varWorkSheet.Range("| + Chr(65 + intCol + 1) + Cstr(intRow) + |").FormulaR1C1 = "| + replaceSpecailChr(varCouseList(1)) + |"|
					Print |varWorkSheet.Range("| + Chr(65 + intCol + 2) + Cstr(intRow) + |").FormulaR1C1 = "| + replaceSpecailChr(varCouseList(2)) + |"|
				End If
				intCol = intCol + 3		'下一个课程信息插入的列数
			Next
			
			intRow = intRow + 1
			Set docTemp = dccTemp.GetNextDocument(docTemp)
		End If
	Next
	
	
	
	Print |varExcelApplication.Visible = true|
	Print |history.back()|
	Print |</script>|


 

导出:

CS版本

 

	Dim ss As New NotesSession
	Dim db As NotesDatabase
	Dim vw As NotesView
	Dim dcc As NotesDocumentCollection
	Dim doc As NotesDocument
	
	Dim excelApplication As Variant
	Dim excelWorkbook As Variant
	Dim excelSheet As Variant
	
	Dim i As Integer,j As Integer
	
	Dim strID As String
	Dim varIDList As Variant
	
	Set db = ss.Currentdatabase
	
	Set vw = db.Getview("Search_PuzzleByPaperIDPersonID_View")
	If vw Is Nothing Then
		Exit sub
	End If
	
	'strID = "39129;33138;29288;41954;37319;27628;27342;21394;11008;39559;22215;22608;39562;21813;46628;21808;44371;30499;11007;21913;20692;8029;29189;44368;41956;39102;20374"
	strID = "1048"
	varIDList = Split(strID,";")
	
	Set excelApplication = CreateObject("Excel.Application")
	excelApplication.Visible = True
	Set excelWorkbook = excelApplication.Workbooks.Add
	Set excelSheet = excelWorkbook.Worksheets("Sheet1")
	
	'根据选中要导出的字段来设置excle的第一列
	excelSheet.Cells(1,1).Value = "工号"
	excelSheet.Cells(1,2).Value = "姓名"
	excelSheet.Cells(1,3).Value = "部门"
	excelSheet.Cells(1,4).Value = "分数"
	excelSheet.Cells(1,5).Value = "开始时间"
	excelSheet.Cells(1,6).Value = "结束时间"
	excelSheet.Cells(1,7).Value = "历次保存时间"
	
	i = 2

	For j = 0 To UBound(varIDList)
		Set doc = vw.Getdocumentbykey("Paper20110609133336DEVM-8HN8JJ"+"#*#"+varIDList(j),true)
		If Not doc Is Nothing then
			excelSheet.Cells(i,1).Value = doc.ID(0)
			excelSheet.Cells(i,2).Value = doc.CName(0)
			excelSheet.Cells(i,3).Value = doc.Depinfo(0)
			excelSheet.Cells(i,4).Value = doc.PuzzleMark(0)
			excelSheet.Cells(i,5).Value = CStr(doc.PuzzleStartDateTime(0))
			excelSheet.Cells(i,6).Value = CStr(doc.PuzzleStartDateTime(0))
			excelSheet.Cells(i,7).Value = Join(doc.Getitemvalue("$Revisions"),Chr(10))
				
			i = i+1
		End if
	Next	


	'excelApplication.Quit
	Set excelApplication = Nothing


 

BS版本

	Dim ss As New NotesSession
	
	Dim db As NotesDatabase
	
	Dim dcctemp As NotesDocumentCollection
	
	Dim doc As NotesDocument
	Dim docTemp As NotesDocument 
	
	Dim vw As NotesView
	
	Set db = ss.CurrentDatabase
	Set doc = ss.DocumentContext	
	
	Set vw = db.GetView("Search_StudentByClassStage_View")
	If  vw Is Nothing Then
		Exit Sub
	End If
	
	'获得本期第一个学员信息(通过有多少门课,确定应该多少列)
	Set dccTemp = vw.GetAllDocumentsByKey(doc.BNType(0) + "#" + doc.ClassStage(0),True)
	Set docTemp = dccTemp.GetFirstDocument
	If docTemp Is Nothing Then
		Print |<script>alert("无对应日期的数据")</script>|
		Print |<script>history.back()</script>|
		Exit Sub
	End If
	
	'初始化Excel组件
	Print |<script lanuage="javascript">|
	Print |var varExcelApplication = new ActiveXObject("Excel.Application");|
	Print |var varWorkbook = varExcelApplication.Workbooks.Add();|
	Print |var varWorkSheet = varWorkbook.Worksheets(1)|
	Print |varWorkSheet.Name = "sheet1"|
	Print |varWorkSheet.Rows("1:1").Select()|
	Print |var varSelection = varExcelApplication.Selection|
	Print |varSelection.Font.Name = "宋体"|
	Print |varSelection.Font.Size = 10|
	Print |varSelection.Font.Bold = true|
	Print |varSelection.HorizontalAlignment = -4108|
	Print |varWorkSheet.Columns("A:Z").Select|
	Print |var varSelection = varExcelApplication.Selection|
	Print |varSelection.NumberFormatLocal = "@"|		
	
	'打印表头start-----------------
	strColName = "序号;工号"
	For i = 1 To 30
		strNum = Cstr(i)
		If docTemp.HasItem("Course_"+strNum) Then
			strColName = strColName + ";课程" + strNum + ";分数" + strNum + ";评语" + strNum
		End If
	Next
	
	varColName = Split(strColName,";")	
	For i = 0 To Ubound(varColName)
		Print |varWorkSheet.Range("| + Chr(65 + i) + |1").FormulaR1C1 = "| + varColName(i) + |"|
	Next	
	'打印表头end-------------------------------------------------------------------------------------
	
	'根据文档中Couse的具体情况导出各个列:课程名称##分数##评语
	intRow = 2
	For i = 1 To dccTemp.Count
		Set docTemp = dccTemp.GetNthDocument(i)
		If Not docTemp Is Nothing Then
			Print |varWorkSheet.Range("| + Chr(65 + 0) + Cstr(intRow) + |").FormulaR1C1 = "| + Cstr(i) + |"|		'序号
			Print |varWorkSheet.Range("| + Chr(65 + 1) + Cstr(intRow) + |").FormulaR1C1 = "| + docTemp.ID(0) + |"|	'工号
			
			'开始插入的列数
			intCol = 2
			
			'最多取30门课程
			For m = 1 To 30
				If docTemp.HasItem("Course_"+Cstr(m)) Then
					strCouse = docTemp.GetFirstItem("Course_"+Cstr(m)).values(0)
					strCouse = Replace(strCouse,"####","## ##")
					varCouseList = Split(strCouse,"##")
					Print |varWorkSheet.Range("| + Chr(65 + intCol + 0) + Cstr(intRow) + |").FormulaR1C1 = "| + replaceSpecailChr(varCouseList(0)) + |"|
					Print |varWorkSheet.Range("| + Chr(65 + intCol + 1) + Cstr(intRow) + |").FormulaR1C1 = "| + replaceSpecailChr(varCouseList(1)) + |"|
					Print |varWorkSheet.Range("| + Chr(65 + intCol + 2) + Cstr(intRow) + |").FormulaR1C1 = "| + replaceSpecailChr(varCouseList(2)) + |"|
				End If
				intCol = intCol + 3		'下一个课程信息插入的列数
			Next
			
			intRow = intRow + 1
			Set docTemp = dccTemp.GetNextDocument(docTemp)
		End If
	Next
	
	
	
	Print |varExcelApplication.Visible = true|
	Print |history.back()|
	Print |</script>|



XML导入方式,更加节省资源

后台

Function GetListXML(vw As NotesView,strSelect As String) As Variant
%REM
	功能:获得每一列的值
	创建:ycy  2011-08-31
%END REM
	On Error Goto ErrorHandle

	Dim docTemp As NotesDocument 

	Dim intRow As Long
	Dim strTitle As String
	Dim varTitle As Variant
	Dim i As Long
	Dim intNum As Long
	Dim strXML As String
	
	'执行查询
	intNum = vw.Ftsearch(strSelect,5000)
	If intNum >= 5000 Then
		strXML = |<excel><result>failue</result><memo>您导出结果数目超过5000,请缩小搜索范围后进行导出</memo></excel>|
		GoTo ResultHandle
	ElseIf intNum = 0 Then
		strXML = |<excel><result>failue</result><memo>没有找到相关文档</memo></excel>|
		GoTo ResultHandle
	End If	
	
	'列名
	strTitle = "排序列;姓名;。。。"
	varTitle = Split(strTitle,";")
	
	'开始组装查询结果XML
	intRow = 1
	strXML = strXML + |<excel>|
	strXML = strXML + |	<result>sucess</result>|
	strXML = strXML + |	<memo></memo>|

	'---列名称
	strXML = strXML + |	<title>|
	For i = 0 To UBound(varTitle)
		strXML = strXML + |	<col>| + varTitle(i) + |</col>|
	Next
	strXML = strXML + |	</title>|
	
	'---正文内容
	Set docTemp = vw.Getfirstdocument()
	While Not docTemp Is Nothing 
		strXML = strXML + |<row>|
		
		strXML = strXML + |		<no><![CDATA[| +  docTemp.ID(0) + |]]></no>|
		strXML = strXML + |		<col><![CDATA[| + 。。。 + |]]></col>|	


		strXML = strXML + |</row>|
		
		intRow = intRow + 1
		Set docTemp = vw.Getnextdocument(docTemp)
	Wend

	strXML = strXML + |</excel>|
	
	
ResultHandle:		
	GetListXML = strXML
	Exit Function
ErrorHandle:
	Msgbox "导出-外包信息|agentOutputPerson的GetList错误在行:" + Cstr(Erl) + "   错误是:" + Cstr(Error)
End Function

前台

//Ajax获得XML内容并写入Excel
//ycy 2011-11-01
var strURL = document.location.href
var intPos = strURL.indexOf(".nsf")
strURL = strURL.substring(0,intPos+4)
strURL = strURL + "/agentOutputITSoft?Openagent";

//加载特效
$("#spanLoading").html("<img src='loading.gif'  style='margin-top:5px;'/>正在获取数据请稍后...")

var varData = {
	CUserID:$("#CUserID").val(),
	RndNum:Math.random()
}

$.ajax({   	
	url: strURL,   
	dataType: 'xml',   
	type: 'GET',   
	data:varData,
	timeout: 60000,   
	error: function(xml){   
		openPrompt("加载失败,请联系管理员");   
		//加载特效
		$("#spanLoading").html("加载失败,请联系管理员")
	},   
	success: function(xml){ 
		var objXML = $(xml)
		//如果获取Excel失败
		if(objXML.find("result").text()=="failue"){
			openPrompt(objXML.find("memo").text())
			$("#spanLoading").html(objXML.find("memo").text())
			return;
		}
		
		//获取成功则加载application
		try{                                                                 
		  var varExcelApplication = new ActiveXObject("Excel.Application");
		}catch(e){                                                           
			alert("请确认本机器已经安装Excel或浏览器安全设置: ActiveX相关项目设置为启用")
			window.open("/sharedb.nsf/excelfaq?OpenPage","_blank")           	
			history.back()                                                   
		}								                               
		var varWorkbook = varExcelApplication.Workbooks.Add();
		var varWorkSheet = varWorkbook.Worksheets(1)
		varWorkSheet.Name = "sheet1"
		varWorkSheet.Rows("1:1").Select()
		var varSelection = varExcelApplication.Selection
		varSelection.Font.Name = "宋体"
		varSelection.Font.Size = 10
		varSelection.Font.Bold = true
		varSelection.HorizontalAlignment = -4108
		
		varWorkSheet.Columns("A:ZZ").Select
		var varSelection = varExcelApplication.Selection
		varSelection.NumberFormatLocal = "@"		
		varExcelApplication.Visible = true
		
		//获得标题
		objXML.find("title col").each(function(i){
			var objTitle = $(this)
			varWorkSheet.Range(String.fromCharCode(65+i) + "1").FormulaR1C1 = objTitle.text()
		})
		
		//获得内容
		objXML.find("row").each(function(i){
			 $(this).find("col").each(function(j){
				var objCol = $(this)
				varWorkSheet.Range(String.fromCharCode(65+j) + (i+2)).FormulaR1C1 = objCol.text()
			})
			
		})

		//对指定列排序[第一列]
		//var xlAscending = 1;
		//var xlYes = 1; 
		//var xlSortRows=1;
		//var xlPinYin= 1;
		//var xlSortNormal =1;
		
		//varWorkSheet.Range("A:Z").Sort(varWorkSheet.Columns("A"),xlAscending,null,null,null,null,null,xlYes,null,null,xlSortRows,xlPinYin,xlSortNormal,null,null);
		

		//设置为10号字体	
		varWorkSheet.Columns("A:ZZ").EntireColumn.AutoFit()	
		varSelection.Font.Size = 10
		
		//加载特效
		$("#spanLoading").html("导出完毕")

	}   
}); 




修改后端'实验名称(备注信息)': f"{video_name}/{area_name}"中的area_name,使用originalName,使得导出excel表格中显示的是对应关联ROI的名称,而不是roi,如何修改,优化代码,代码如下:import os import cv2 import base64 import json import numpy as np import pandas as pd from PIL import Image, ImageDraw, ImageFont from collections import defaultdict from channels.generic.websocket import AsyncWebsocketConsumer from django.conf import settings from ultralytics import YOLO import time import asyncio import logging from index.page_4_1_auto_set import image_detect logger = logging.getLogger(__name__) class VideoAnalyzer: def __init__(self, scale_info, movement_threshold=0.5): """ 初始化视频分析器 :param scale_info: 比例尺信息字典 :param movement_threshold: 移动状态判断阈值(厘米/秒) """ # 计算像素到厘米的转换因子 if 'pixels_per_cm' in scale_info: self.pixels_per_cm = scale_info['pixels_per_cm'] else: self.pixels_per_cm = scale_info['pixel_length'] / scale_info['real_length'] self.movement_threshold = movement_threshold # 存储中间结果 self.frame_data = [] # 存储每帧的检测结果 self.trajectories = defaultdict(list) # {roi_key: [center_points]} self.results = {} # 最终计算结果 self.movement_state_data = defaultdict(list) # 存储移动状态数据 self.acceleration_state_data = defaultdict(list) # 存储加速度状态数据 self.in_analysis_zone = defaultdict(bool) # 跟踪是否在分析区内 self.roi_area_mapping = {} # 存储ROI到分析区的映射 def set_roi_area_mapping(self, roi_area_mapping): """设置ROI到分析区的映射""" self.roi_area_mapping = roi_area_mapping def process_frame(self, frame_index, timestamp, centers_dict, in_analysis_zone_dict): """ 处理每帧的检测结果 :param frame_index: 帧索引 :param timestamp: 当前帧的时间戳(秒) :param centers_dict: 每个ROI的中心点坐标 {roi_key: (x, y)} :param in_analysis_zone_dict: 每个ROI是否在分析区内 {roi_key: bool} """ # 存储当前帧的检测结果 self.frame_data.append({ 'frame_index': frame_index, 'timestamp': timestamp, 'centers': centers_dict, 'in_analysis_zone': in_analysis_zone_dict }) # 更新每个ROI的轨迹 for roi_key, center in centers_dict.items(): self.trajectories[roi_key].append(( timestamp, center, in_analysis_zone_dict.get(roi_key, False) )) # 更新当前是否在分析区内的状态 self.in_analysis_zone[roi_key] = in_analysis_zone_dict.get(roi_key, False) def calculate_movement_metrics(self): """计算运动指标""" for roi_key, trajectory in self.trajectories.items(): if len(trajectory) < 2: # 轨迹点不足,无法计算 self.results[roi_key] = { 'total_distance_cm': 0, 'frame_distances': [], 'analysis_zone_distances': [], 'average_speed_cm_s': 0, 'max_speed_cm_s': 0, 'min_speed_cm_s': 0, 'distances': [], 'speeds': [], 'accelerations': [], 'movement_state_data': [], 'acceleration_data': [], 'acceleration_states': [], 'movement_episodes': [], # 新增:存储移动事件 'area_id': self.roi_area_mapping.get(roi_key, 'unknown') # 添加区域ID } continue # 初始化指标 frame_distances = [] # 每帧移动距离 analysis_zone_distances = [] # 分析区内移动距离 speeds = [] # 每帧速度 accelerations = [] # 每帧加速度 movement_states = [] # 每帧移动状态 acceleration_states = [] # 每帧加速度状态 total_distance = 0 # 总移动距离 total_analysis_zone_distance = 0 # 分析区内总移动距离 movement_episodes = [] # 存储移动事件 # 计算每帧的距离和速度 for i in range(1, len(trajectory)): prev_time, prev_center, prev_in_zone = trajectory[i - 1] curr_time, curr_center, curr_in_zone = trajectory[i] # 检查中心点是否为None(目标未检测到) if prev_center is None or curr_center is None: # 如果任一中心点为None,则距离和速度设为0 cm_distance = 0 speed = 0 movement_state = '静止' else: # 计算像素距离 pixel_distance = self._euclidean_distance(prev_center, curr_center) # 转换为厘米 cm_distance = pixel_distance / self.pixels_per_cm time_diff = curr_time - prev_time # 计算速度 (厘米/秒) speed = cm_distance / time_diff if time_diff > 0 else 0 movement_state = '移动' if speed > self.movement_threshold else '静止' frame_distances.append(cm_distance) total_distance += cm_distance # 如果在分析区内,记录分析区移动距离 if prev_in_zone and curr_in_zone: analysis_zone_distances.append(cm_distance) total_analysis_zone_distance += cm_distance else: analysis_zone_distances.append(0) speeds.append(speed) # 判断移动状态 movement_states.append({ 'timestamp': curr_time, 'state': movement_state }) # 计算移动事件(连续的移动状态) current_episode = None for i, state_data in enumerate(movement_states): if state_data['state'] == '移动': if current_episode is None: # 开始新的移动事件 current_episode = { 'start_time': state_data['timestamp'], 'end_time': state_data['timestamp'], 'duration': 0 } else: # 更新移动事件结束时间 current_episode['end_time'] = state_data['timestamp'] else: if current_episode is not None: # 结束当前移动事件 current_episode['duration'] = current_episode['end_time'] - current_episode['start_time'] movement_episodes.append(current_episode) current_episode = None # 处理最后一个移动事件 if current_episode is not None: current_episode['duration'] = current_episode['end_time'] - current_episode['start_time'] movement_episodes.append(current_episode) # 计算加速度和加速度状态 avg_acceleration = 0 max_acceleration = 0 min_acceleration = 0 if len(speeds) >= 2: for i in range(1, len(speeds)): time_diff = trajectory[i + 1][0] - trajectory[i][0] acceleration = (speeds[i] - speeds[i - 1]) / time_diff if time_diff > 0 else 0 accelerations.append(acceleration) # 计算加速度统计 if accelerations: avg_acceleration = np.mean(accelerations) max_acceleration = np.max(accelerations) min_acceleration = np.min(accelerations) # 判断加速度状态 for acc in accelerations: acceleration_state = '高加速度' if acc > avg_acceleration else '低加速度' acceleration_states.append(acceleration_state) # 存储结果 self.results[roi_key] = { 'total_distance_cm': total_distance, 'total_analysis_zone_distance_cm': total_analysis_zone_distance, 'frame_distances': frame_distances, 'analysis_zone_distances': analysis_zone_distances, 'average_speed_cm_s': np.mean(speeds) if speeds else 0, 'max_speed_cm_s': np.max(speeds) if speeds else 0, 'min_speed_cm_s': np.min(speeds) if speeds else 0, 'speeds': speeds, 'accelerations': accelerations, 'movement_state_data': movement_states, 'acceleration_data': [{'timestamp': trajectory[i + 1][0], 'acceleration': acc} for i, acc in enumerate(accelerations)], 'acceleration_states': acceleration_states, 'avg_acceleration': avg_acceleration, 'max_acceleration': max_acceleration, 'min_acceleration': min_acceleration, 'movement_episodes': movement_episodes, # 新增:移动事件 'area_id': self.roi_area_mapping.get(roi_key, 'unknown') # 添加区域ID } def generate_excel_report(self, video_name, experiment_info=None): """生成Excel报告,格式与统计数据值.xlsx一致""" # 准备数据 data = [] # 获取实验信息和时间范围 exp_name = experiment_info.get('name', '') if experiment_info else '' exp_note = experiment_info.get('note', '') if experiment_info else '' duration = experiment_info.get('duration', 0) if experiment_info else 0 # 格式化时间范围 def format_duration(seconds): hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = seconds % 60 return f"{hours:02d}:{minutes:02d}:{secs:06.3f}" end_time = format_duration(duration) time_range = f"00:00:00.000-{end_time}" for roi_key, metrics in self.results.items(): # 获取区域名称 area_id = metrics.get('area_id', 'unknown') area_name = f"区域{area_id.split('-')[-1]}" if area_id != 'unknown' else '未知区域' # 1. 移动距离计算 frame_distances = metrics['frame_distances'] n_distance = len(frame_distances) distance_mean = np.mean(frame_distances) if n_distance > 0 else 0 distance_se = np.std(frame_distances) / np.sqrt(n_distance) if n_distance > 1 else 0 # 2. 分析区移动距离 analysis_zone_distances = metrics['analysis_zone_distances'] n_analysis_zone = len(analysis_zone_distances) analysis_zone_mean = np.mean(analysis_zone_distances) if n_analysis_zone > 0 else 0 analysis_zone_se = np.std(analysis_zone_distances) / np.sqrt(n_analysis_zone) if n_analysis_zone > 1 else 0 # 3. 速度计算 speeds = metrics['speeds'] n_speed = len(speeds) speed_mean = np.mean(speeds) if n_speed > 0 else 0 speed_se = np.std(speeds) / np.sqrt(n_speed) if n_speed > 1 else 0 # 4. 移动状态计算(关键修复) movement_states = metrics['movement_state_data'] movement_durations = [] current_duration = 0 last_timestamp = movement_states[0]['timestamp'] if movement_states else 0 for i in range(1, len(movement_states)): current = movement_states[i] prev = movement_states[i - 1] time_diff = current['timestamp'] - prev['timestamp'] if current['state'] == '移动': current_duration += time_diff elif current_duration > 0: movement_durations.append(current_duration) current_duration = 0 if current_duration > 0: movement_durations.append(current_duration) movement_episodes = metrics['movement_episodes'] n_movement_state = len(movement_episodes) movement_durations = [episode['duration'] for episode in movement_episodes] movement_duration_mean = np.mean(movement_durations) if n_movement_state > 0 else 0 movement_duration_se = np.std(movement_durations) / np.sqrt(n_movement_state) if n_movement_state > 1 else 0 total_movement_duration = sum(movement_durations) # 5. 加速度计算 accelerations = metrics['accelerations'] n_acc = len(accelerations) avg_acc = metrics['avg_acceleration'] acc_se = np.std(accelerations) / np.sqrt(n_acc) if n_acc > 1 else 0 # 加速度极值 max_acc = np.max(accelerations) if accelerations else 0 min_acc = np.min(accelerations) if accelerations else 0 # 关键修复:确保数据字典有23个元素 data.append({ '实验名称(备注信息)': f"{video_name}/{area_name}", '移动距离_N': n_distance, '移动距离_平均值': round(distance_mean, 3), '移动距离_总距离': round(metrics['total_distance_cm'], 3), '移动距离_标准误差': round(distance_se, 3), '分析区移动距离_N': n_analysis_zone, '分析区移动距离_平均值': round(analysis_zone_mean, 3), '分析区移动距离_累计移动距离': round(metrics['total_analysis_zone_distance_cm'], 3), '分析区移动距离_标准误差': round(analysis_zone_se, 3), '速度_N': n_speed, '速度_平均值': round(speed_mean, 3), '速度_标准误差': round(speed_se, 3), '移动状态_N': n_movement_state, '移动状态_平均值': round(movement_duration_mean, 3), '移动状态_累计持续时间': round(total_movement_duration, 3), '移动状态_标准误差': round(movement_duration_se, 3), '加速度_最大值_N': n_acc, '加速度_最大值_平均值': round(max_acc, 3), '加速度最大值_标准误差': 0, # 单个值无标准误差 '加速度_最小值_N': n_acc, '加速度_最小值_平均值': round(min_acc, 3), '加速度最小值_标准误差': 0 # 单个值无标准误差 }) # 创建DataFrame df = pd.DataFrame(data) # 保存到文件 report_dir = os.path.join(settings.MEDIA_ROOT, 'reports') os.makedirs(report_dir, exist_ok=True) report_path = os.path.join(report_dir, f"{video_name}_analysis.xlsx") # 创建Excel写入器 with pd.ExcelWriter(report_path, engine='openpyxl') as writer: # 写入数据(现在有23列) df.to_excel(writer, sheet_name='检测结果', index=False) # 获取工作簿和工作表对象 workbook = writer.book worksheet = writer.sheets['检测结果'] # 定义不同区域的背景色 colors = { '移动距离': 'FFE6B3', # 浅橙色 '分析区移动距离': 'B3E6FF', # 浅蓝色 '速度': 'B3FFB3', # 浅绿色 '移动状态': 'FFB3E6', # 浅粉色 '加速度': 'E6B3FF' # 浅紫色 } # 导入样式类 from openpyxl.styles import PatternFill, Alignment, Font, Border, Side # 设置居中对齐 center_alignment = Alignment(horizontal='center', vertical='center') # 设置边框样式 thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) # 设置列宽 for col in worksheet.columns: column_letter = col[0].column_letter worksheet.column_dimensions[column_letter].width = 11.75 # 添加多级表头 # 第一行标题 worksheet.merge_cells('A1:V1') title_cell = worksheet['A1'] title_cell.value = f"{exp_name} ({exp_note}) {time_range}" if exp_note else f"{exp_name} {time_range}" title_cell.alignment = center_alignment title_cell.font = Font(bold=True, size=14) title_cell.border = thin_border # 第二行分类 - 设置居中并添加背景色 worksheet.merge_cells('A2:A6') worksheet['A2'] = '指标' worksheet['A2'].alignment = center_alignment worksheet['A2'].border = thin_border # 移动距离区域 worksheet.merge_cells('B2:E2') move_dist_cell = worksheet['B2'] move_dist_cell.value = '移动距离' move_dist_cell.alignment = center_alignment move_dist_cell.fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') move_dist_cell.font = Font(bold=True) move_dist_cell.border = thin_border # 分析区移动距离区域 worksheet.merge_cells('F2:I2') area_dist_cell = worksheet['F2'] area_dist_cell.value = '分析区移动距离' area_dist_cell.alignment = center_alignment area_dist_cell.fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') area_dist_cell.font = Font(bold=True) area_dist_cell.border = thin_border # 速度区域 worksheet.merge_cells('J2:L2') speed_cell = worksheet['J2'] speed_cell.value = '速度' speed_cell.alignment = center_alignment speed_cell.fill = PatternFill(start_color=colors['速度'], end_color=colors['速度'], fill_type='solid') speed_cell.font = Font(bold=True) speed_cell.border = thin_border # 移动状态区域 worksheet.merge_cells('M2:P2') move_state_cell = worksheet['M2'] move_state_cell.value = '移动状态' move_state_cell.alignment = center_alignment move_state_cell.fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') move_state_cell.font = Font(bold=True) move_state_cell.border = thin_border # 加速度区域 worksheet.merge_cells('Q2:V2') acc_cell = worksheet['Q2'] acc_cell.value = '加速度' acc_cell.alignment = center_alignment acc_cell.fill = PatternFill(start_color=colors['加速度'], end_color=colors['加速度'], fill_type='solid') acc_cell.font = Font(bold=True) acc_cell.border = thin_border # 第三行指标 - 设置背景色和边框 for col in range(2, 23): # B到V列 cell = worksheet.cell(row=3, column=col) cell.border = thin_border cell.alignment = center_alignment # 根据列范围设置背景色 if 2 <= col <= 5: # B-E列:移动距离 cell.fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') elif 6 <= col <= 9: # F-I列:分析区移动距离 cell.fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') elif 10 <= col <= 12: # J-L列:速度 cell.fill = PatternFill(start_color=colors['速度'], end_color=colors['速度'], fill_type='solid') elif 13 <= col <= 16: # M-P列:移动状态 cell.fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') elif 17 <= col <= 22: # Q-V列:加速度 cell.fill = PatternFill(start_color=colors['加速度'], end_color=colors['加速度'], fill_type='solid') # 设置第三行具体内容 worksheet['B3'] = '' worksheet.merge_cells('C3:D3') worksheet['C3'] = '中心点' worksheet['C3'].alignment = center_alignment worksheet['C3'].fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') worksheet['E3'] = '' worksheet['F3'] = '' worksheet.merge_cells('G3:H3') worksheet['G3'] = '中心点/1' worksheet['G3'].alignment = center_alignment worksheet['G3'].fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') worksheet['I3'] = '' worksheet['J3'] = '' worksheet['K3'] = '中心点' worksheet['L3'] = '' worksheet['M3'] = '' worksheet.merge_cells('N3:O3') worksheet['N3'] = '中心点/移动' worksheet['N3'].alignment = center_alignment worksheet['N3'].fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') worksheet['P3'] = '' worksheet['Q3'] = '' worksheet['R3'] = '中心点' worksheet['S3'] = '' worksheet['T3'] = '' worksheet['U3'] = '中心点' worksheet['V3'] = '' # 第四行具体指标 - 设置背景色和边框 for col in range(2, 23): # B到V列 cell = worksheet.cell(row=4, column=col) cell.border = thin_border cell.alignment = center_alignment # 根据列范围设置背景色 if 2 <= col <= 5: # B-E列:移动距离 cell.fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') elif 6 <= col <= 9: # F-I列:分析区移动距离 cell.fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') elif 10 <= col <= 12: # J-L列:速度 cell.fill = PatternFill(start_color=colors['速度'], end_color=colors['速度'], fill_type='solid') elif 13 <= col <= 16: # M-P列:移动状态 cell.fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') elif 17 <= col <= 22: # Q-V列:加速度 cell.fill = PatternFill(start_color=colors['加速度'], end_color=colors['加速度'], fill_type='solid') # 设置第四行具体内容 worksheet['B4'] = '' worksheet['C4'] = '' worksheet['D4'] = '' worksheet['E4'] = '' worksheet['F4'] = '' worksheet['G4'] = '' worksheet['H4'] = '' worksheet['I4'] = '' worksheet['J4'] = '' worksheet['K4'] = '' worksheet['L4'] = '' worksheet['M4'] = '' worksheet['N4'] = '' worksheet['O4'] = '' worksheet['P4'] = '' worksheet['Q4'] = '' worksheet['R4'] = '最大值' worksheet['S4'] = '' worksheet['T4'] = '' worksheet['U4'] = '最小值' worksheet['V4'] = '' # 第五行具体指标 - 设置背景色和边框 for col in range(2, 23): # B到V列 cell = worksheet.cell(row=5, column=col) cell.border = thin_border cell.alignment = center_alignment # 根据列范围设置背景色 if 2 <= col <= 5: # B-E列:移动距离 cell.fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') elif 6 <= col <= 9: # F-I列:分析区移动距离 cell.fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') elif 10 <= col <= 12: # J-L列:速度 cell.fill = PatternFill(start_color=colors['速度'], end_color=colors['速度'], fill_type='solid') elif 13 <= col <= 16: # M-P列:移动状态 cell.fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') elif 17 <= col <= 22: # Q-V列:加速度 cell.fill = PatternFill(start_color=colors['加速度'], end_color=colors['加速度'], fill_type='solid') # 设置第五行具体内容 worksheet['B5'] = 'N' worksheet['C5'] = '平均值' worksheet['D5'] = '总距离' worksheet['E5'] = '标准误差' worksheet['F5'] = 'N' worksheet['G5'] = '平均值' worksheet['H5'] = '累计移动距离' worksheet['I5'] = '标准误差' worksheet['J5'] = 'N' worksheet['K5'] = '平均值' worksheet['L5'] = '标准误差' worksheet['M5'] = 'N' worksheet['N5'] = '平均值' worksheet['O5'] = '累计持续时间' worksheet['P5'] = '标准误差' worksheet['Q5'] = 'N' worksheet['R5'] = '平均值' worksheet['S5'] = '标准误差' worksheet['T5'] = 'N' worksheet['U5'] = '平均值' worksheet['V5'] = '标准误差' # 第六行单位 - 设置背景色和边框,并确保文字居中 for col in range(2, 23): # B到V列 cell = worksheet.cell(row=6, column=col) cell.border = thin_border cell.alignment = center_alignment # 根据列范围设置背景色 if 2 <= col <= 5: # B-E列:移动距离 cell.fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') elif 6 <= col <= 9: # F-I列:分析区移动距离 cell.fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') elif 10 <= col <= 12: # J-L列:速度 cell.fill = PatternFill(start_color=colors['速度'], end_color=colors['速度'], fill_type='solid') elif 13 <= col <= 16: # M-P列:移动状态 cell.fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') elif 17 <= col <= 22: # Q-V列:加速度 cell.fill = PatternFill(start_color=colors['加速度'], end_color=colors['加速度'], fill_type='solid') # 设置第六行具体内容 worksheet['B6'] = '' worksheet['C6'] = '厘米' worksheet['D6'] = '厘米' worksheet['E6'] = '厘米' worksheet['F6'] = '' worksheet['G6'] = '厘米' worksheet['H6'] = '厘米' worksheet['I6'] = '厘米' worksheet['J6'] = '' worksheet['K6'] = '厘米/秒' worksheet['L6'] = '厘米/秒' worksheet['M6'] = '' worksheet['N6'] = '秒' worksheet['O6'] = '秒' worksheet['P6'] = '秒' worksheet['Q6'] = '' worksheet['R6'] = '厘米/秒²' worksheet['S6'] = '厘米/秒²' worksheet['T6'] = '' worksheet['U6'] = '厘米/秒²' worksheet['V6'] = '厘米/秒²' # 从第七行开始写入数据并设置样式 for row_idx, row_data in enumerate(df.values, 7): for col_idx, value in enumerate(row_data[:22], 1): # 只处理A-V列 cell = worksheet.cell(row=row_idx, column=col_idx, value=value) cell.border = thin_border cell.alignment = center_alignment # 为数据行设置背景色 if col_idx >= 2: # 从B列开始 if 2 <= col_idx <= 5: # B-E列:移动距离 cell.fill = PatternFill(start_color=colors['移动距离'], end_color=colors['移动距离'], fill_type='solid') elif 6 <= col_idx <= 9: # F-I列:分析区移动距离 cell.fill = PatternFill(start_color=colors['分析区移动距离'], end_color=colors['分析区移动距离'], fill_type='solid') elif 10 <= col_idx <= 12: # J-L列:速度 cell.fill = PatternFill(start_color=colors['速度'], end_color=colors['速度'], fill_type='solid') elif 13 <= col_idx <= 16: # M-P列:移动状态 cell.fill = PatternFill(start_color=colors['移动状态'], end_color=colors['移动状态'], fill_type='solid') elif 17 <= col_idx <= 22: # Q-V列:加速度 cell.fill = PatternFill(start_color=colors['加速度'], end_color=colors['加速度'], fill_type='solid') return f"{settings.MEDIA_URL}reports/{os.path.basename(report_path)}" def generate_json_summary(self): """生成JSON格式的摘要数据""" summary = {} for roi_key, metrics in self.results.items(): summary[roi_key] = { 'total_distance': metrics['total_distance_cm'], 'total_analysis_zone_distance': metrics['total_analysis_zone_distance_cm'], 'average_speed': metrics['average_speed_cm_s'], 'max_speed': metrics['max_speed_cm_s'], 'min_speed': metrics['min_speed_cm_s'], 'movement_count': len([s for s in metrics['movement_state_data'] if s['state'] == '移动']), 'total_movement_time': sum([1 for s in metrics['movement_state_data'] if s['state'] == '移动']), 'acceleration_data': metrics['acceleration_data'], 'acceleration_states': metrics['acceleration_states'], 'avg_acceleration': metrics['avg_acceleration'], 'area_id': metrics.get('area_id', 'unknown') # 添加区域ID } return summary def get_current_metrics(self): """获取当前帧的运动指标""" current_metrics = {} for roi_key, trajectory in self.trajectories.items(): if len(trajectory) < 2: current_metrics[roi_key] = { 'current_speed': 0, 'current_distance': 0, 'current_acceleration': 0, 'in_analysis_zone': self.in_analysis_zone.get(roi_key, False), 'area_id': self.roi_area_mapping.get(roi_key, 'unknown') # 添加区域ID } continue # 获取最近的两个点 prev_time, prev_center, prev_in_zone = trajectory[-2] curr_time, curr_center, curr_in_zone = trajectory[-1] # 检查中心点是否为None(目标未检测到) if prev_center is None or curr_center is None: current_metrics[roi_key] = { 'current_speed': 0, 'current_distance': 0, 'current_acceleration': 0, 'in_analysis_zone': curr_in_zone, 'area_id': self.roi_area_mapping.get(roi_key, 'unknown') # 添加区域ID } continue # 计算像素距离 pixel_distance = self._euclidean_distance(prev_center, curr_center) # 转换为厘米 cm_distance = pixel_distance / self.pixels_per_cm # 计算时间差 time_diff = curr_time - prev_time # 计算速度 (厘米/秒) speed = cm_distance / time_diff if time_diff > 0 else 0 # 计算加速度 (需要至少三个点) acceleration = 0 if len(trajectory) >= 3: prev_prev_time, prev_prev_center, _ = trajectory[-3] # 检查前一个点是否存在 if prev_prev_center is not None: prev_pixel_distance = self._euclidean_distance(prev_prev_center, prev_center) prev_cm_distance = prev_pixel_distance / self.pixels_per_cm prev_time_diff = prev_time - prev_prev_time prev_speed = prev_cm_distance / prev_time_diff if prev_time_diff > 0 else 0 acceleration = (speed - prev_speed) / time_diff if time_diff > 0 else 0 current_metrics[roi_key] = { 'current_speed': speed, 'current_distance': cm_distance, 'current_acceleration': acceleration, 'in_analysis_zone': curr_in_zone, 'area_id': self.roi_area_mapping.get(roi_key, 'unknown') # 添加区域ID } return current_metrics def _euclidean_distance(self, point1, point2): """计算两点之间的欧氏距离,如果任一点为None则返回0""" if point1 is None or point2 is None: return 0.0 return np.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2) class VideoDetectionConsumer(AsyncWebsocketConsumer): async def connect(self): logger.info(f"WebSocket 连接尝试: {self.scope}") await self.accept() logger.info("WebSocket 连接已建立") self.last_time = time.time() self.start_time = time.time() self.frame_count = 0 self.total_processing_time = 0 async def disconnect(self, close_code): pass async def receive(self, text_data=None, bytes_data=None): if text_data: text_data_json = json.loads(text_data) action = text_data_json.get('action') video_name = text_data_json.get('video') # 处理暂停指令 if action == 'pause_detection': if hasattr(self, 'detection_paused'): self.detection_paused = True self.paused_frame_index = text_data_json.get('current_frame', 0) await self.send(text_data=json.dumps({ 'type': 'pause_ack', 'message': '检测已暂停', 'paused_frame': self.paused_frame_index })) # 处理继续指令 elif action == 'resume_detection': if hasattr(self, 'detection_paused'): self.detection_paused = False self.resume_from_frame = text_data_json.get('resume_from', 0) await self.send(text_data=json.dumps({ 'type': 'resume_ack', 'message': '检测已继续', 'resume_from': self.resume_from_frame })) # 处理取消指令 elif action == 'cancel_detection': # 设置取消标志 self.detection_canceled = True # 解除暂停状态以确保能退出等待循环 if hasattr(self, 'detection_paused'): self.detection_paused = False await self.send(text_data=json.dumps({ 'type': 'cancel_ack', 'message': '检测已取消' })) # 处理常规检测启动 elif action == 'start_detection': # 确保临时目录存在 temp_dir = os.path.join(settings.BASE_DIR, 'temp') if not os.path.exists(temp_dir): os.makedirs(temp_dir) video_path = os.path.join(temp_dir, video_name) # 检查视频文件是否存在 if not os.path.exists(video_path): await self.send(text_data=json.dumps({ 'type': 'error', 'message': f'视频文件不存在: {video_path}' })) return model_path = os.path.join(settings.BASE_DIR, "C:/Users/16660/Desktop/网页搭建/Behaviewer/models/best.pt") output_video_path = os.path.join(settings.MEDIA_ROOT, 'videos', video_name) output_video_dir = os.path.dirname(output_video_path) if not os.path.exists(output_video_dir): os.makedirs(output_video_dir) # 初始化检测状态变量 self.detection_paused = False self.paused_frame_index = 0 self.detection_canceled = False # 启动视频处理任务 asyncio.create_task(self.detect_objects_in_video(model_path, video_path, output_video_path)) # 处理自定义检测启动 elif action == 'start_custom_detection': # 处理detection页面的自定义检测 temp_dir = os.path.join(settings.BASE_DIR, 'temp') if not os.path.exists(temp_dir): os.makedirs(temp_dir) video_path = os.path.join(temp_dir, video_name) # 检查视频文件是否存在 if not os.path.exists(video_path): await self.send(text_data=json.dumps({ 'type': 'error', 'message': f'视频文件不存在: {video_path}' })) return # 获取检测参数和ROI数据 params = text_data_json.get('params', {}) roi_data = text_data_json.get('roi', {}) scale_info = text_data_json.get('scale_info', {}) # 新增 # 获取实验信息 experiment_info = text_data_json.get('experiment_info', {}) # 验证比例尺信息 if 'pixel_length' not in scale_info or 'real_length' not in scale_info: await self.send(text_data=json.dumps({ 'type': 'error', 'message': '缺少比例尺信息,无法进行厘米转换' })) return output_video_path = os.path.join(settings.MEDIA_ROOT, 'videos', f"Test_{video_name}") output_video_dir = os.path.dirname(output_video_path) if not os.path.exists(output_video_dir): os.makedirs(output_video_dir) # 初始化检测状态变量 self.detection_paused = False self.paused_frame_index = 0 self.detection_canceled = False # 启动自定义视频处理任务 asyncio.create_task(self.process_custom_detection( video_path, output_video_path, params, roi_data, scale_info, experiment_info )) async def detect_objects_in_video(self, model_path, video_path, output_path): try: # 加载模型 model = YOLO(model_path) # 打开视频 cap = cv2.VideoCapture(video_path) if not cap.isOpened(): await self.send(text_data=json.dumps({ 'type': 'error', 'message': f'无法打开视频文件: {video_path}' })) return total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 创建视频写入器 fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) frame_index = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break # 处理帧 frame_index += 1 start_time = time.time() # 目标检测 results = model(frame) annotated_frame = results[0].plot() # 计算处理时间 processing_time = time.time() - start_time self.total_processing_time += processing_time self.frame_count += 1 # 计算当前FPS current_fps = 1.0 / processing_time if processing_time > 0 else 0 # 添加FPS显示 fps_text = f"FPS: {current_fps:.2f}" cv2.putText(annotated_frame, fps_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) # 保存处理后的帧 out.write(annotated_frame) # 将处理后的帧转换为base64 _, buffer = cv2.imencode('.jpg', annotated_frame) frame_base64 = base64.b64encode(buffer).decode('utf-8') # 计算进度 progress = frame_index / total_frames # 发送处理后的帧 await self.send(text_data=json.dumps({ 'type': 'frame', 'frame': frame_base64, 'objects': len(results[0].boxes), 'fps': current_fps, 'progress': progress })) # 稍微延迟以控制发送速率 await asyncio.sleep(0.01) # 释放资源 cap.release() out.release() # 计算平均FPS avg_fps = self.frame_count / self.total_processing_time if self.total_processing_time > 0 else 0 # 发送完成消息 output_video_url = f'{settings.MEDIA_URL}videos/{os.path.basename(output_path)}' await self.send(text_data=json.dumps({ 'type': 'end', 'output_video_url': output_video_url, 'total_frames': total_frames, 'avg_fps': avg_fps, 'fps': fps })) except Exception as e: await self.send(text_data=json.dumps({ 'type': 'error', 'message': f'处理错误: {str(e)}' })) import traceback traceback.print_exc() async def process_custom_detection(self, video_path, output_path, params, roi_data, scale_info, experiment_info=None): """ 自定义视频检测处理流程,包含运动指标计算 """ try: # 从接收的消息中获取实验信息 experiment_name = experiment_info.get('name', os.path.basename( video_path)) if experiment_info else os.path.basename(video_path) experiment_note = experiment_info.get('note', '') if experiment_info else '' # 验证比例尺信息 if 'pixel_length' not in scale_info or 'real_length' not in scale_info: await self.send(text_data=json.dumps({ 'type': 'error', 'message': '缺少比例尺信息,无法进行厘米转换' })) return # 构建符合 image_detect 要求的 config_area_current config_area_current = {} roi_name_mapping = {} # 初始化 ROI 名称映射 roi_area_mapping = {} # 初始化 ROI 到分析区的映射 for roi_key, roi_info in roi_data.items(): polygons = roi_info.get('polygons') if polygons and len(polygons) > 0 and len(polygons[0]) >= 3: config_area_current[roi_key] = [polygons] # 获取areaId并提取分析区编号 area_id = roi_info.get('areaId', '') original_name = roi_info.get('originalName', roi_key) # 存储ROI到分析区的映射 roi_area_mapping[roi_key] = area_id # if area_id.startswith('analysis-'): # # 提取分析区编号(例如:从"analysis-1"中提取"1") # analysis_num = area_id.split('-')[1] # roi_name_mapping[roi_key] = f'分析区{analysis_num}' # # else: # # 对于没有关联到区域的ROI,使用原始名称(去掉后缀) # if '-' in original_name: # # 提取基础名称(去掉"-矩形"或"-圆形"后缀) # base_name = original_name.split('-')[0] # roi_name_mapping[roi_key] = base_name # else: # roi_name_mapping[roi_key] = original_name if not config_area_current: await self.send(text_data=json.dumps({ 'type': 'error', 'message': '无效的 ROI 数据格式' })) return # 初始化视频分析器 analyzer = VideoAnalyzer(scale_info) analyzer.set_roi_area_mapping(roi_area_mapping) # 设置ROI到分析区的映射 # 打开视频 cap = cv2.VideoCapture(video_path) if not cap.isOpened(): await self.send(text_data=json.dumps({ 'type': 'error', 'message': f'无法打开视频文件: {video_path}' })) return # 获取视频信息 total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = 1.0 / fps if fps > 0 else 0.033 # 默认30fps width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 初始化检测状态变量 self.detection_paused = False self.paused_frame_index = 0 self.detection_canceled = False self.frame_count = 0 self.total_processing_time = 0 # 发送初始化消息,包含总帧数信息 await self.send(text_data=json.dumps({ 'type': 'detection_init', 'total_frames': total_frames })) # 创建视频写入器 fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) # 初始化帧计数器 frame_index = self.paused_frame_index if self.paused_frame_index > 0 else 0 self.frame_count = frame_index self.total_processing_time = 0 # 如果是从暂停处继续,跳转到指定帧 if frame_index > 0: cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index) while cap.isOpened() and not self.detection_canceled: # 检查暂停状态 while self.detection_paused and not self.detection_canceled: # 发送暂停状态更新 await self.send(text_data=json.dumps({ 'type': 'detection_paused', 'frame_index': frame_index, 'total_frames': total_frames })) await asyncio.sleep(0.5) # 暂停检查间隔 if self.detection_canceled: break ret, frame = cap.read() if not ret: break # 处理帧 frame_index += 1 start_time = time.time() timestamp = frame_index * frame_interval # 当前帧时间戳 # 使用 image_detect 处理当前帧 result = image_detect( frame, config_area_current, currentback=params['currentback'], kernal_erode=params['kernal_erode'], kernal_dilate=params['kernal_dilate'], kernal_erode_2=params['kernal_erode_2'], min_area=params['min_area'], max_area=params['max_area'], adjust_threshold=params['adjust_threshold'], feature=params.get('feature', 1) # 默认为中心点检测 ) # 根据实际返回值解包 if len(result) == 2: processed_frame, centers_dict = result else: # 容错处理 processed_frame = result[0] centers_dict = result[1] if len(result) > 1 else {} # 计算每个ROI是否在分析区内 in_analysis_zone_dict = {} for roi_key in centers_dict.keys(): # 这里需要根据ROI配置和检测到的中心点判断是否在分析区内 # 假设所有检测到的目标都在分析区内,实际应根据ROI配置进行判断 in_analysis_zone_dict[roi_key] = True # 记录当前帧的分析结果 analyzer.process_frame(frame_index, timestamp, centers_dict, in_analysis_zone_dict) # 计算处理时间和FPS processing_time = time.time() - start_time self.total_processing_time += processing_time self.frame_count += 1 current_fps = 1.0 / processing_time if processing_time > 0 else 0 # 使用PIL绘制中文字符 def draw_chinese_text(image, text, position, font_size=20, color=(0, 255, 0)): # 将BGR图像转换为RGB img_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(img_pil) # 加载中文字体(确保系统中有中文字体文件,或者提供字体文件路径) try: font = ImageFont.truetype("Behaviewer/static/simhei.ttf", font_size) # 使用黑体 except: font = ImageFont.load_default() # 绘制文本 draw.text(position, text, font=font, fill=color) # 转换回OpenCV格式 return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) # 修改FPS显示部分 fps_text = f"FPS: {current_fps:.2f}" processed_frame = draw_chinese_text(processed_frame, fps_text, (10, 30), 20, (0, 255, 0)) # 添加实时指标显示 current_metrics = analyzer.get_current_metrics() # 修改实时指标显示部分,使用 ROI 名称映射 if frame_index > 1: display_line = 1 for roi_key, metrics in current_metrics.items(): area_name = roi_name_mapping.get(roi_key, roi_key) if metrics['current_speed'] > 0.1: text = f"{area_name}: {metrics['current_speed']:.2f} cm/s" processed_frame = draw_chinese_text(processed_frame, text, (10, 30 + 25 * display_line), 16, (0, 255, 255)) display_line += 1 if metrics['current_distance'] > 0: text = f" 移动: {metrics['current_distance']:.2f} cm" processed_frame = draw_chinese_text(processed_frame, text, (10, 30 + 25 * display_line), 14, (0, 200, 255)) display_line += 1 if abs(metrics['current_acceleration']) > 0.1: acc_text = "加速" if metrics['current_acceleration'] > 0 else "减速" text = f" {acc_text}: {abs(metrics['current_acceleration']):.2f} cm/s²" processed_frame = draw_chinese_text(processed_frame, text, (10, 30 + 25 * display_line), 14, (0, 200, 255)) display_line += 1 # 保存处理后的帧 out.write(processed_frame) # 转换为base64发送到前端 _, buffer = cv2.imencode('.jpg', processed_frame) frame_base64 = base64.b64encode(buffer).decode('utf-8') # 计算进度 progress = frame_index / total_frames # 发送处理后的帧(包含帧索引信息) await self.send(text_data=json.dumps({ 'type': 'custom_detection_frame', 'frame': frame_base64, 'fps': current_fps, 'progress': progress, 'frame_index': frame_index, 'total_frames': total_frames, 'current_metrics': current_metrics })) # 控制发送速率 await asyncio.sleep(0.01) # 释放资源 cap.release() out.release() # 如果是正常结束而非取消,计算最终指标并生成报告 if not self.detection_canceled: # 计算运动指标 analyzer.calculate_movement_metrics() # 保存实验信息(在使用前定义) video_name = os.path.basename(video_path) json_summary = analyzer.generate_json_summary() # 新增:提取关键指标(为每个ROI都计算) key_metrics = {} for roi_key, roi_metrics in analyzer.results.items(): key_metrics[roi_key] = { 'total_distance': round(roi_metrics['total_distance_cm'], 3), 'total_analysis_zone_distance': round(roi_metrics['total_analysis_zone_distance_cm'], 3), 'avg_speed': round(roi_metrics['average_speed_cm_s'], 3), 'max_acceleration': round(roi_metrics['max_acceleration'], 3), 'min_acceleration': round(roi_metrics['min_acceleration'], 3), 'total_movement_duration': round(sum(ep['duration'] for ep in roi_metrics['movement_episodes']), 3), 'area_id': roi_metrics.get('area_id', 'unknown') } experiment_info = { 'name': experiment_name, 'note': experiment_note, 'duration': self.total_processing_time, 'total_frames': total_frames, 'metrics': json_summary } # 生成报告(现在 experiment_info 已经定义) report_url = analyzer.generate_excel_report(video_name, experiment_info) # 计算平均FPS avg_fps = self.frame_count / self.total_processing_time if self.total_processing_time > 0 else 0 # 发送完成消息 output_video_url = f'{settings.MEDIA_URL.rstrip("/")}/videos/{os.path.basename(output_path)}' await self.send(text_data=json.dumps({ 'type': 'custom_detection_end', 'output_video_url': output_video_url, 'analysis_report_url': report_url, 'metrics_summary': json_summary, 'key_metrics': key_metrics, # 新增关键指标字段 'roi_area_mapping': roi_area_mapping, # 新增ROI到分析区的映射 'total_frames': total_frames, 'processed_frames': frame_index, 'avg_fps': avg_fps, 'fps': fps, 'experiment_info': { 'name': experiment_name, 'note': experiment_note, 'duration': self.total_processing_time, # 这是总处理时间 'total_frames': total_frames, 'metrics': json_summary } })) else: # 发送取消消息 await self.send(text_data=json.dumps({ 'type': 'detection_canceled', 'processed_frames': frame_index, 'total_frames': total_frames })) except Exception as e: await self.send(text_data=json.dumps({ 'type': 'error', 'message': f'处理错误: {str(e)}' })) import traceback traceback.print_exc()
08-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值