项目背景

在计算机视觉任务中,我们经常需要对大量图片进行目标检测和标注。YOLO 系列模型凭借其高效性成为目标检测的首选工具之一,但批量处理图片时往往需要编写繁琐的脚本。本文将介绍一个基于 Flask 和 YOLOv11 的 API 服务,支持单张图片和文件夹批量处理,可自定义置信度、交并比等参数,并能返回详细的标注统计结果。

功能特点

  • 支持单张图片和文件夹批量处理,自动识别输入类型
  • 可自定义置信度阈值 (conf) 和交并比阈值 (iou)
  • 自动选择运行设备 (GPU 优先,无则 CPU)
  • 生成标注后的图片和检测结果 TXT 文件
  • 返回详细的标注统计信息 (每个文件的目标类别及数量)
  • 提供完整的任务状态查询和结果下载功能

技术栈

  • fastapi:用于构建高性能的 Web API。
  • uvicorn:一个快速的 ASGI 服务器,用于运行 FastAPI 应用。
  • pydantic:用于数据验证和设置类型提示。
  • ultralytics:包含 YOLO 模型,用于目标检测。
  • opencv-python:用于图像处理和计算机视觉任务。
  • numpy:用于数值计算。
  • pillow:Python Imaging Library,用于图像处理。
  • torch:PyTorch 深度学习框架,YOLO 模型依赖于此。
  • base64:用于 Base64 编码和解码,虽然是 Python 标准库,但为了完整性列出。

代码实现

完整代码

import os
import shutil
import time
import json
import logging
import cv2
import numpy as np
import base64
from typing import Dict, Any, Optional, List
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Query
from fastapi.responses import JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from ultralytics import YOLO
from PIL import Image, ImageDraw, ImageFont
import torch
import threading# 配置日志系统,设置日志级别为INFO,记录关键操作和异常信息
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)# 初始化FastAPI应用,设置API标题和版本
app = FastAPI(title="YOLO目标检测API", version="1.0")# 配置CORS(跨域资源共享),允许所有来源的请求访问API
app.add_middleware(CORSMiddleware,allow_origins=["*"],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],
)# 任务状态跟踪字典,用于存储每个检测任务的执行状态和结果
tasks: Dict[str, Dict[str, Any]] = {}# 定义绘制检测框的颜色列表,为不同类别分配不同颜色
COLORS = [(0, 255, 0), (0, 0, 255), (255, 0, 0), (255, 255, 0), (0, 255, 255),(255, 0, 255), (128, 0, 0), (0, 128, 0), (0, 0, 128)
]# 尝试加载Arial字体用于绘制标签,若加载失败则使用默认字体
try:font = ImageFont.truetype("arial.ttf", 18)
except:font = ImageFont.load_default()# 定义检测请求的数据模型,使用pydantic进行参数校验
class DetectRequest(BaseModel):input_path: str  # 输入文件或文件夹路径output_dir: str = "demo"  # 输出目录,默认为demomodel_path: str = "yolo11n.pt"  # 模型路径,默认为YOLO Nano版本device: Optional[str] = None  # 设备选择,可选参数(如'0'表示GPU,'cpu'表示CPU)conf: float = 0.25  # 置信度阈值,过滤低置信度的检测结果iou: float = 0.7  # IOU(交并比)阈值,用于非极大值抑制target_classes: Optional[str] = None  # 目标类别,逗号分隔的字符串(如"person,car")def draw_annotations(image: np.ndarray, boxes, class_names) -> np.ndarray:"""在图像上绘制检测框和类别标签Args:image: 输入的图像数组(BGR格式)boxes: 过滤后的检测框列表(YOLO模型的Box对象)class_names: 类别名称字典({类别ID: 类别名称})Returns:绘制标注后的图像数组(BGR格式)"""# 将OpenCV的BGR格式转换为PIL的RGB格式,用于绘制文本frame_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(frame_pil)if len(boxes) == 0:# 若无检测结果,直接返回原图return image# 遍历每个检测框for box in boxes:# 解析框坐标、类别ID和置信度x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())  # 检测框的左上角和右下角坐标class_id = int(box.cls)  # 类别IDconf = float(box.conf)   # 置信度# 为不同类别分配不同颜色(循环使用预定义颜色列表)color = COLORS[class_id % len(COLORS)]# 绘制边界框draw.rectangle([(x1, y1), (x2, y2)], outline=color, width=3)# 构建标签文本(类别名 + 置信度)label = f"{class_names[class_id]}: {conf:.2f}"# 获取文本边界框,用于确定标签背景位置try:text_bbox = draw.textbbox((x1, y1), label, font=font)except AttributeError:# 兼容旧版本PIL,使用textsize方法替代textbboxtext_width, text_height = draw.textsize(label, font=font)text_bbox = (x1, y1, x1 + text_width, y1 + text_height)# 计算标签的垂直位置,避免超出图像边界text_height = text_bbox[3] - text_bbox[1]label_y1 = y1 - text_height - 5 if (y1 - text_height - 5) > 0 else y1 + 5# 绘制标签背景(与边界框颜色相同的矩形)draw.rectangle([(x1, label_y1), (x1 + (text_bbox[2] - text_bbox[0]), label_y1 + text_height)],fill=color)# 绘制标签文本(白色字体)draw.text((x1, label_y1), label, font=font, fill=(255, 255, 255))# 将PIL图像转换回OpenCV的BGR格式return cv2.cvtColor(np.array(frame_pil), cv2.COLOR_RGB2BGR)def batch_detect_and_annotate(task_id: str,input_path: str,output_dir: str = "demo",model_path: str = "yolo11n.pt",device: Optional[str] = None,conf: float = 0.25,iou: float = 0.7,target_classes: Optional[str] = None
):"""批量处理图像或文件夹中的所有图像,执行目标检测并生成标注图像和结果文件Args:task_id: 唯一任务标识符input_path: 输入文件或文件夹路径output_dir: 输出目录model_path: YOLO模型路径device: 推理设备(如'0'表示GPU,'cpu'表示CPU)conf: 置信度阈值iou: IOU阈值target_classes: 目标类别(逗号分隔的字符串)"""# 初始化任务状态为"running"tasks[task_id] = {"status": "running", "progress": 0, "message": "开始处理..."}try:start_time = time.time()  # 记录开始时间os.makedirs(output_dir, exist_ok=True)  # 创建输出目录(如果不存在)# 自动选择设备:若未指定则优先使用GPU,否则使用CPUselected_device = device if device else ('0' if torch.cuda.is_available() else 'cpu')# 加载YOLO模型try:model = YOLO(model_path)except Exception as e:# 模型加载失败,更新任务状态为"failed"tasks[task_id] = {"status": "failed", "message": f"模型加载失败:{str(e)}"}return# 解析目标类别参数(如果有)target_set = Noneif target_classes:# 将逗号分隔的字符串转换为集合,便于快速查找target_set = set([cls.strip() for cls in target_classes.split(',')])# 验证目标类别是否存在于模型类别中model_classes = set(model.names.values())invalid_classes = [cls for cls in target_set if cls not in model_classes]if invalid_classes:# 若存在无效类别,更新任务状态为"failed"tasks[task_id] = {"status": "failed", "message": f"无效的目标类别: {', '.join(invalid_classes)}"}return# 处理输入路径(文件或文件夹)input_dir = None  # 预初始化输入目录变量is_single_file = Falseif os.path.isfile(input_path):# 输入是单个文件image_files = [os.path.basename(input_path)]  # 获取文件名input_dir = os.path.dirname(input_path)       # 获取文件所在目录is_single_file = Trueelif os.path.isdir(input_path):# 输入是文件夹image_extensions = (".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff")# 筛选文件夹中的所有图像文件image_files = [f for f in os.listdir(input_path)if f.lower().endswith(image_extensions) and os.path.isfile(os.path.join(input_path, f))]input_dir = input_path  # 输入目录即为指定文件夹is_single_file = Falseelse:# 输入路径不存在,更新任务状态为"failed"tasks[task_id] = {"status": "failed", "message": f"输入路径不存在: {input_path}"}returnif not image_files:# 未找到图像文件,更新任务状态为"failed"tasks[task_id] = {"status": "failed", "message": f"未找到图片文件: {input_path}"}returntotal_files = len(image_files)  # 总文件数success_count = 0               # 成功处理的文件数fail_count = 0                  # 处理失败的文件数file_annotations = {}           # 存储每个文件的检测结果# 遍历所有图像文件for i, img_file in enumerate(image_files, 1):img_path = os.path.join(input_dir, img_file)  # 构建完整文件路径img_name = os.path.splitext(img_file)[0]      # 获取不带扩展名的文件名# 更新任务进度progress = int((i / total_files) * 100)tasks[task_id]["progress"] = progresstasks[task_id]["message"] = f"正在处理:{img_file}"try:# 执行目标检测results = model(img_path, device=selected_device, conf=conf, iou=iou)all_boxes = results[0].boxes  # 获取所有检测框# 过滤检测框(如果指定了目标类别)filtered_boxes = []if target_set:# 仅保留目标类别中的检测框for box in all_boxes:cls_name = model.names[int(box.cls)]if cls_name in target_set:filtered_boxes.append(box)else:# 未指定目标类别时,保留所有检测框filtered_boxes = list(all_boxes)# 读取原始图像image = cv2.imread(img_path)if image is None:raise Exception(f"无法读取图像: {img_path}")# 生成标注图像annotated_img = draw_annotations(image, filtered_boxes, model.names)# 定义输出文件路径output_img_name = f"{img_name}_annotated.jpg"output_txt_name = f"{img_name}_detections.txt"output_img_path = os.path.join(output_dir, output_img_name)txt_path = os.path.join(output_dir, output_txt_name)# 保存标注图像cv2.imwrite(output_img_path, annotated_img)# 保存检测结果到文本文件with open(txt_path, "w", encoding="utf-8") as f:for box in filtered_boxes:cls_name = model.names[int(box.cls)]confidence = round(float(box.conf), 4)x1, y1, x2, y2 = map(round, box.xyxy[0].tolist())f.write(f"{cls_name} {confidence} {x1} {y1} {x2} {y2}\n")# 统计每个类别的检测数量annotations = {}for box in filtered_boxes:cls_name = model.names[int(box.cls)]annotations[cls_name] = annotations.get(cls_name, 0) + 1# 记录当前文件的检测结果file_annotations[img_name] = {"annotated_image": output_img_path,"detection_txt": txt_path,"class_counts": annotations}success_count += 1  # 成功计数加1except Exception as e:# 单个文件处理失败,记录错误并继续处理下一个fail_count += 1logger.error(f"处理{img_file}失败: {str(e)}")# 计算总处理时间total_time = round(time.time() - start_time, 2)# 更新任务状态为"completed",并保存详细结果tasks[task_id] = {"status": "completed", "progress": 100,"total_time": total_time,"success_count": success_count,"fail_count": fail_count,"total_files": total_files,"output_dir": os.path.abspath(output_dir),"input_path": input_path,"is_single_file": is_single_file,"parameters": {"confidence_threshold": conf,"iou_threshold": iou,"device": selected_device,"target_classes": list(target_set) if target_set else None},"annotations": file_annotations,"message": "处理完成"}except Exception as e:# 发生未知错误,更新任务状态为"failed"tasks[task_id] = {"status": "failed", "message": f"未知错误:{str(e)}"}@app.post("/detect")
async def detect(request: DetectRequest):"""接收参数并启动目标检测任务(同步模式)Args:request: 检测请求参数Returns:任务执行结果"""logger.info(f"收到检测请求: {request.input_path}")# 验证参数范围if not (0 <= request.conf <= 1):raise HTTPException(status_code=400, detail="conf参数必须在0-1之间")if not (0 <= request.iou <= 1):raise HTTPException(status_code=400, detail="iou参数必须在0-1之间")# 生成唯一任务ID(使用时间戳确保唯一性)task_id = str(int(time.time() * 1000))logger.info(f"创建任务: {task_id}")try:# 执行检测任务(同步调用,会阻塞直到完成)batch_detect_and_annotate(task_id, request.input_path, request.output_dir, request.model_path, request.device, request.conf, request.iou, request.target_classes)# 获取任务结果task_result = tasks.get(task_id)if not task_result:raise HTTPException(status_code=500, detail="任务执行失败,未获取到结果")if task_result["status"] == "failed":# 任务执行失败,返回错误信息return JSONResponse(status_code=400,content={"task_id": task_id,"status": "failed","message": task_result["message"]})logger.info(f"任务完成: {task_id}, 处理时间: {task_result['total_time']}秒")return task_result  # 返回完整的任务结果except Exception as e:# 处理请求过程中发生异常logger.exception(f"请求处理失败: {str(e)}")raise HTTPException(status_code=500, detail=f"请求处理失败: {str(e)}")@app.get("/status/{task_id}")
async def get_status(task_id: str):"""获取指定任务的执行状态Args:task_id: 任务IDReturns:任务状态信息"""status = tasks.get(task_id, {"status": "not_found", "message": "任务ID不存在"})return status@app.get("/results/{task_id}")
async def get_results(task_id: str):"""获取指定任务的结果(仅当任务完成时可用)Args:task_id: 任务IDReturns:任务结果信息"""task = tasks.get(task_id)if not task:raise HTTPException(status_code=404, detail="任务ID不存在")if task["status"] != "completed":# 任务未完成,返回当前状态和错误信息return {"status": task["status"],"progress": task["progress"],"message": task["message"],"error": "任务未完成,无法获取结果"}# 返回完整的任务结果return {"task_id": task_id,"status": "completed","total_time": task["total_time"],"success_count": task["success_count"],"fail_count": task["fail_count"],"total_files": task["total_files"],"input_path": task["input_path"],"is_single_file": task["is_single_file"],"output_dir": task["output_dir"],"parameters": task["parameters"],"annotations": task["annotations"],"message": "处理完成"}@app.get("/download/{task_id}/{filename:path}")
async def download_file(task_id: str, filename: str):"""下载任务结果文件Args:task_id: 任务IDfilename: 要下载的文件名Returns:文件响应"""task = tasks.get(task_id)if not task or task["status"] != "completed":raise HTTPException(status_code=400, detail="任务未完成或不存在")output_dir = task["output_dir"]file_path = os.path.join(output_dir, filename)if not os.path.isfile(file_path):raise HTTPException(status_code=404, detail="文件不存在")# 返回文件内容供客户端下载return FileResponse(path=file_path,filename=os.path.basename(filename),media_type="application/octet-stream")@app.websocket("/ws/video_detection")
async def detect_video_websocket(websocket: WebSocket):"""通过WebSocket处理实时视频帧检测(适用于实时视频流)Args:websocket: WebSocket连接对象"""await websocket.accept()  # 接受WebSocket连接logging.info("WebSocket 连接已建立。")try:while True:# 接收客户端发送的数据data_str = await websocket.receive_text()data = json.loads(data_str)# 解析请求参数model_name = data['model_name']base64_str = data['image_base64']conf = data.get('conf', 60) / 100.0  # 默认置信度阈值为0.6iou = data.get('iou', 65) / 100.0   # 默认IOU阈值为0.65# 加载YOLO模型model = YOLO(model_name)# 解码Base64格式的图像try:header, encoded_data = base64_str.split(",", 1)if not encoded_data:logging.warning("接收到空的Base64数据,已跳过。")continueimage_bytes = base64.b64decode(encoded_data)if not image_bytes:logging.warning("Base64解码后数据为空,已跳过。")continueexcept (ValueError, TypeError, IndexError) as e:logging.warning(f"Base64解析失败: {e},已跳过。")continue# 将字节数据转换为OpenCV图像image_cv2 = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR)if image_cv2 is None:logging.warning("图像解码失败,已跳过。")continue# 执行目标检测并过滤结果(如果指定了目标类别)target_classes = data.get('target_classes')target_set = set(target_classes.split(',')) if target_classes else Noneresults = model(image_cv2, conf=conf, iou=iou, verbose=False)all_boxes = results[0].boxesfiltered_boxes = []if target_set:# 过滤出目标类别for box in all_boxes:cls_name = model.names[int(box.cls)]if cls_name in target_set:filtered_boxes.append(box)else:# 保留所有类别filtered_boxes = list(all_boxes)# 绘制标注(调用之前定义的函数)annotated_image = draw_annotations(image_cv2, filtered_boxes, model.names)# 将标注后的图像编码为Base64格式_, buffer = cv2.imencode('.jpg', annotated_image)result_base64 = base64.b64encode(buffer).decode("utf-8")# 将结果发送回客户端await websocket.send_json({"image_base64": f"data:image/jpeg;base64,{result_base64}"})except WebSocketDisconnect:# 客户端断开连接logging.info("WebSocket 客户端断开连接。")except Exception as e:# 处理WebSocket异常error_message = f"WebSocket 处理错误: {type(e).__name__}"logging.error(f"{error_message} - {e}")await websocket.close(code=1011, reason=error_message)if __name__ == '__main__':import uvicorn# 打印API启动信息和可用端点print("启动YOLO目标检测API服务...")print("支持的API端点:")print("  POST /detect - 启动检测任务")print("  GET /status/<task_id> - 获取任务状态")print("  GET /results/<task_id> - 获取结果")print("  GET /download/<task_id>/<filename> - 下载结果文件")print("  WS /ws/video_detection - 实时视频帧检测")# 启动FastAPI应用uvicorn.run(app, host="0.0.0.0", port=5000)

核心参数说明

参数名

类型

说明

默认值

input_path

String

输入路径(支持单张图片或文件夹)

无(必填)

output_dir

String

结果输出文件夹路径

"demo"

model_path

String

YOLO 模型路径

"yolo11n.pt"

device

String

运行设备("cpu" 或 "0")

自动选择

conf

Float

置信度阈值(0-1)

0.25

iou

Float

交并比阈值(0-1)

0.7

target_classesString目标类别(逗号分隔的字符串)

 

部署与使用

1. 安装依赖

pip install fastapi uvicorn pydantic ultralytics opencv-python numpy pillow torch base64

2. 启动服务

ython yolo_api.py

服务启动后会监听本地 5000 端口,输出如下:

启动YOLO目标检测API服务...

支持的API端点:

POST /detect - 启动检测任务并返回结果

GET /status/<task_id> - 获取任务状态

GET /results/<task_id> - 获取结果(与/detect相同)

GET /download/<task_id>/<filename> - 下载结果文件

* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

3. 使用 Postman 调用 API

处理图片
  • 请求 URL: http://localhost:5000/detect
  • 请求方法: POST
  • 请求体:

{

    "input_path": "C:/Users/HUAWEI/Desktop/yoloapi/tupian/",  // 待检测图片文件夹

    "output_dir": "C:/Users/HUAWEI/Desktop/yoloapi/output",  // 结果输出文件夹

    "model_path": "yolo11n.pt",  // 模型路径(默认会自动下载)

    "device": null,  // 自动选择设备(也可指定"cpu"或"0")

    "conf": 0.1,    // 置信度阈值(越高越严格,默认0.25)

    "iou": 0.6,     // 交并比阈值(越高越严格,默认0.7)

    "target_classes" : "car"

}

4. 返回结果示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90117.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90117.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90117.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

周志华《机器学习导论》第13章 半监督学习

目录 1. 未标记样本 2. 生成式方法 高斯混合EM 3. 半监督SVM 存在未标记样本的SVM变形 4. 图半监督学习 对图权值迭代矩阵计算 5. 基于分歧的方法 多视图协同训练 6. 半监督聚类 k-means的条件变形 6.1 Constrained k-means 利用“必连”与 “勿连”约束 6.2 Constra…

消息推送功能设计指南:精准触达与用户体验的平衡之道

消息推送功能设计指南&#xff1a;精准触达与用户体验的平衡之道消息推送是平台与用户保持连接的重要桥梁&#xff0c;既能及时传递重要资讯&#xff0c;又能唤醒沉睡用户、提升活跃度。然而&#xff0c;推送功能若设计不当&#xff0c;可能变成 “信息骚扰”&#xff0c;导致用…

CanOpen--SDO 数据帧分析

CanOpen--SDO 数据帧分析1 介绍1.1 概述1.2 主站与从站2 数据帧详细分析2.1 主站发送的请求帧 (Client → Server)2.2 从站响应的确认帧 (Server → Client)成功数据帧内容示例错误帧2.3 命令字2.4 小端格式&#xff1a;低字节在前3 其他示例60FF index 发送 数值 1000 数据帧分…

Day20-二叉树基础知识

二叉树&#xff08;Binary Tree&#xff09;是一种每个节点最多有两个子节点的树形数据结构&#xff0c;这两个子节点分别称为左子节点和右子节点。二叉树是计算机科学中最基础、最常用的树结构之一&#xff0c;广泛应用于搜索、排序、表达式解析等领域&#xff01; 核心特点 …

示波器探头接口类型与PINTECH品致探头选型指南

一、示波器探头接口类型及技术特点1. BNC接口&#xff1a;通用型主流标准- 优势&#xff1a;75%以上示波器标配接口&#xff0c;具备阻抗匹配灵活&#xff08;50Ω/1MΩ&#xff09;、插拔稳定、抗干扰性强等特点。 - 应用场景&#xff1a;适用于大多数示波器&#xff08;如Le…

Spring之【Bean工厂后置处理器】

目录 BeanFactoryPostProcessor BeanDefinitionRegistryPostProcessor 使用一下Bean工厂后置处理器 定义包扫描范围 定义一个组件Bean 定义一个普通的类 自定义一个组件类实现Bean工厂后处理器 测试类 BeanFactoryPostProcessor 该接口是Spring提供的扩展点之一是一个…

【C++】第十八节—一文万字详解 | map和set的使用

嗨&#xff0c;我是云边有个稻草人&#xff0c;与你分享C领域专业知识(*^▽^*) 《C》本篇文章所属专栏—持续更新中—欢迎订阅— 目录 一、序列式容器和关联式容器 二、set系列的使用 2.1 set和multiset参考⽂档 2.2 set类的介绍 2.3 set的构造和迭代器 2.4 set的增删查…

Java 大视界 -- Java 大数据在智能交通自动驾驶车辆与周边环境信息融合与决策中的应用(357)

Java 大视界 -- Java 大数据在智能交通自动驾驶车辆与周边环境信息融合与决策中的应用&#xff08;357&#xff09;引言&#xff1a;正文&#xff1a;一、Java 构建的环境信息融合架构1.1 多传感器数据实时关联1.2 动态障碍物轨迹预测二、Java 驱动的决策系统设计2.1 紧急决策与…

单细胞转录组学+空间转录组的整合及思路

一、概念 首先还是老规矩&#xff0c;处理一下概念问题&#xff0c;好将之后的问题进行分类和区分 单细胞转录组&#xff1a;指在单个细胞水平上对转录组&#xff08;即细胞内所有转录出来的 RNA&#xff0c;主要是 mRNA&#xff09;进行研究的学科或技术方向&#xff0c;核心…

用Python实现神经网络(五)

这一节告诉你如何用TensorFlow实现全连接网络。安装 DeepChem这一节&#xff0c;你将使用DeepChem 机器学习工具链进行实验在网上可以找到 DeepChem详细安装指导。Tox21 Dataset作为我们的建模案例研究&#xff0c;我们使用化学数据库。毒理学家很感兴趣于用机器学习来预测化学…

ReasonFlux:基于思维模板与分层强化学习的高效推理新范式

“以结构化知识压缩搜索空间&#xff0c;让轻量模型实现超越尺度的推理性能” ReasonFlux 是由普林斯顿大学与北京大学联合研发的创新框架&#xff08;2025年2月发布&#xff09;&#xff0c;通过 结构化思维模板 与 分层强化学习&#xff0c;显著提升大语言模型在复杂推理任务…

PHP与Web页面交互:从基础表单到AJAX实战

文章目录 PHP与Web页面交互:从基础到高级实践 1. 引言 2. 基础表单处理 2.1 HTML表单与PHP交互基础 2.2 GET与POST方法比较 3. 高级交互技术 3.1 AJAX与PHP交互 3.2 使用Fetch API进行现代AJAX交互 4. 文件上传处理 5. 安全性考量 5.1 常见安全威胁与防护 5.2 数据验证与过滤 …

OpenCV基本的图像处理

参考资料&#xff1a; 参考视频 视频参考资料:链接: https://pan.baidu.com/s/1_DJTOerxpu5_dSfd4ZNlAA 提取码: 8v2n 相关代码 概述&#xff1a; 因为本人是用于机器视觉的图像处理&#xff0c;所以只记录了OpenCV的形态学操作和图像平滑处理两部分 形态学操作&#xff1a;…

Git 与 GitHub 学习笔记

本文是一份全面的 Git 入门指南,涵盖了从环境配置、创建仓库到日常分支管理和与 GitHub 同步的全部核心操作。 Part 1: 初始配置 (一次性搞定) 在开始使用 Git 之前,需要先配置好你的电脑环境。(由于网络的原因,直接使用https的方式拉取仓库大概率是失败的,故使用ssh的方…

文件系统-文件存储空间管理

文件存储空间管理的核心是空闲块的组织、分配与回收&#xff0c;确保高效利用磁盘空间并快速响应文件操作&#xff08;创建、删除、扩展&#xff09;。以下是三种主流方法&#xff1a;1. 空闲表法&#xff08;连续分配&#xff09;原理&#xff1a;类似内存动态分区&#xff0c…

python爬虫实战-小案例:爬取苏宁易购的好评

一、项目背景与价值1 为什么爬取商品好评&#xff1f; 消费者洞察&#xff1a;分析用户真实反馈&#xff0c;了解产品优缺点 市场研究&#xff1a;监测竞品评价趋势&#xff0c;优化产品策略二.实现代码from selenium import webdriver from selenium.webdriver.edge.options i…

Spring Boot环境搭建与核心原理深度解析

一、开发环境准备 1.1 工具链选择 JDK版本&#xff1a;推荐使用JDK 17&#xff08;LTS版本&#xff09;&#xff0c;与Spring Boot 3.2.5完全兼容&#xff0c;支持虚拟线程等JDK 21特性可通过配置启用构建工具&#xff1a;Maven 3.8.6&#xff08;配置阿里云镜像加速依赖下载…

Java自动拆箱机制

在黑马点评项目中&#xff0c;提到了一个细节&#xff0c;就是Java的自动拆箱机制&#xff0c;本文来简单了解一下。Java 的​​自动拆箱机制&#xff08;Unboxing&#xff09;​​是一种编译器层面的语法糖&#xff0c;用于简化​​包装类对象​​&#xff08;如 Integer、Boo…

哈希算法(Hash Algorithm)

哈希算法&#xff08;Hash Algorithm&#xff09;是一种将任意长度的数据映射为固定长度的哈希值&#xff08;Hash Value&#xff09;的算法&#xff0c;广泛应用于密码学、数据完整性验证、数据结构&#xff08;如哈希表&#xff09;和数字签名等领域。&#x1f9e0; 一、哈希…

黑马点评使用Apifox进行接口测试(以导入更新店铺为例、详细图解)

目录 一、前言 二、手动完成接口测试所需配置 三、进行接口测试 一、前言 在学习黑马点评P39实现商铺缓存与数据库的双写一致课程中&#xff0c;老师使用postman进行了更新店铺的接口测试。由于课程是22年的&#xff0c;按照我从24年JavaWebAI课程所学习使用的Apifox内部其实…