背景介绍
这是一个 简单的人脸识别项目,用 FastApi 在本地实现,使用预训练模型,直接可用。
新方案比之前的FaceNet强太多了,甚至不用数据增强等操作,就可以识别戴眼镜、不戴眼镜、歪着的人脸等。
充分证明了选型的重要性,选对了模型方案,效果直线上升。
细节说明
1、model.py 的 align_face 方法使用的是 仿射变换,不可以换成 透视变换哦,尤其是 ArcFace 这种场景,仿射变换更合适,原因如下:
①ArcFace 训练是基于仿射对齐后的人脸图像(五点坐标 + 仿射对齐 +输出112x112),改变对齐方式容易导致提取的 embedding 无法匹配;
②人脸在拍摄中的角度变化通常较小,仿射变换可以很好地处理这类刚性变换(左右偏转,微小仰头低头,轻微比例失衡等);
③透视变换自由度太高,容易引入图像畸变,对 ArcFace 这种要求人脸标准姿态输入的模型来说可能导致 向量化的不稳定;
可改善
1、可增加活体检测功能;
项目结构
在这里插入图片描述
核心代码都放下面了
src/face_rec/model.py

# src/face_rec/model.py
# RetinaFace + ArcFace + 五点对齐;使用cpuimport cv2, os
import numpy as np
from PIL import Image
from datetime import datetime# insightface 库:arcface 和 retinaface
from insightface.app import FaceAnalysisclass FaceRecModel:def __init__(self, root_dir):"""初始化 RetinaFace 检测器 + ArcFace 特征提取器"""self.root_dir = root_dir# FaceAnalysis集成了 人脸检测器(RetinaFace)、人脸识别器(ArcFace)、关键点提取器(5点、106点)# providers:这是 onnxruntime 的后端执行器选择,代表你想使用什么设备运行模型self.app = FaceAnalysis(providers=['CPUExecutionProvider'])  # 也可以用 GPUExecutionProvider、TensorrtExecutionProvider# prepare是对 FaceAnalysis 进行初始化配置,必须调用,否则模型无法运行。# det_size=(320, 320):会影响 检测速度和检测精度,太小-快但容易漏检小人脸;太大-准但慢,资源消耗大;# 实测 det_size=(320, 320) 比较好用self.app.prepare(ctx_id=-1, det_size=(320, 320))  # ctx_id=-1是CPU,0是GPU,det_size是检测尺寸(宽,高)# 如果你输入的图像尺寸不是 (320, 320),会怎么样?# insightface 会自动 resize 成指定的 det_size(保持长宽比)来检测。原图尺寸不会影响使用,只是检测过程会先缩放成这个尺寸,然后再还原人脸框到原图坐标。def get_image_face_vector(self, pil_img, name=None):"""检测人脸,返回对齐后的最大人脸图像及其特征向量。:param pil_img: PIL.Image RGB 图像:param name: 用户名(保存图片时使用):return: 对齐后的 PIL.Image(或None),对齐人脸保存路径(或None),特征向量(或None)"""# 将 PIL 图像转成 numpy RGB 数组np_img = np.array(pil_img)# faces 是一个人脸列表,每个元素是一个 insightface.Face 对象(可以理解为一个人脸的“数据包”),包含了检测框、关键点、embedding 特征等信息。# app.get() 方法会执行人脸检测、特征提取。faces = self.app.get(np_img)if len(faces) == 0:print("[INFO] 未在图像中检测到人脸,无法进行对齐和特征提取。")return None, None, None# 选择最大面积人脸face = max(faces, key=lambda x: x.bbox[2] * x.bbox[3])  # W*H=人脸框面积# 五点关键点坐标 (5,2) numpy数组kps = face.kps.astype(np.float32)  # 左眼,右眼,鼻子,左嘴角,右嘴角# 仿射对齐人脸,输出 112x112 RGB 图像aligned_face_np = self.align_face(np_img, kps)  # align_face 是自定义函数,返回 numpy 数组# 将对齐后的 numpy 图像转回 PIL 图像aligned_pil = Image.fromarray(aligned_face_np)# 保存对齐后图像save_dir = os.path.join(self.root_dir, 'aligned_faces')os.makedirs(save_dir, exist_ok=True)# 命名方式:name_时间戳.jpg 或 timestamp.jpgtimestamp = datetime.now().strftime("%Y%m%d_%H%M%S")filename = f"{name}_{timestamp}_insert.jpg" if name else f"face_{timestamp}_search.jpg"save_path = os.path.join(save_dir, filename)aligned_pil.save(save_path)print(f"[INFO] 已保存对齐人脸: {save_path}")# --- 在这里整合特征提取逻辑 ---# 优先使用 app.get() 已经提取的 embedding,因为它是在检测阶段就完成了。并且通常是针对原始图像的更鲁棒的特征。# 如果 face.embedding 存在且不为 None,就直接使用它。if hasattr(face, 'embedding') and face.embedding is not None:embedding = face.embedding.astype(np.float32)print("[INFO] 已成功提取人脸特征。", embedding.mean())else:# 如果 face 对象中没有 embedding,说明 app.get() 未能成功提取特征。# 这可能是由于识别模型加载失败、图像质量问题或其他内部原因。print("[ERROR] FaceAnalysis.get() 未能为检测到的人脸提取特征。")print("请检查 FaceAnalysis 的初始化配置和输入图像质量。")embedding = None # 无法获取特征,返回 Nonereturn aligned_pil, save_path, embeddingdef align_face(self, np_img, landmark5):"""使用五点关键点仿射变换对齐人脸,输出 112x112 RGB 图像(ArcFace标准输入):param np_img: numpy RGB 图像:param landmark5: numpy array (5,2) 左眼,右眼,鼻子,左嘴角,右嘴角:return: 对齐后 112x112 RGB numpy 图像"""# ArcFace标准5点模板坐标,单位像素src = np.array([[38.2946, 51.6963],[73.5318, 51.5014],[56.0252, 71.7366],[41.5493, 92.3655],[70.7299, 92.2041]], dtype=np.float32)dst = landmark5.astype(np.float32)# 计算仿射变换矩阵: 计算 从 dst(检测的人脸五点) 到 src(ArcFace标准模板五点) 的 仿射变换矩阵(2x3)# 使用 LMEDS(最小中值平方)方法计算仿射矩阵,比普通最小二乘法更稳健(可以抵抗部分错误点)。tform = cv2.estimateAffinePartial2D(dst, src, method=cv2.LMEDS)[0]# 使用仿射矩阵 tform 对整张人脸图像做变换(对齐),输出大小为 (112, 112) 的人脸图像# borderValue=0:如果变换后图像边界有空白部分,用黑色像素(值为0)填充。aligned = cv2.warpAffine(np_img, tform, (112, 112), borderValue=0)return aligned

src/face_rec/vector.py

# src/face_rec/vector.py
# 使用 FAISS + cosine 相似度做人脸特征向量的存储与检索import os
import pickle
import numpy as np
import faiss  # Facebook AI 相似度搜索库,适合处理大规模向量比对class VectorService:def __init__(self, root_dir):# 保存向量的目录self.root_dir = os.path.join(root_dir, "embeddings")if not os.path.exists(self.root_dir):os.makedirs(self.root_dir)self.dims = 512  # ArcFace 输出是 512 维向量# 特征向量文件(以 pickle 存储)self.embedding_file = os.path.join(self.root_dir, "emb.pkl")if os.path.exists(self.embedding_file):# 如果文件存在就加载已有向量self.embeddings = np.asarray(self.load_pickle_data(self.embedding_file)).astype('float32')else:# 否则初始化为一个全0向量(避免空索引时报错)self.embeddings = np.zeros((1, self.dims), dtype='float32')# 初始化 FAISS 索引:使用 HNSW(图结构)+ cosine 相似度(归一化后使用内积)# HNSW:Hierarchical Navigable Small World 一种高效的图结构索引算法,适合大规模、近似最近邻查找(Approximate Nearest Neighbor, ANN)# faiss.METRIC_INNER_PRODUCT:表示使用 内积(dot product) 作为距离度量方式。但这并不是欧氏距离,而是为了计算 cosine 相似度self.face_index = faiss.index_factory(self.dims, 'HNSW16', faiss.METRIC_INNER_PRODUCT)# 如果已有向量,归一化后加入索引中if self.embeddings.shape[0] > 0:faiss.normalize_L2(self.embeddings)  # L2归一化后,求内积就是cosine 余弦相似度self.face_index.add(self.embeddings)# ID 到用户姓名的映射表self.idx_2_name_file = os.path.join(self.root_dir, 'id2name.pkl')if os.path.exists(self.idx_2_name_file):self.idx_2_name = self.load_pickle_data(self.idx_2_name_file)else:self.idx_2_name = {}# 当前用户索引起始值(从已有数量+1开始)self.user_idx = len(self.idx_2_name) + 1def add_embedding(self, vector, name):"""添加一个人脸向量及其对应用户名"""vector = np.asarray(vector, dtype='float32').reshape((1, self.dims))faiss.normalize_L2(vector)  # cosine相似度必须先归一化# 更新内存中的向量数组self.embeddings = np.concatenate([self.embeddings, vector], axis=0)self.face_index.add(vector)  # 加入FAISS索引# 保存向量到文件self.save_pickle_data(self.embedding_file, self.embeddings)# 记录用户索引与名字的对应关系self.idx_2_name[self.user_idx] = nameself.user_idx += 1self.save_pickle_data(self.idx_2_name_file, self.idx_2_name)def search(self, vector, thred=0.4):"""给定一个向量,返回最相似的用户名(如果相似度超过阈值):param vector: 512维特征向量:param thred: 相似度阈值,越高越严格(默认0.4):return: 最相似用户名 或 None"""vector = np.asarray(vector, dtype='float32').reshape((1, self.dims))faiss.normalize_L2(vector)# 搜索与该向量最接近的索引。np.ndarray prob.shape=(1, 1), np.ndarray idx.shape=(1, 1)prob, idx = self.face_index.search(vector, 1)  # 返回前1个最相似结果prob = prob[0, 0]  # 相似度(cosine)idx = idx[0, 0]    # 对应索引值print(f"最大相似度:{prob}(判定阈值:{thred})")if prob < thred:return Nonereturn self.idx_2_name.get(idx, None)@staticmethoddef load_pickle_data(path):"""从指定路径加载pickle对象"""# 因为 pickle 模块在保存/读取 Python 对象时,是按二进制格式编码的,不是普通文本。with open(path, 'rb') as r:  # 以“二进制”的方式打开一个文件,只读return pickle.load(r)@staticmethoddef save_pickle_data(path, data):"""将对象保存到pickle文件"""# 因为 pickle 模块在保存/读取 Python 对象时,是按二进制格式编码的,不是普通文本。with open(path, 'wb') as w:  # 以“二进制”的方式打开一个文件,可写pickle.dump(data, w)

src/face_rec/util.py

# src/face_rec/util.py
# 对齐仍使用五点关键点,图像增强接口保留,增强亮度、对比度、锐度,确保模型识别效果更稳定import numpy as np
from PIL import ImageEnhancedef preprocess_image(pil_image):'''图像预处理函数(可选):增强亮度、对比度、锐度image (PIL.Image.Image): 输入的 PIL 图像对象:np.ndarray: 增强后的图像,格式为 NumPy 数组,可用于 OpenCV 或模型输入'''# 亮度增强:增强到原始亮度的 1.3 倍enhancer = ImageEnhance.Brightness(pil_image)image_bright = enhancer.enhance(1.3)# 对比度增强:增强到原始对比度的 1.2 倍enhancer_contrast = ImageEnhance.Contrast(image_bright)image_contrast = enhancer_contrast.enhance(1.2)# 锐度增强:增强到原始锐度的 2.0 倍enhancer_sharp = ImageEnhance.Sharpness(image_contrast)image_sharpened = enhancer_sharp.enhance(2.0)# 将 PIL 图像转换为 NumPy 格式(OpenCV 可用)image_sharpened = np.array(image_sharpened)return image_sharpened

src/face_rec/app.py

# src/face_rec/app.pyimport os
import io
import base64
from fastapi import FastAPI, File, Form, UploadFile, Request
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
from fastapi.templating import Jinja2Templates
from PIL import Image, ImageOpsfrom .model import FaceRecModel                # 引入人脸识别模型封装
from .vector import VectorService              # 引入向量搜索服务
from .util import preprocess_image             # 可选:图像预处理(增强)# 获取静态资源目录(如 模型文件、向量文件等) C:\baidu_sync\BaiduSyncdisk\PythonFiles\CV_liuyuan\InsightFace\src\face_rec\static
__static_dir_path__ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")# 创建 FastAPI 应用
app = FastAPI()# 指定 FastAPI 中 Jinja2 模板文件目录(用于返回 HTML 页面) C:\baidu_sync\BaiduSyncdisk\PythonFiles\CV_liuyuan\InsightFace\src\face_rec\templates
# FastAPI 使用 Jinja2 来支持 HTML 模板渲染(跟 Flask 类似)
templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "templates"))# 实例化加载人脸识别模型与向量库
model = FaceRecModel(root_dir=__static_dir_path__)
vector = VectorService(root_dir=__static_dir_path__)# 网页标签图片
@app.get("/favicon.ico")
async def favicon():return FileResponse(os.path.join(__static_dir_path__, "favicon.ico"))# 首页:GET / 或 /index,返回欢迎信息
@app.get("/", response_class=HTMLResponse)
@app.get("/index", response_class=HTMLResponse)
# 异步的处理函数(协程)
async def index(request: Request):return HTMLResponse("欢迎使用简易人脸检索系统!")# GET 上传页面:返回上传图片的 HTML 页面
@app.get("/face/image/upload", response_class=HTMLResponse)
async def upload_get(request: Request):return templates.TemplateResponse("upload.html", {"request": request})# POST 上传接口:上传用户图像并提取特征,存入向量库
@app.post("/face/image/upload")
async def upload_post(name: str = Form(...),          # 表单中提供的人名(作为身份标签)file: UploadFile = File(...)    # 上传的图片文件 (此时 file 是一个 UploadFile 对象,它是一个 异步文件类,用于处理客户端上传的文件)
):                                  # HTML 表单的 name 名称和后端参数名一致;后端用 Form(...) 处理文本、用 File(...) 处理文件# 读取上传文件内容并转为 PIL 图像# await 表示“等待它完成”并获取返回值,必须在 async def 里使用contents = await file.read()  # file.read() 是一个异步操作(coroutine),会从上传的文件中读取所有字节内容(返回 bytes)img = Image.open(io.BytesIO(contents)).convert("RGB")# 处理图片方向信息(EXIF)img = ImageOps.exif_transpose(img)# 可选预处理(增强亮度/对比度/锐度)# img = preprocess_image(img)# 检测并裁剪人脸图像,保存文件,提取人脸特征向量img, save_path, embedding = model.get_image_face_vector(img, name)if img is None:return templates.TemplateResponse("upload.html", {"request": {}, "msg": "未检测到人脸区域!"})if embedding is None:return templates.TemplateResponse("upload.html", {"request": {}, "msg": "人脸特征提取失败!"})# 将特征向量存入数据库vector.add_embedding(embedding, name)return JSONResponse({"code": 0, "msg": "人脸添加成功!"})# GET 检索页面:返回搜索页面的 HTML 页面
@app.get("/face/image/search", response_class=HTMLResponse)
async def search_get(request: Request):return templates.TemplateResponse("search.html", {"request": request})# POST 检索接口:上传图像 -> 提取特征 -> 搜索库中最相似用户
@app.post("/face/image/search")
async def search_post(file: UploadFile = File(...)):contents = await file.read()img = Image.open(io.BytesIO(contents)).convert("RGB")# 处理图片方向信息(EXIF)img = ImageOps.exif_transpose(img)# 可选预处理(增强亮度/对比度/锐度)# img = preprocess_image(img)# 检测并裁剪人脸图像,保存文件,提取人脸特征向量img, save_path, embedding = model.get_image_face_vector(img)if img is None:return templates.TemplateResponse("search.html", {"request": {}, "msg": "未检测到人脸区域!"})if embedding is None:return templates.TemplateResponse("search.html", {"request": {}, "msg": "人脸特征提取失败!"})# 在向量库中进行相似度匹配name = vector.search(embedding)if name is None:return templates.TemplateResponse("search.html", {"request": {}, "msg": "人脸未匹配到用户!"})return JSONResponse({"code": 0, "msg": "search success!", "name": name})# POST 检测接口(用于 API 接口)接收 base64 图像,输出识别结果
@app.post('/face/image/detect')
async def detect_post(request: Request):data = await request.json()img_b64 = data.get('img')if img_b64 is None:return JSONResponse({'code': 202, 'msg': '必须提供 img 参数!'})# 解码 base64 图像并转换为 PIL 图像img_data = base64.b64decode(img_b64)img = Image.open(io.BytesIO(img_data)).convert("RGB")# 处理图片方向信息(EXIF)img = ImageOps.exif_transpose(img)# 可选预处理(增强亮度/对比度/锐度)# img = preprocess_image(img)# 检测并裁剪人脸图像,保存文件,提取人脸特征向量img, save_path, embedding = model.get_image_face_vector(img)if img is None:return JSONResponse({'code': 203, 'msg': '未检测到人脸区域!'})if embedding is None:return JSONResponse({'code': 204, 'msg': '人脸特征提取失败!'})name = vector.search(embedding)if name is None:return JSONResponse({'code': 205, 'msg': '人脸未匹配到用户!'})return JSONResponse({'code': 200, 'msg': 'search success!', 'name': name})

test/main.py

# test/main.py
# 项目入口文件,用于启动 FastAPI 服务(人脸识别 Web 接口)import warnings
warnings.filterwarnings('ignore')  # 忽略所有警告信息,保持输出干净import os
import sys
import uvicorn  # 用于运行 FastAPI 的 ASGI 服务器
# ASGI(Asynchronous Server Gateway Interface)是 Python 的一种 Web 应用服务器接口规范,是 WSGI 的升级版,用于支持异步编程。
# ASGI 是连接 Web 框架(如 FastAPI)和底层服务器(如 Uvicorn)的桥梁接口标准,支持异步请求处理。# 避免 OpenMP 多线程库冲突(常用于 PyTorch 或 NumPy 内部),防止报错
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"# 将 src 目录添加到模块导入路径,确保可以导入 src 下的自定义模块
# 例如:C:\baidu_sync\BaiduSyncdisk\PythonFiles\CV_liuyuan\InsightFace\src
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))# 导入 FastAPI 应用实例(app)供 uvicorn 启动
from src.face_rec.app import app# 运行服务
if __name__ == "__main__":# 启动 FastAPI 应用,监听所有 IP,端口为 9999,日志级别为 infouvicorn.run(app, host="0.0.0.0", port=9999, log_level="info")  # 正常启动# uvicorn.run(app, host="0.0.0.0", port=9999, log_level="info", reload=False)  # 调试启动(debug)

test/client.py

# test/client.py
"""客户端测试代码:向 FastAPI 服务端发送 base64 编码的人脸图像,接收识别结果"""import base64
from typing import Optional
import requests
import cv2
import numpy as np
from io import BytesIO
from pathlib import Path
from PIL import Image
from datetime import datetimedef fetch_person_name(img: np.ndarray) -> Optional[str]:"""给定一张图像(OpenCV格式 BGR),向服务端发送请求,返回识别结果:param img: OpenCV 格式图像(BGR):return: 返回识别出的姓名字符串,或 None"""# 转换 OpenCV (BGR) -> PIL (RGB)pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))# 将图像保存为 PNG 到内存buffer = BytesIO()pil_img.save(buffer, format='PNG')img_bytes = buffer.getvalue()# 编码成 base64 字符串img_b64 = base64.b64encode(img_bytes).decode('utf-8')# 构造 JSON 请求体payload = {'img': img_b64}headers = {'Content-Type': 'application/json'}try:# 向本地服务发送 POST 请求(FastAPI)response = requests.post('http://127.0.0.1:9999/face/image/detect', json=payload, headers=headers)if response.status_code == 200:result = response.json()if result.get('code') == 200:return result.get('name')else:print(f"[服务返回] 识别失败: {result}")else:print(f"[HTTP错误] 状态码: {response.status_code}")except Exception as e:print(f"[异常] 请求失败: {e}")return Noneif __name__ == '__main__':# 加载测试图像img_path = Path('..') / 'data' / 'img0.jpeg'img = cv2.imread(str(img_path))if img is None:print(f"图像读取失败: {img_path}")exit(1)  # 程序异常退出,错误码为 1# 获取识别结果name = fetch_person_name(img)print(f"识别结果: {name}")# 画图、显示(如有识别结果,在图像上绘制姓名)if name:cv2.putText(img, name, (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 2)  # BGR,红色255else:cv2.putText(img, "No Match", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 255), 2)cv2.imshow('Face Recognition Result', img)cv2.waitKey(0)  # 等待任意键cv2.destroyAllWindows()# The function is not implemented
# OpenCV 安装缺少图形界面后端支持,默认安装的是一个轻量级的版本,
# 不包含 GUI (图形用户界面) 功能,例如 cv2.imshow()、cv2.waitKey() 等。这在服务器环境或不需要显示图像的场景下是常见的。

src/face_rec/templates/upload.html

<!-- upload.html 人脸图像录入表单页的模板 --><!DOCTYPE html>
<html lang="zh-CN">  <!-- 设置页面语言为中文(简体) --><head><meta charset="UTF-8">  <!-- 设置网页的字符编码为 UTF-8,支持中文 --><title>人脸录入</title>  <!-- 网页标题:浏览器标签页上显示 -->
</head><body><h1>员工人脸打卡系统(人脸录入)</h1><!-- 若后端传递了 msg(通常是错误或提示信息),则显示为红色加粗字体 -->{% if msg %}<strong style="color:red">{{msg}}</strong>{% endif %}<!-- 上传表单 --><!-- method="POST" 表示表单提交方式为 POST --><!-- action="/face/image/upload" 表示提交到后端对应的 Flask 路由 --><!-- enctype="multipart/form-data" 表示表单中包含文件上传字段(如图像) --><form method="POST" action="/face/image/upload" enctype="multipart/form-data"><!-- 文本输入框:输入姓名 -->姓名:<input type="text" name="name"/> <br/><br/><!-- 文件选择框:上传图像文件 -->图像:<input type="file" name="file"/> <br/><br/><!-- 提交按钮 --><input type="submit" name="submit" value="录入"/></form>
</body>
</html>

src/face_rec/templates/search.html

<!--人脸图像检索表单页的模板-->
<!DOCTYPE html>
<html lang="zh-CN">  <!-- 设置页面语言为中文(简体) --><head><meta charset="UTF-8">  <!-- 设置网页的字符编码为 UTF-8,支持中文 --><title>人脸检索</title>  <!-- 网页标题:浏览器标签页上显示 -->
</head><body><h1>员工人脸打卡系统(人脸检索)</h1><!-- 若后端传递了 msg(通常是错误或提示信息),则显示为红色加粗字体 -->{% if msg %}<strong style="color:red">{{msg}}</strong>{% endif %}<!-- 上传表单 --><!-- method="POST" 表示表单提交方式为 POST --><!-- action="/face/image/search" 表示提交到后端对应的 Flask 路由 --><!-- enctype="multipart/form-data" 表示表单中包含文件上传字段(如图像) --><form method="POST" action="/face/image/search" enctype="multipart/form-data"><!-- 文件选择框:上传图像文件 -->图像:<input type="file" name="file"/> <br/><br/><!-- 提交按钮 --><input type="submit" name="submit" value="检索"/></form>
</body>
</html>

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

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

相关文章

App Inventor 2 使用 MaterialIcons 图标字体,快捷展示专业图标

平时布局的话&#xff0c;如果要使用图标&#xff0c;一般需要去找 png 图片&#xff0c;且透明背景的。如果需要根据不同常见图标进行变色的话&#xff0c;就需要准备多张不同样式的图标&#xff0c;还要考虑图片的分辨率等等因素&#xff0c;非常的麻烦。 这时&#xff0c;如…

C语言——关于指针(逐渐清晰版)

为了更好地理解本篇文章的知识内容&#xff0c;读者可以将以下文章作为补充知识进行阅读 &#xff1a; C语言————原码 补码 反码 &#xff08;超绝详细解释&#xff09;-CSDN博客 C语言————二、八、十、十六进制的相互转换-CSDN博客 C语言————斐波那契数列的理解…

SVG 在线编辑器

SVG 在线编辑器 引言 随着互联网技术的发展&#xff0c;矢量图形在网页设计和数据可视化中扮演着越来越重要的角色。SVG&#xff08;可缩放矢量图形&#xff09;因其文件小、无限缩放不模糊的特性&#xff0c;成为了网页设计中常用的图形格式。SVG 在线编辑器的出现&#xff0c…

libpostproc 已经从 ffmpeg 中移除,导致编译 ffmpeg 时没有 libpostproc

今天编译 ffmpeg 时突然发现 libpostproc 不见了&#xff0c;-enable-postproc 也变成了非法的选项。用搜索引擎搜索相关信息找不到一点&#xff0c;于是去 github 看。 从提交记录可以看到 libpostproc 已经被移除了 链接 主线中已经看不到了 libpostproc 这个目录了

基于 Dell PowerEdge T440 搭建的 Proxmox VE 配置 RTX 3060 显卡直通虚拟机、切换直通

基于 Dell PowerEdge T440 搭建的 Proxmox VE 配置 RTX 3060 显卡直通虚拟机、切换直通 文章目录 基于 Dell PowerEdge T440 搭建的 Proxmox VE 配置 RTX 3060 显卡直通虚拟机、切换直通 1. 前言 2. 前提条件 3. 配置步骤 3.1. 启用 VT-d 3.2. 激活 IOMMU 3.3. 添加 VFIO 模块 …

如何解决pip安装报错ModuleNotFoundError: No module named ‘voila’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘voila’问题 摘要 在开发过程中&#xff0c;我们常常会遇到pip安装包时出现各种错误&#xff0c;特别是在使用PyCharm进行开发时。本文将详细介绍如何解决安装…

[spring6: @EnableWebMvc]-源码分析

源码 EnableWebMvc EnableWebMvc 是用于启用 Spring MVC 的注解&#xff0c;它通过导入 DelegatingWebMvcConfiguration 来加载默认的 MVC 配置&#xff0c;同时允许开发者通过实现 WebMvcConfigurer 接口来自定义部分配置&#xff1b;若需更高阶的控制&#xff0c;则可直接继承…

Jmeter的元件使用介绍:(四)前置处理器详解

Jmeter的前置处理器可以用来在取样器执行前做一些数据准备操作&#xff0c;也需要注意使用的作用域问题。常用的前置处理器有&#xff1a;用户参数、BeanShell预处理器、JDBC预处理器。一、用户参数 【用户参数】与前面介绍过的【用户定义的变量】有相似之处&#xff0c;先来介…

十七、K8s 可观测性:全链路追踪

十七、K8s 可观测性&#xff1a;全链路追踪 文章目录十七、K8s 可观测性&#xff1a;全链路追踪1、Skywalking 初识1.1 为什么需要全链路追踪平台1.2 全链路追踪核心组件及工作原理1.2.1 全链路追踪核心概念1.2.2 全链路追踪工作原理1.3 什么是Skywalking&#xff1f;1.4 Skywa…

2025 Gitee vs. GitLab:全面对比与选择指南

在软件研发持续加速、合规要求日益严格的背景下&#xff0c;选择合适的代码托管平台成为团队数字化能力建设的关键环节。尤其在中国本土市场&#xff0c;Gitee正凭借其深度本地化能力、全面生态整合和开源社区支撑&#xff0c;成为国内团队首选的开发协作平台。 一、Gitee&…

期货反向跟单忌讳问题(一): 不断调整盘手交易规则

在期货反向跟单领域&#xff0c;不少运营者在摸着石头过河的过程中&#xff0c;容易陷入一个致命误区——对盘手交易规则的频繁调整。这种看似“优化策略”的举动&#xff0c;往往会让整个跟单体系陷入恶性循环&#xff0c;最终偏离盈利初衷。期货反向跟单的核心逻辑是&#xf…

Effective C++ 条款07:为多态基类声明virtual析构函数

Effective C 条款07&#xff1a;为多态基类声明virtual析构函数核心思想&#xff1a;当通过基类指针删除派生类对象时&#xff0c;如果基类没有虚析构函数&#xff0c;会导致派生类资源泄漏。因为此时只会调用基类的析构函数&#xff0c;而不会调用派生类的析构函数。 ⚠️ 1. …

C++进阶—C++11

第一章&#xff1a;C11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞进行修复&#xff0c;语言的核心部分则没有改动&#xff0c;因此人们习惯性…

把振动数据转成音频并播放

把振动数据转声音并播放 1、实现流程 安装第三方库: pip install numpy==1.23.5 pip install scipy==1.10.1 pip install sounddevice==0.4.6流程: 1、导入振动数据 2、数据归一化到[-1, 1]范围 3、重采样到44.1kHz 4、播放音频 5、保存音频为WAV文件(可选)2、代码示例 …

ServBay 1.15.0 更新,拥抱 Bun Deno 新生态

历时一个月&#xff0c;ServBay迎来了1.15.0的更新。我们始终坚信&#xff0c;一个优秀的本地开发环境&#xff0c;不仅要稳定、高效&#xff0c;更要紧跟技术的演进脉搏。ServBay 的使命是为开发者扫清开发环境配置的障碍&#xff0c;让您能聚焦于创造本身。 本次ServBay 1.1…

Java设计模式-通俗举例

设计模式就像做菜的食谱&#xff0c;告诉我们遇到常见问题时该用什么"烹饪方法"。今天我就用最生活化的例子&#xff0c;带大家轻松掌握23种设计模式的精髓。一、创建型模式&#xff08;5种&#xff09;&#xff1a;怎么"造东西"1. 单例模式&#xff1a;公…

【跟我学YOLO】YOLO12(3)训练自己的数据集

欢迎关注『跟我学 YOLO』系列 【跟我学YOLO】&#xff08;1&#xff09;YOLO12&#xff1a;以注意力为中心的物体检测 【跟我学YOLO】&#xff08;2&#xff09;YOLO12 环境配置与基本应用 【跟我学YOLO】&#xff08;3&#xff09;YOLO12 训练自己的数据集 【跟我学YOLO】&…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博舆情分析实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博舆情分析实现 视频在线地址&#xff1…

【C++】手搓一个STL风格的vector容器

TOC(手搓一个STL风格的vector容器) 手搓一个STL风格的vector容器 github地址 有梦想的电信狗 0. 前言&#xff1a;动态数组的工程实践 ​ 在C标准库中&#xff0c;vector容器作为最核心的序列式容器&#xff0c;其设计融合了动态数组的高效性与安全性。本文将通过完整实现…

24. 了解过 webp 吗

总结 一种图片格式 一、什么是 WebP&#xff1f; WebP&#xff08;发音为 “weppy”&#xff09;是由 Google 推出的一种现代图片格式&#xff0c;支持有损压缩和无损压缩&#xff0c;旨在提供更小的文件体积和更高质量的图像显示。 它兼容常见的图片功能&#xff0c;如&#…