YOLO旋轉目標檢測之ONNX模型推理

YOLO旋轉檢測相較于目標檢測而言,其只是最后的輸出層網絡發生了改變,一個最明顯的區別便是:目標檢測的檢測框是xywh,而旋轉檢測則為xywha,其中,這個a代表angle,即旋轉角度,其余的基本相同。
在這里插入圖片描述

pt模型推理

這里我們在模型訓練完成后,即可進行推理操作,這里我們首先使用默認的模型格式,即pt格式

from ultralytics import YOLO
import cv2
import numpy as np
# 加載模型
model = YOLO("best.pt")  # 加載訓練好的旋轉框檢測模型
# 預測圖像
results = model("1.jpg")  # 預測圖像
# 可視化參數配置
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.6
thickness = 2
colors = [(0,255,0), (255,0,0), (0,0,255)]  # 不同類別的顏色
# 遍歷每個檢測結果
for result in results:# 獲取原始圖像并轉換為OpenCV格式img = result.orig_img.copy()# 遍歷每個旋轉框for polygon, cls, conf in zip(result.obb.xyxyxyxy, result.obb.cls, result.obb.conf):# 將坐標轉換為整數類型pts = polygon.cpu().numpy().reshape(-1, 2).astype(int)# 繪制多邊形邊界框cv2.polylines(img, [pts], isClosed=True,color=colors[int(cls)%len(colors)],thickness=thickness)# 構建標簽文本label = f"{result.names[int(cls)]} {conf:.2f}"# 計算文本位置(取第一個點上方)text_origin = (pts[0][0], pts[0][1] - 10 if pts[0][1] > 20 else pts[0][1] + 20)# 繪制文本背景(text_w, text_h), _ = cv2.getTextSize(label, font, font_scale, thickness)cv2.rectangle(img,(text_origin[0], text_origin[1] - text_h - 5),(text_origin[0] + text_w, text_origin[1] + 5),colors[int(cls)%len(colors)],-1)  # 填充矩形# 繪制文本cv2.putText(img, label,(text_origin[0], text_origin[1]),font, font_scale,(255,255,255),  # 白色文字thickness)cv2.imwrite("result.jpg", img)

從結果來看,輸出的結果的維度為(4,7)其中4代表4個結果,7則是對應的內容,根據拆分結果來看,分別是xywhr以及class_id(類別編號)以及scores(置信度),同時需要注意的是,使用pt的推理結果中,其自動執行了將xywhr轉換為xyxyxyxy的操作,這方便我們直接使用opencv中的rectangle方法進行繪圖操作。

在這里插入圖片描述

ONNX模型推理

ultralytics中提供了將pt文件轉換為onnxtflite等多種格式的方法,ONNX(Open Neural Network Exchange)是一種開放的文件格式,用于表示機器學習模型。它使得不同的人工智能框架能夠互相交換模型,從而提高了模型的可移植性和互操作性。通過ONNX,開發者可以在一個框架中訓練模型,然后將該模型遷移到另一個支持ONNX的框架中進行推理,而無需重新訓練或大幅修改模型。

使用ONNX模型進行推理的代碼如下:其主要包含數據預處理、模型加載、模型推理三個步驟:

def load_model(weights):"""加載ONNX模型并返回會話對象。:param weights: 模型權重文件路徑:return: ONNX運行會話對象"""session = ort.InferenceSession(weights, providers=['CPUExecutionProvider'])logging.info(f"模型加載成功: {weights}")return sessiondef run_inference(session, image_bytes, imgsz=(640, 640)):"""對輸入圖像進行預處理,然后使用ONNX模型執行推理。:param session: ONNX運行會話對象:param image_bytes: 輸入圖像的字節數據:param imgsz: 模型輸入的尺寸:return: 推理結果、縮放比例、填充尺寸"""im0 = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR)  # 解碼圖像字節數據if im0 is None:raise ValueError("無法從image_bytes解碼圖像")img, ratio, (dw, dh) = letterbox(im0, new_shape=imgsz)  # 調整圖像尺寸img = img.transpose((2, 0, 1))[::-1]  # 調整通道順序由(640,640,3)變為(3,640,640)img = np.ascontiguousarray(img)img = img[np.newaxis, ...].astype(np.float32) / 255.0  # 歸一化處理input_name = session.get_inputs()[0].nameresult = session.run(None, {input_name: img})  # 執行模型推理return result[0], ratio, (dw, dh)def process_images_in_folder(folder_path, model_weights, output_folder, conf_threshold, iou_threshold, imgsz):"""批量處理文件夾中的圖像,執行推理、解析和可視化,保存結果。:param folder_path: 輸入圖像文件夾路徑:param model_weights: ONNX模型權重文件路徑:param output_folder: 輸出結果文件夾路徑:param conf_threshold: 置信度閾值:param iou_threshold: IoU 閾值,用于旋轉NMS:param imgsz: 模型輸入大小"""session = load_model(weights=model_weights)  # 加載ONNX模型if not os.path.exists(output_folder):os.makedirs(output_folder)  # 如果輸出文件夾不存在,則創建for filename in os.listdir(folder_path):if filename.endswith(('.jpg', '.png', '.jpeg')):  # 處理圖片文件image_path = os.path.join(folder_path, filename)with open(image_path, 'rb') as f:image_bytes = f.read()print("image_path:", image_path)raw_output, ratio, dwdh = run_inference(session=session, image_bytes=image_bytes, imgsz=imgsz)  # 執行推理# 主函數:加載參數
if __name__ == "__main__":folder_path = r"images"  # 輸入圖像文件夾路徑model_weights = r"best.onnx"  # ONNX模型路徑output_folder = "results"  # 輸出結果文件夾conf_threshold = 0.5  # 置信度閾值iou_threshold = 0.5  # IoU閾值,用于旋轉NMSimgsz = (640, 640)  # 模型輸入大小process_images_in_folder(folder_path, model_weights, output_folder, conf_threshold, iou_threshold, imgsz)  # 執行批量處理

推理出的結果如下:raw_output(1,7,8400), ratio((0.15873015873015872, 0.15873015873015872)為縮放比例, dwdh(0.0, 80.0)是填充尺度。

隨后,便是結果解析了,即后處理過程,如下:
這里需要注意的是,輸出結果為(8400,7)其中,0-3xywh4scores5class_id6angle

import os
import cv2
import numpy as np
import onnxruntime as ort
import logging"""
YOLO11 旋轉目標檢測OBB
1、ONNX模型推理、可視化
2、ONNX輸出格式: x_center, y_center, width, height, class1_confidence, ..., classN_confidence, angle
3、支持不同尺寸圖片輸入、支持旋轉NMS過濾重復框、支持ProbIoU旋轉IOU計算
"""def letterbox(img, new_shape=(640, 640), color=(0, 0, 0), auto=False, scale_fill=False, scale_up=False, stride=32):"""將圖像調整為指定尺寸,同時保持長寬比,添加填充以適應目標輸入形狀。:param img: 輸入圖像:param new_shape: 目標尺寸:param color: 填充顏色:param auto: 是否自動調整填充為步幅的整數倍:param scale_fill: 是否強制縮放以完全填充目標尺寸:param scale_up: 是否允許放大圖像:param stride: 步幅,用于自動調整填充:return: 調整后的圖像、縮放比例、填充尺寸(dw, dh)"""shape = img.shape[:2]if isinstance(new_shape, int):new_shape = (new_shape, new_shape)r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])  # 計算縮放比例if not scale_up:r = min(r, 1.0)ratio = r, rnew_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]if auto:dw, dh = np.mod(dw, stride), np.mod(dh, stride)elif scale_fill:dw, dh = 0.0, 0.0new_unpad = (new_shape[1], new_shape[0])ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]dw /= 2  # 填充均分dh /= 2if shape[::-1] != new_unpad:img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))left, right = int(round(dw - 0.1)), int(round(dw + 0.1))img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)return img, ratio, (dw, dh)def _get_covariance_matrix(obb):"""計算旋轉邊界框的協方差矩陣。:param obb: 旋轉邊界框 (Oriented Bounding Box),包含中心坐標、寬、高和旋轉角度:return: 協方差矩陣的三個元素 a, b, c"""widths = obb[..., 2] / 2heights = obb[..., 3] / 2angles = obb[..., 4]cos_angle = np.cos(angles)sin_angle = np.sin(angles)a = (widths * cos_angle)**2 + (heights * sin_angle)**2b = (widths * sin_angle)**2 + (heights * cos_angle)**2c = widths * cos_angle * heights * sin_anglereturn a, b, cdef batch_probiou(obb1, obb2, eps=1e-7):"""計算旋轉邊界框之間的 ProbIoU。:param obb1: 第一個旋轉邊界框集合:param obb2: 第二個旋轉邊界框集合:param eps: 防止除零的極小值:return: 兩個旋轉邊界框之間的 ProbIoU"""x1, y1 = obb1[..., 0], obb1[..., 1]x2, y2 = obb2[..., 0], obb2[..., 1]a1, b1, c1 = _get_covariance_matrix(obb1)a2, b2, c2 = _get_covariance_matrix(obb2)t1 = ((a1[:, None] + a2) * (y1[:, None] - y2)**2 + (b1[:, None] + b2) * (x1[:, None] - x2)**2) / ((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2 + eps) * 0.25t2 = ((c1[:, None] + c2) * (x2 - x1[:, None]) * (y1[:, None] - y2)) / ((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2 + eps) * 0.5t3 = np.log(((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2) /(4 * np.sqrt((a1 * b1 - c1**2)[:, None] * (a2 * b2 - c2**2)) + eps) + eps) * 0.5bd = np.clip(t1 + t2 + t3, eps, 100.0)hd = np.sqrt(1.0 - np.exp(-bd) + eps)return 1 - hddef rotated_nms_with_probiou(boxes, scores, iou_threshold=0.5):"""使用 ProbIoU 執行旋轉邊界框的非極大值抑制(NMS)。:param boxes: 旋轉邊界框的集合:param scores: 每個邊界框的置信度得分:param iou_threshold: IoU 閾值,用于確定是否抑制框:return: 保留的邊界框索引列表"""order = scores.argsort()[::-1]  # 根據置信度得分降序排序keep = []while len(order) > 0:i = order[0]keep.append(i)if len(order) == 1:breakremaining_boxes = boxes[order[1:]]iou_values = batch_probiou(boxes[i:i+1], remaining_boxes).squeeze(0)mask = iou_values < iou_threshold  # 保留 IoU 小于閾值的框order = order[1:][mask]return keepdef parse_onnx_output(output, ratio, dwdh, conf_threshold=0.5, iou_threshold=0.5):"""解析ONNX模型的輸出,提取旋轉邊界框坐標、置信度和類別信息,并應用旋轉NMS。:param output: ONNX模型的輸出,包含預測的邊界框信息:param ratio: 縮放比例,用于將坐標還原到原始尺度:param dwdh: 填充的寬高,用于調整邊界框的中心點坐標:param conf_threshold: 置信度閾值,過濾低于該閾值的檢測框:param iou_threshold: IoU 閾值,用于旋轉邊界框的非極大值抑制(NMS):return: 符合條件的旋轉邊界框的檢測結果"""boxes, scores, classes, detections = [], [], [], []num_detections = output.shape[2]  # 獲取檢測的邊界框數量num_classes = output.shape[1] - 6  # 計算類別數量# 逐個解析每個檢測結果for i in range(num_detections):detection = output[0, :, i]x_center, y_center, width, height = detection[0], detection[1], detection[2], detection[3]  # 提取邊界框的中心坐標和寬高angle = detection[-1]  # 提取旋轉角度if num_classes > 0:class_confidences = detection[4:4 + num_classes]  # 獲取類別置信度if class_confidences.size == 0:continueclass_id = np.argmax(class_confidences)  # 獲取置信度最高的類別索引confidence = class_confidences[class_id]  # 獲取對應的置信度else:confidence = detection[4]  # 如果沒有類別信息,直接使用置信度值class_id = 0  # 默認類別為 0if confidence > conf_threshold:  # 過濾掉低置信度的檢測結果x_center = (x_center - dwdh[0]) / ratio[0]  # 還原中心點 x 坐標y_center = (y_center - dwdh[1]) / ratio[1]  # 還原中心點 y 坐標width /= ratio[0]  # 還原寬度height /= ratio[1]  # 還原高度boxes.append([x_center, y_center, width, height, angle])  # 將邊界框信息加入列表scores.append(confidence)  # 將置信度加入列表classes.append(class_id)  # 將類別加入列表if not boxes:return []# 轉換為 NumPy 數組boxes = np.array(boxes)scores = np.array(scores)classes = np.array(classes)# 應用旋轉 NMSkeep_indices = rotated_nms_with_probiou(boxes, scores, iou_threshold=iou_threshold)# 構建最終檢測結果for idx in keep_indices:x_center, y_center, width, height, angle = boxes[idx]  # 獲取保留的邊界框信息confidence = scores[idx]  # 獲取對應的置信度class_id = classes[idx]  # 獲取類別obb_corners = calculate_obb_corners(x_center, y_center, width, height, angle)  # 計算旋轉邊界框的四個角點detections.append({"position": obb_corners,  # 旋轉邊界框的角點坐標"confidence": float(confidence),  # 置信度"class_id": int(class_id),  # 類別 ID"angle": float(angle)  # 旋轉角度})return detectionsdef calculate_obb_corners(x_center, y_center, width, height, angle):"""根據旋轉角度計算旋轉邊界框的四個角點。:param x_center: 邊界框中心的 x 坐標:param y_center: 邊界框中心的 y 坐標:param width: 邊界框的寬度:param height: 邊界框的高度:param angle: 旋轉角度:return: 旋轉邊界框的四個角點坐標"""cos_angle = np.cos(angle)  # 計算旋轉角度的余弦值sin_angle = np.sin(angle)  # 計算旋轉角度的正弦值dx = width / 2  # 計算寬度的一半dy = height / 2  # 計算高度的一半# 計算旋轉邊界框的四個角點坐標corners = [(int(x_center + cos_angle * dx - sin_angle * dy), int(y_center + sin_angle * dx + cos_angle * dy)),(int(x_center - cos_angle * dx - sin_angle * dy), int(y_center - sin_angle * dx + cos_angle * dy)),(int(x_center - cos_angle * dx + sin_angle * dy), int(y_center - sin_angle * dx - cos_angle * dy)),(int(x_center + cos_angle * dx + sin_angle * dy), int(y_center + sin_angle * dx - cos_angle * dy)),]return corners  # 返回角點坐標def save_detections(image, detections, output_path):"""在圖像上繪制旋轉邊界框檢測結果并保存。:param image: 原始圖像:param detections: 檢測結果列表:param output_path: 保存路徑"""for det in detections:corners = det['position']  # 獲取旋轉邊界框的四個角點confidence = det['confidence']  # 獲取置信度class_id = det['class_id']  # 獲取類別ID# 繪制邊界框的四條邊for j in range(4):pt1 = corners[j]pt2 = corners[(j + 1) % 4]cv2.line(image, pt1, pt2, (0, 0, 255), 2)# 在邊界框上方顯示類別和置信度cv2.putText(image, f'Class: {class_id}, Conf: {confidence:.2f}',(corners[0][0], corners[0][1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 3)cv2.imwrite(output_path, image)  # 保存繪制后的圖像

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/81349.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81349.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/81349.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

架構進階:深入學習企業總體架構規劃(Oracle 戰略專家培訓課件)【附全文閱讀】

本文主要討論了企業總體技術架構規劃的重要性與實施建議。針對Oracle戰略專家培訓課件中的內容&#xff0c;文章強調了行業面臨的挑戰及現狀分析、總體技術架構探討、SOA集成解決方案討論與問題解答等方面。文章指出&#xff0c;為了消除信息孤島、強化應用系統&#xff0c;需要…

llamafactory-cli webui啟動報錯TypeError: argument of type ‘bool‘ is not iterable

一、問題 在阿里云NoteBook上啟動llamafactory-cli webui報錯TypeError: argument of type ‘bool’ is not iterable This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run gradio deploy from the terminal in the working directory t…

Gas 優化不足、升級機制缺陷問題

以下是針對智能合約中 Gas 優化不足 與 升級機制缺陷 的技術風險分析與解決方案: 一、Gas 優化不足 1. 核心問題 Gas 優化不足會導致合約執行成本過高,直接影響用戶體驗和協議可行性,尤其在交易高峰期可能引發鏈上擁堵或交易失敗。 2. 常見風險點 冗余計算與存儲操作 例如…

使用xlwings計算合并單元格的求和

有如下一個excel表 表內有合并單元格&#xff0c;現在需要求和&#xff0c;不能直接下拉填充公式怎么辦&#xff1f; 通常的辦法是先取消合并單元格&#xff0c;計算后&#xff0c;再次合并單元格&#xff0c;比較繁瑣。 在此&#xff0c;嘗試使用python和xlwings運行直接給出…

[創業之路-354]:農業文明到智能紀元:四次工業革命下的人類遷徙與價值重構

農業文明到智能紀元&#xff1a;四次工業革命下的人類遷徙與價值重構 從游牧到定居&#xff0c;從蒸汽轟鳴到算法洪流&#xff0c;人類文明的每一次跨越都伴隨著生產關系的劇烈震蕩。四次工業革命的浪潮不僅重塑了物質世界的生產方式&#xff0c;更將人類推向了身份認同與存在…

LeetCode 2302.統計得分小于 K 的子數組數目:滑動窗口(不需要前綴和)

【LetMeFly】2302.統計得分小于 K 的子數組數目&#xff1a;滑動窗口&#xff08;不需要前綴和&#xff09; 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/count-subarrays-with-score-less-than-k/ 一個數組的 分數 定義為數組之和 乘以 數組的長度。 比方說&…

kafka學習筆記(四、生產者(客戶端)深入研究(二)——消費者協調器與_consumer_offsets剖析)

1.消費者協調器和組協調器 如果消費者客戶端中配置了多個分配策略&#xff0c;則多消費者的分區分配交由消費者協調器和組協調器來完成&#xff0c;他們之間使用一套組協調協議進行交互。 1.1.在均衡原理 將全部消費者分成多個子集&#xff0c;每個消費者組的子集在服務中對…

快速將FastAPI接口轉為模型上下文協議(MCP)!

fastapi_mcp 是一個用于將 FastAPI 端點暴露為模型上下文協議&#xff08;Model Context Protocol, MCP&#xff09;工具的庫&#xff0c;并且支持認證功能。 環境macbook&#xff0c;python3.13 pip install fastapi uvicorn fastapi-mcp 代碼 from fastapi import FastAPI, …

實驗數據的轉換

最近做實驗需要把x軸y軸z軸的數據處理一下&#xff0c;總結一下解決的方法&#xff1a; 源文件為兩個txt文檔&#xff0c;分別為x軸和y軸&#xff0c;如下&#xff1a; 最終需要達到的效果是如下&#xff1a; 就是需要把各個矩陣的數據整理好放在同一個txt文檔里。 步驟① …

第Y3周:yolov5s.yaml文件解讀

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 本次任務&#xff1a;將yolov5s網絡模型中的第4層的C3x2修改為C3x1&#xff0c;第6層的C3x3修改為C3x2。 首先輸出原來的網絡結構&#xff1a; from n pa…

Ansible安裝配置

一、前提 服務器操作系統均為centos7.9 主機ipmaster(Ansible管理端)172.25.192.2node1172.25.192.10node2172.25.192.3 更新/etc/hosts文件 二、安裝 master節點&#xff1a; 1. 安裝epel源 yum install -y epel-release 2. 安裝Ansible yum install -y ansible A…

MySQL中ROW_NUMBER() OVER的用法以及使用場景

使用語法 ROW_NUMBER() OVER ([PARTITION BY partition_column1, partition_column2, ...]ORDER BY sort_column1 [ASC|DESC], sort_column2 [ASC|DESC], ... )PARTITION BY&#xff1a;將數據按指定列分組&#xff0c;每組內單獨生成行號。ORDER BY&#xff1a;決定組內行號的…

【人工智能】釋放本地AI潛能:LM Studio用戶腳本自動化DeepSeek的實戰指南

《Python OpenCV從菜鳥到高手》帶你進入圖像處理與計算機視覺的大門! 解鎖Python編程的無限可能:《奇妙的Python》帶你漫游代碼世界 隨著大型語言模型(LLM)的快速發展,DeepSeek以其高效的性能和開源特性成為開發者關注的焦點。LM Studio作為一款強大的本地AI模型管理工具…

筆試強訓:Day3

一、牛牛沖鉆五&#xff08;模擬&#xff09; 登錄—專業IT筆試面試備考平臺_牛客網 #include<iostream> using namespace std; int main(){int t,n,k;string s;cin>>t;while(t--){cin>>n>>k>>s;int ret0;//統計加了多少星for(int i0;i<n;i)…

語音識別質量的跟蹤

背景 這個項目是用來生成結構化的電子病歷的。數據的來源是醫生的錄音。中間有一大堆的處理&#xff0c;語音識別&#xff0c;關鍵字匹配&#xff0c;結構化處理&#xff0c;病歷編輯......。最多的時候給上百家醫院服務。 語音識別質量的跟蹤 一、0225醫院的訓練后的情況分…

人工智能搜索時代的SEO:關鍵趨勢與優化策略

隨著人工智能&#xff08;AI&#xff09;技術的飛速發展&#xff0c;搜索引擎的運作方式正在經歷前所未有的變革。2025年&#xff0c;AI驅動的搜索&#xff08;如谷歌的AI概覽、ChatGPT搜索和必應的AI增強功能&#xff09;不僅改變了用戶獲取信息的方式&#xff0c;還為SEO從業…

Node.js心得筆記

npm init 可用npm 來調試node項目 瀏覽器中的頂級對象時window <ref *1> Object [global] { global: [Circular *1], clearImmediate: [Function: clearImmediate], setImmediate: [Function: setImmediate] { [Symbol(nodejs.util.promisify.custom)]: [Getter] }, cl…

計算機網絡01-網站數據傳輸過程

局域網&#xff1a; 覆蓋范圍小&#xff0c;自己花錢買設備&#xff0c;寬帶固定&#xff0c;自己維護&#xff0c;&#xff0c;一般長度不超過100米&#xff0c;&#xff0c;&#xff0c;帶寬也比較固定&#xff0c;&#xff0c;&#xff0c;10M&#xff0c;&#xff0c;&…

Mysql常用函數解析

字符串函數 CONCAT(str1, str2, …) 將多個字符串連接成一個字符串。 SELECT CONCAT(Hello, , World); -- 輸出: Hello World??SUBSTRING(str, start, length) 截取字符串的子串&#xff08;起始位置從1開始&#xff09;。 SELECT SUBSTRING(MySQL, 3, 2); -- 輸出: SQ…

SpringMVC 前后端數據交互 中文亂碼

ajax 前臺傳入數據&#xff0c;但是后臺接收到的數據中文亂碼 首先我們分析一下原因&#xff1a;我們調用接口的時候傳入的中文&#xff0c;是沒有亂碼的 此時我們看一下Java后臺接口對應的編碼&#xff1a; 默認情況&#xff1a;Servlet容器&#xff08;如Tomcat&#xff09;默…