DINOv2 + yolov8 + opencv 检测卡车的可拉拽雨覆是否完全覆盖
最近是接了一个需求咨询图像处理类的,甲方要在卡车过磅的地方装一个摄像头用检测卡车的车斗雨覆是否完全, 让我大致理了下需求并对技术核心做下预研究
开发一套图像处理软件,能够实时监控经过的卡车并判断其车斗的雨覆状态。
系统需具备以下功能:
- 图像采集:通过高分辨率摄像头采集卡车经过时的图像。
- 图像处理:对采集的图像进行处理,识别车斗及雨覆的具体位置。
- 状态判断:
- 判断雨覆是否完全覆盖车斗。
- 在雨覆未完全覆盖时,生成警报或提示信息。
例如这样的图片,检测上方雨覆是否完全遮盖住
需求分析
图像采集是视频流接收,确保摄像头支持所选协议,并具备高清分辨率(至少1080p)以提高图像识别的准确性。摄像头应具备良好的低光性能,以适应不同的环境光照条件。
- 使用开源媒体框架(如 FFmpeg 或 GStreamer)来接收和处理视频流。
- 实现视频流的解码,提取每一帧图像供后续处理。
所以在技术预研上直接从图片开始, 目测了下需要使用图像语义分割再在分割图像基础上再计算雨覆的遮盖率,会使用到的工具具体有
导入必要的库,用于深度学习(PyTorch)、图像处理(PIL、OpenCV)、可视化(Matplotlib)、以及 YOLOv8(Ultralytics)目标检测和 DINOv2(Transformers)语义分割。
- PyTorch的作用:
- 提供核心深度学习功能,如 torch.nn 用于定义 DINOv2 分割模型(DINOv2ForSegmentation 类)。
- 通过 torch.device 确定设备(CPU 或 CUDA),将模型和数据加载到 GPU(self.device 和 self.model.to(self.device))。
- 处理张量操作(如 torch.softmax 在 postprocess 中生成概率分布)。
- PIL 作用:用于图像处理,支持打开、转换、调整大小和增强图像。
- 加载和处理图像文件(如 Image.open("trunk.jpg"))。
- 转换图像格式(如 image.convert("RGB")),调整大小(如 mask.resize),并支持数据增强(如 ImageEnhance 进行亮度、对比度、色调和饱和度调整)。
- 创建和保存掩码或结果图像(如 Image.fromarray 和 save 方法)。
- cv2的作用:
- 处理颜色分割(如 color_based_segmentation 使用 HSV 颜色空间分割车斗和覆盖布)。
- 进行后处理优化,包括形态学操作(dilate、erode、morphologyEx 在 enhance_segmentation 和 color_based_segmentation 中填补空洞、去除噪声)。
- 边缘检测(Canny 在 enhance_segmentation 中捕捉车斗边缘)和轮廓检测(findContours 填补完整轮廓)。
- Matplotlib作用:用于数据可视化和绘图,适合生成图形和保存图像。
- 保存分割结果的图像(如 plt.imsave 在 visualize_and_extract_regions 中保存车斗和覆盖布到黑色背景的图片)。
- 提供可视化支持,但当前代码未直接使用 Matplotlib 绘制图表,仅用于文件保存
- Ultralytics(YOLOv8)的作用:
- 加载预训练的 yolov8n.pt 模型(YOLO("yolov8n.pt")),检测图像中的卡车(detect_truck 函数)。
- 返回卡车的边界框(box.xyxy),用于裁剪图像区域供 DINOv2 语义分割,确保仅在 truck 区域内分割车斗和覆盖布。
- 设置置信度阈值(conf=0.3)以平衡检测精度和召回率。
定义类别和参数(Constants and Parameters)
CLASS_NAMES = ["background", "truck_bed", "tarp"]AUGMENTATION_PARAMS = {"brightness_factor": (0.8, 1.2),"contrast_factor": (0.8, 1.2),"rotation_range": (-30, 30),"hue_shift": (-0.1, 0.1),"saturation_factor": (0.8, 1.2),
}YOLO_CLASSES = {6: "train",7: "truck",
}
- 作用:
- CLASS_NAMES:定义语义分割的类别(背景、车斗、覆盖布),用于 DINOv2 模型的输出和后续处理。
- AUGMENTATION_PARAMS:设置数据增强参数,模拟亮度、对比度、旋转、色调和饱和度的变化,提高模型对不同光照、角度和颜色的泛化能力。
- YOLO_CLASSES:定义 YOLOv8 模型的类别映射,指定类别 ID(如 7 表示 "truck"),用于检测卡车。
自定义 DINOv2 分割模型(DINOv2ForSegmentation)
class DINOv2ForSegmentation(nn.Module):def __init__(self, num_classes=3, model_name="./dinov2_base/"):# 加载 DINOv2 主干网络并冻结参数self.backbone = ViTModel.from_pretrained(model_name)hidden_size = self.backbone.config.hidden_size# 添加分割头,适配 518x518 输入self.segmentation_head = nn.Sequential(nn.Conv2d(hidden_size, 256, kernel_size=1),nn.Upsample(scale_factor=14, mode='bilinear'),nn.Conv2d(256, num_classes, kernel_size=1))for param in self.backbone.parameters():param.requires_grad = Falsedef forward(self, pixel_values):# 从 DINOv2 提取特征并通过分割头生成分割结果outputs = self.backbone(pixel_values)last_hidden = outputs.last_hidden_statefeatures = last_hidden[:, 1:].permute(0, 2, 1).view(last_hidden.size(0), -1, 37, 37)logits = self.segmentation_head(features)return logits
作用:
- 定义基于 DINOv2 的语义分割模型,使用预训练的 ViT(Vision Transformer)作为主干网络,冻结其参数以减少计算量。
- 添加自定义分割头(segmentation_head),将 37x37 的特征图上采样并生成 3 类的分割结果(背景、车斗、覆盖布)。
- forward 方法处理输入图像(518x518),输出分割 logits。
分割管道(SegmentationPipeline)
class SegmentationPipeline:def __init__(self, num_classes=3):# 初始化 DINOv2 模型和特征提取器self.feature_extractor = ViTFeatureExtractor.from_pretrained("./dinov2_base", size={"height": 518, "width": 518})self.model = DINOv2ForSegmentation(num_classes)self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.model.to(self.device)self.model.eval()def augment_image(self, image):# 应用数据增强(亮度、对比度、旋转、色调、饱和度)image = ImageEnhance.Brightness(image).enhance(random.uniform(*AUGMENTATION_PARAMS["brightness_factor"]))image = ImageEnhance.Contrast(image).enhance(random.uniform(*AUGMENTATION_PARAMS["contrast_factor"]))image = ImageEnhance.Color(image).enhance(random.uniform(*AUGMENTATION_PARAMS["saturation_factor"]))angle = random.uniform(*AUGMENTATION_PARAMS["rotation_range"])image = image.rotate(angle, expand=True, fillcolor=(0, 0, 0))return imagedef preprocess(self, image):# 预处理图像:转换为 RGB,应用增强,调整到 518x518if image.mode != "RGB":image = image.convert("RGB")augmented_image = self.augment_image(image)inputs = self.feature_extractor(images=augmented_image, return_tensors="pt", size={"height": 518, "width": 518})return inputs.pixel_values.to(self.device)def postprocess(self, logits, original_size):# 后处理:softmax 转换为概率,取最大值生成掩码,调整回原图大小probs = torch.softmax(logits, dim=1)mask = torch.argmax(probs, dim=1).squeeze().cpu().numpy()mask = Image.fromarray(mask.astype(np.uint8)).resize(original_size, Image.NEAREST)return maskdef predict(self, image, truck_bbox=None):# 在 truck 边界框内或全图进行预测if truck_bbox:cropped_image = image.crop((max(0, truck_bbox[0] - 20), max(0, truck_bbox[1] - 20),min(image.size[0], truck_bbox[2] + 20), min(image.size[1], truck_bbox[3] + 20)))else:cropped_image = imageinputs = self.preprocess(cropped_image)with torch.no_grad():logits = self.model(inputs)probs = torch.softmax(logits, dim=1)mask = self.postprocess(logits, cropped_image.size)if truck_bbox:full_mask = Image.new("L", image.size, 0)full_mask.paste(mask, (max(0, truck_bbox[0] - 20), max(0, truck_bbox[1] - 20),min(image.size[0], truck_bbox[2] + 20), min(image.size[1], truck_bbox[3] + 20)))return full_maskreturn mask
作用
- SegmentationPipeline 封装了 DINOv2 模型的预处理、预测和后处理逻辑。
- augment_image:通过随机变换增强图像,模拟不同光照、角度和颜色,提高模型泛化能力。
- preprocess:将输入图像转换为 RGB,应用增强,调整到 518x518,发送到 GPU/CPU。
- postprocess:将模型输出转换为掩码,调整回原图大小。
- predict:根据 YOLO 检测的 truck 边界框裁剪图像进行分割,支持扩展边界(padding=20)以捕捉边缘。
可视化和提取区域(visualize_and_extract_regions)
def visualize_and_extract_regions(original_image, mask, save_base_path="output", num_classes=3):# 创建黑色背景,提取车斗和覆盖布,保存到单独图片colormap = np.array([[0, 0, 0], [255, 0, 0], [0, 255, 0]]) mask_array = np.array(mask.convert("L")).resize(original_image.size, Image.NEAREST)black_background = np.zeros((*original_image.size, 3), dtype=np.uint8)for class_id, class_name in enumerate(CLASS_NAMES[1:], 1):class_mask = (mask_array == class_id).astype(np.uint8) * 255extracted_region = black_background.copy()for i in range(3):extracted_region[:, :, i] = black_background[:, :, i] * (1 - class_mask / 255) + colormap[class_id, i] * (class_mask / 255)Image.fromarray(extracted_region).save(f"{save_base_path}_{class_name}_on_black.png")mask.save(f"{save_base_path}_mask.png")color_based_mask = color_based_segmentation(original_image)if color_based_mask:visualize_color_based_mask_on_black(original_image, color_based_mask, save_base_path + "_color_based_on_black")
作用:
- 将车斗和覆盖布提取到黑色背景的单独图片,忽略背景。
- 调整掩码尺寸匹配原图,确保尺寸一致。
- 保存原始掩码和颜色增强后的掩码,用于检查和调试。
YOLOv8 检测卡车(detect_truck)
def detect_truck(image_path):# 使用 YOLOv8 检测卡车,返回边界框model = YOLO("yolov8n.pt")print('yolov8n load successfully~~~')results = model(image_path, conf=0.3)for result in results:for box in result.boxes:cls = int(box.cls[0])if YOLO_CLASSES.get(cls):print('detect out:', YOLO_CLASSES.get(cls))x1, y1, x2, y2 = box.xyxy[0].tolist()return (int(x1), int(y1), int(x2), int(y2))return None
- 加载 YOLOv8 模型(yolov8n.pt),检测图像中的卡车(类别 "truck",ID 7)。
- 设置置信度阈值(conf=0.3),返回卡车的边界框(x1, y1, x2, y2)。
颜色分割(color_based_segmentation)
def color_based_segmentation(image):# 使用颜色阈值分割车斗(红、蓝、黄)和覆盖布(绿、蓝)img = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2BGR)hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)mask_truck = cv2.bitwise_or(cv2.bitwise_or(cv2.inRange(hsv, [0, 120, 70], [10, 255, 255]),cv2.inRange(hsv, [170, 120, 70], [180, 255, 255])),cv2.bitwise_or(cv2.inRange(hsv, [100, 120, 70], [130, 255, 255]),cv2.inRange(hsv, [20, 120, 70], [40, 255, 255])))mask_tarp = cv2.bitwise_or(cv2.inRange(hsv, [35, 40, 40], [85, 255, 255]),cv2.inRange(hsv, [100, 40, 40], [130, 255, 255]))combined_mask = np.zeros(hsv.shape[:2], dtype=np.uint8)combined_mask[mask_truck > 0] = 1combined_mask[mask_tarp > 0] = 2kernel = np.ones((5, 5), np.uint8)return Image.fromarray(cv2.morphologyEx(cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel),cv2.MORPH_OPEN, kernel))
-
作用:
- 使用 HSV 颜色空间分割车斗(红、蓝、黄)和覆盖布(绿、蓝),提高对颜色变化的鲁棒性。
- 应用形态学操作(闭运算和开运算)去除噪声并填补空洞,作为 DINOv2 的补充。
def visualize_color_based_mask_on_black(original_image, mask, save_path):# 将颜色分割结果提取到黑色背景colormap = np.array([[0, 0, 0], [255, 0, 0], [0, 255, 0]])mask_array = np.array(mask)black_background = np.zeros((*mask_array.shape, 3), dtype=np.uint8)for class_id in [1, 2]:class_mask = (mask_array == class_id).astype(np.uint8) * 255for i in range(3):black_background[:, :, i] = black_background[:, :, i] * (1 - class_mask / 255) + colormap[class_id, i] * (class_mask / 255)Image.fromarray(black_background).save(save_path + ".png")
- 作用:将颜色分割的车斗和覆盖布提取到黑色背景,生成单独的图像文件。
测试卡车图片语义分割
if __name__ == "__main__":# 初始化 pipelinenum_classes = 3 # 背景, 车斗, 覆盖布try:pipeline = SegmentationPipeline(num_classes=num_classes)# 加载原始图像image_path = "trunk.jpg"original_image = Image.open(image_path)# 使用YOLOv8检测卡车truck_bbox = detect_truck(image_path)if truck_bbox:print(f"Detected truck bounding box: {truck_bbox}")# 进行分割segmentation_mask = pipeline.predict(original_image, truck_bbox)# 提取并可视化车斗和覆盖布到黑色背景visualize_and_extract_regions(original_image=original_image,mask=segmentation_mask,save_base_path="segmentation_output",num_classes=num_classes)else:print("No truck detected, using full image for segmentation")except Exception as e:print(f"Error in main execution: {e}")
运行~~
分割后图像
优化目标和当前问题
- 当前问题:车斗边缘未完全分割,可能因 DINOv2 模型未训练捕捉细小边缘、YOLO 边界框未包含边缘、或后处理未充分扩展轮廓。
- 优化:通过扩展 YOLO 边界框(padding)、增强边缘检测(Canny 和形态学操作)、结合颜色和形状信息,确保车斗完整轮廓被分割。
经过修改和测试
import torch
import torch.nn as nn
from transformers import ViTModel, ViTFeatureExtractor
from PIL import Image, ImageEnhance
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import os
import cv2 # For color-based and shape-based post-processing
import random
from ultralytics import YOLO # For YOLOv8# 定义类别:0 = 背景, 1 = 车斗, 2 = 覆盖布
CLASS_NAMES = ["background", "truck_bed", "tarp"]# 数据增强参数
AUGMENTATION_PARAMS = {"brightness_factor": (0.8, 1.2), # 亮度变化范围"contrast_factor": (0.8, 1.2), # 对比度变化范围"rotation_range": (-30, 30), # 旋转角度范围(度)"hue_shift": (-0.1, 0.1), # 色调变化范围"saturation_factor": (0.8, 1.2), # 饱和度变化范围
}# YOLOv8 类别映射(假设 'truck' 是可识别的类别)
YOLO_CLASSES = {6: "train",7: "truck",
}# ======================
# 自定义分割模型定义(适配518x518)
# ======================
class DINOv2ForSegmentation(nn.Module):def __init__(self, num_classes=3, model_name="./dinov2_base/"):super().__init__()# 加载 DINOv2 主干网络try:self.backbone = ViTModel.from_pretrained(model_name)except Exception as e:print(f"Error loading DINOv2 model: {e}")raisehidden_size = self.backbone.config.hidden_size# 分割头调整(适配518输入)self.segmentation_head = nn.Sequential(nn.Conv2d(hidden_size, 256, kernel_size=1),nn.Upsample(scale_factor=14, mode='bilinear'), # 518/14=37nn.Conv2d(256, num_classes, kernel_size=1))# 冻结主干网络(可选解冻部分层以微调)for param in self.backbone.parameters():param.requires_grad = Falsedef forward(self, pixel_values):# 获取特征 [batch, 37x37+1, hidden_size]outputs = self.backbone(pixel_values)last_hidden = outputs.last_hidden_state# 转换特征形状 [batch, hidden_size, 37, 37]batch_size = last_hidden.size(0)features = last_hidden[:, 1:].permute(0, 2, 1) # 移除CLS tokenfeatures = features.view(batch_size, -1, 37, 37) # 518/14=37# 分割头logits = self.segmentation_head(features)return logits# ======================
# 预处理与后处理工具
# ======================
class SegmentationPipeline:def __init__(self, num_classes=3):# 确保dinov2_base目录包含正确的preprocessor_config.jsontry:self.feature_extractor = ViTFeatureExtractor.from_pretrained("./dinov2_base",size={"height": 518, "width": 518} # 关键修改)except Exception as e:print(f"Error loading feature extractor: {e}")raiseself.model = DINOv2ForSegmentation(num_classes)self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.model.to(self.device)self.model.eval()print(f"Model loaded on {self.device}")def augment_image(self, image):"""应用数据增强以提高泛化能力"""# 亮度enhancer = ImageEnhance.Brightness(image)brightness = random.uniform(*AUGMENTATION_PARAMS["brightness_factor"])image = enhancer.enhance(brightness)# 对比度enhancer = ImageEnhance.Contrast(image)contrast = random.uniform(*AUGMENTATION_PARAMS["contrast_factor"])image = enhancer.enhance(contrast)# 色调和饱和度(使用PIL的Color)enhancer = ImageEnhance.Color(image)saturation = random.uniform(*AUGMENTATION_PARAMS["saturation_factor"])image = enhancer.enhance(saturation)# 旋转(使用PIL的rotate,修正fillmode错误)angle = random.uniform(*AUGMENTATION_PARAMS["rotation_range"])image = image.rotate(angle, expand=True, fillcolor=(0, 0, 0)) # 黑色填充return imagedef preprocess(self, image):if not isinstance(image, Image.Image):raise ValueError("Input must be a PIL Image")if image.mode != "RGB":print(f"Converting image from {image.mode} to RGB")image = image.convert("RGB")# 应用数据增强augmented_image = self.augment_image(image)# 自动调整到518x518try:inputs = self.feature_extractor(images=augmented_image,return_tensors="pt",size={"height": 518, "width": 518})return inputs.pixel_values.to(self.device)except Exception as e:print(f"Error in preprocessing: {e}")raisedef postprocess(self, logits, original_size):probs = torch.softmax(logits, dim=1)mask = torch.argmax(probs, dim=1).squeeze().cpu().numpy()print(f"Mask shape: {mask.shape}, Unique values: {np.unique(mask)}")print(f"Probability distribution per class: {probs.mean(dim=(0, 2, 3))}") # Debug class probabilitiesmask = Image.fromarray(mask.astype(np.uint8))return mask.resize(original_size, Image.NEAREST)def predict(self, image, truck_bbox=None):"""在指定的truck区域内进行预测,如果没有truck区域则使用整个图像"""if truck_bbox:# 裁剪图像到truck区域,扩展更大边界以包含边缘x1, y1, x2, y2 = truck_bboxpadding = 50 # 增加边界以捕捉完整边缘(从 20 增加到 50)cropped_image = image.crop((max(0, x1 - padding), max(0, y1 - padding),min(image.size[0], x2 + padding), min(image.size[1], y2 + padding)))else:cropped_image = image# 预处理inputs = self.preprocess(cropped_image)# 推理with torch.no_grad():logits = self.model(inputs)probs = torch.softmax(logits, dim=1) # Compute probabilities hereprint(f"Logits shape: {logits.shape}")print(f"Logits max: {logits.max()}, min: {logits.min()}")print(f"Probabilities max: {probs.max()}, min: {probs.min()}")# 后处理mask = self.postprocess(logits, cropped_image.size)# 如果有truck区域,将mask扩展回原图大小if truck_bbox:full_mask = Image.new("L", image.size, 0) # 背景为0adjusted_bbox = (max(0, x1 - padding), max(0, y1 - padding),min(image.size[0], x2 + padding), min(image.size[1], y2 + padding))full_mask.paste(mask, adjusted_bbox)return full_maskreturn mask# ======================
# 使用示例
# ======================
def visualize_and_extract_regions(original_image, mask, save_base_path="output", num_classes=3):"""将车斗和覆盖布提取到黑色背景的单独图片上,不再叠加到原图:param original_image: PIL.Image 原始图片:param mask: PIL.Image 分割mask:param save_base_path: 保存路径基础名称:param num_classes: 分割类别数"""# 创建颜色映射 (RGB格式)colormap = []# 背景色为黑色(用于单独输出)colormap.append([0, 0, 0]) # Class 0: Background (black)# 车斗(红色)、覆盖布(绿色)colormap.append([255, 0, 0]) # Class 1: Truck bed (red)colormap.append([0, 255, 0]) # Class 2: Tarp (green)colormap = np.array(colormap, dtype=np.uint8)# 将mask转换为数组,确保尺寸匹配mask_array = np.array(mask.convert("L")) # 确保mask是单通道original_array = np.array(original_image.convert("RGB"))height, width = original_array.shape[:2]# 调整mask尺寸以匹配原图(如果不匹配)if mask_array.shape != (height, width):mask_array = np.array(mask.resize((width, height), Image.NEAREST))# 提取并保存车斗和覆盖布到黑色背景的单独图片,只处理车斗和覆盖布black_background = np.zeros((height, width, 3), dtype=np.uint8) # 黑色背景for class_id, class_name in enumerate(CLASS_NAMES[1:], 1): # 跳过背景(0)class_mask = (mask_array == class_id).astype(np.uint8) * 255 # 二值掩码# 提取区域到黑色背景extracted_region = black_background.copy()for i in range(3): # RGB通道extracted_region[:, :, i] = black_background[:, :, i] * (1 - class_mask / 255) + \colormap[class_id, i] * (class_mask / 255)# 保存提取的区域到黑色背景extracted_image = Image.fromarray(extracted_region.astype(np.uint8))extracted_image.save(f"{save_base_path}_{class_name}_on_black.png")print(f"Extracted {class_name} on black background saved to {save_base_path}_{class_name}_on_black.png")# 也可以保存原始mask以便检查mask.save(f"{save_base_path}_mask.png")print(f"Raw mask saved to {save_base_path}_mask.png")# 附加:尝试基于颜色后处理以改进结果color_based_mask = color_based_segmentation(original_image)if color_based_mask is not None:visualize_color_based_mask_on_black(original_image, color_based_mask, save_base_path + "_color_based_on_black")def detect_truck(image_path):"""使用YOLOv8检测卡车并返回边界框"""try:# 加载YOLOv8模型model = YOLO("yolov8n.pt") # 确保yolov8n.pt在当前目录下# 进行预测print('yolov8n load successfully~~~')results = model(image_path, conf=0.3) # confidence threshold 0.3#print('result:', results)for result in results:boxes = result.boxes # 获取检测框for box in boxes:cls = int(box.cls[0]) # 类别IDprint('cls:', cls)if YOLO_CLASSES.get(cls): # == "truck":print('detect out:', YOLO_CLASSES.get(cls))x1, y1, x2, y2 = box.xyxy[0].tolist() # 边界框坐标return (int(x1), int(y1), int(x2), int(y2)) # 返回(x1, y1, x2, y2)return None # 如果未检测到卡车except Exception as e:print(f"Error in YOLOv8 detection: {e}")return Nonedef color_based_segmentation(image):"""使用颜色阈值分割车斗(多种颜色)和覆盖布(多种颜色),提高泛化能力"""try:# 转换为OpenCV格式 (BGR)img = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2BGR)# 定义更广泛的颜色范围(HSV空间更适合)hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)# 红色范围(车斗,可能为红色、蓝色、黄色等)lower_red1 = np.array([0, 120, 70]) # 红色范围1upper_red1 = np.array([10, 255, 255])lower_red2 = np.array([170, 120, 70]) # 红色范围2upper_red2 = np.array([180, 255, 255])lower_blue = np.array([100, 120, 70]) # 蓝色范围upper_blue = np.array([130, 255, 255])lower_yellow = np.array([20, 120, 70]) # 黄色范围upper_yellow = np.array([40, 255, 255])mask_red1 = cv2.inRange(hsv, lower_red1, upper_red1)mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)mask_truck = cv2.bitwise_or(cv2.bitwise_or(mask_red1, mask_red2), cv2.bitwise_or(mask_blue, mask_yellow))# 绿色范围(覆盖布,可能为绿色、蓝色等)lower_green = np.array([35, 40, 40])upper_green = np.array([85, 255, 255])lower_blue_tarp = np.array([100, 40, 40]) # 蓝色覆盖布upper_blue_tarp = np.array([130, 255, 255])mask_green = cv2.inRange(hsv, lower_green, upper_green)mask_blue_tarp = cv2.inRange(hsv, lower_blue_tarp, upper_blue_tarp)mask_tarp = cv2.bitwise_or(mask_green, mask_blue_tarp)# 合并掩码:0=背景, 1=车斗, 2=覆盖布combined_mask = np.zeros((hsv.shape[0], hsv.shape[1]), dtype=np.uint8)combined_mask[mask_truck > 0] = 1 # 车斗combined_mask[mask_tarp > 0] = 2 # 覆盖布# 应用形态学操作去除噪声并填充小孔kernel = np.ones((5, 5), np.uint8)combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel)combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel)return Image.fromarray(combined_mask)except Exception as e:print(f"Error in color-based segmentation: {e}")return Nonedef visualize_color_based_mask_on_black(original_image, mask, save_path):"""可视化基于颜色的分割结果到黑色背景"""colormap = np.array([[0, 0, 0], # 背景 (黑)[255, 0, 0], # 车斗 (红)[0, 255, 0] # 覆盖布 (绿)], dtype=np.uint8)mask_array = np.array(mask)height, width = mask_array.shapeblack_background = np.zeros((height, width, 3), dtype=np.uint8)# 提取区域到黑色背景for class_id in [1, 2]: # 只处理车斗和覆盖布class_mask = (mask_array == class_id).astype(np.uint8) * 255for i in range(3): # RGB通道black_background[:, :, i] = black_background[:, :, i] * (1 - class_mask / 255) + \colormap[class_id, i] * (class_mask / 255)extracted_image = Image.fromarray(black_background.astype(np.uint8))extracted_image.save(save_path + ".png")print(f"Color-based visualization on black background saved to {save_path}.png")def enhance_segmentation(mask_array, truck_region, num_classes):"""增强分割结果以捕捉车斗的完整轮廓,处理边缘和颜色遮挡"""# 应用更强的形态学操作以填补空洞和捕捉边缘kernel_large = np.ones((20, 20), np.uint8) # 增大内核以捕捉完整边缘(从 15 增加到 20)kernel_small = np.ones((3, 3), np.uint8) # 用于细化边缘# 膨胀以捕捉完整轮廓dilated_mask = cv2.dilate(mask_array, kernel_large, iterations=4) # 增加迭代次数以捕捉更多边缘# 腐蚀以去除噪声,保持边界eroded_mask = cv2.erode(dilated_mask, kernel_large, iterations=1)# 闭运算填补空洞closed_mask = cv2.morphologyEx(eroded_mask, cv2.MORPH_CLOSE, kernel_large)# 开运算去除小噪声opened_mask = cv2.morphologyEx(closed_mask, cv2.MORPH_OPEN, kernel_small)# 细化边缘:使用更敏感的 Canny 边缘检测edges = cv2.Canny((opened_mask == 1).astype(np.uint8) * 255, 30, 100) # 降低阈值以捕捉更多细小边缘dilated_edges = cv2.dilate(edges, kernel_small, iterations=3) # 增加膨胀以连接边缘# 使用颜色和形状信息进一步优化车斗区域hsv_truck = cv2.cvtColor(truck_region, cv2.COLOR_RGB2HSV)height, width = opened_mask.shape# 查找车斗和覆盖布的轮廓truck_bed_mask = (opened_mask == 1).astype(np.uint8) * 255tarp_mask = (opened_mask == 2).astype(np.uint8) * 255# 轮廓检测,填充完整轮廓contours_truck, _ = cv2.findContours(truck_bed_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)contours_tarp, _ = cv2.findContours(tarp_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 创建增强后的掩码enhanced_mask = np.zeros_like(opened_mask, dtype=np.uint8)# 填充车斗轮廓,确保完整性并结合边缘if contours_truck:# 找到最大的轮廓(假设车斗是最大的区域)largest_contour = max(contours_truck, key=cv2.contourArea)cv2.drawContours(enhanced_mask, [largest_contour], -1, 1, thickness=cv2.FILLED)# 结合边缘信息,填补细小边界enhanced_mask[dilated_edges > 0] = 1 # 将检测到的边缘区域标记为车斗else:# 如果没有检测到轮廓,使用膨胀后的区域enhanced_mask[opened_mask == 1] = 1# 填充覆盖布轮廓for contour in contours_tarp:cv2.drawContours(enhanced_mask, [contour], -1, 2, thickness=cv2.FILLED)# 确保掩码值在有效范围内enhanced_mask = np.clip(enhanced_mask, 0, num_classes - 1)# 额外处理:如果车斗区域有小断裂或边缘缺失,使用连通性分析填补num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(truck_bed_mask, connectivity=8)if num_labels > 1: # 如果有多个连通区域largest_area = 0largest_label = 0for label in range(1, num_labels): # 跳过背景(标签0)area = stats[label, cv2.CC_STAT_AREA]if area > largest_area:largest_area = arealargest_label = labelenhanced_mask[labels == largest_label] = 1 # 保留最大连通区域作为车斗# 再次细化边缘,确保完整性final_edges = cv2.Canny((enhanced_mask == 1).astype(np.uint8) * 255, 20, 80) # 进一步降低阈值捕捉边缘enhanced_mask[final_edges > 0] = 1 # 填补边缘# 边界扩展:额外膨胀以确保边缘完整final_dilated = cv2.dilate((enhanced_mask == 1).astype(np.uint8) * 255, kernel_small, iterations=2)enhanced_mask[final_dilated > 0] = 1 # 扩展车斗边缘return enhanced_maskif __name__ == "__main__":# 初始化 pipelinenum_classes = 3 # 背景, 车斗, 覆盖布try:pipeline = SegmentationPipeline(num_classes=num_classes)# 加载原始图像image_path = "trunk.jpg"original_image = Image.open(image_path)# 使用YOLOv8检测卡车truck_bbox = detect_truck(image_path)if truck_bbox:print(f"Detected truck bounding box: {truck_bbox}")# 进行分割segmentation_mask = pipeline.predict(original_image, truck_bbox)# 提取并可视化车斗和覆盖布到黑色背景visualize_and_extract_regions(original_image=original_image,mask=segmentation_mask,save_base_path="segmentation_output",num_classes=num_classes)else:print("No truck detected, using full image for segmentation")except Exception as e:print(f"Error in main execution: {e}")
- 扩展 YOLO 边界框(padding):
-
在 predict 函数中,将 padding 从 20 增加到 50,确保 YOLO 检测的边界框包含车斗的完整边缘,减少因边界框过紧而丢失边缘的可能性。
-
- 增强边缘检测(Canny 和形态学操作):
-
增加额外膨胀步骤(final_dilated 使用 3x3 内核,迭代 2 次)以扩展车斗边缘,确保边缘完整。
-
优化 Canny 边缘检测,降低阈值(从 30, 100 调整到 20, 80 再到 20, 80),确保捕捉更多细小边缘。
-
增大形态学操作的内核大小(kernel_large 从 15x15 增加到 20x20),并增加膨胀迭代次数(从 3 增加到 4),更好地捕捉车斗边缘
-
- 结合颜色和形状信息:
-
保留颜色分割(color_based_segmentation)和轮廓检测(findContours),结合最大连通区域和边缘信息,确保车斗的完整轮廓被分割,即使有光影变化或遮挡。
-
使用连通性分析(connectedComponentsWithStats)保留最大连通区域,填补小断裂或边缘缺失。
-
- 预期效果:
-
车斗的完整轮廓(包括边缘)将被标记为红色,输出到 segmentation_output_truck_bed_on_black.png,即使边缘细小或有光影变化。
-
覆盖布(绿色)仍会被正确分割到 segmentation_output_tarp_on_black.png,但不会干扰车斗的完整性。
-
调试和下一步
- 运行优化后的代码,检查输出 segmentation_output_truck_bed_on_black.png 和 segmentation_output_enhanced_mask.png,确保车斗边缘被完整分割。
- 如果车斗边缘仍不完整,尝试:
- 进一步增加 padding(如 70 或更高)。
- 调整 enhance_segmentation 中的 kernel_large(如 25x25)、迭代次数,或 Canny 阈值(试试 10, 60)。
在语义分割的基础上,下面是判断车斗上方空间的范围何雨覆的位置
分析当前分割结果
- 车斗(红色):从图片看,车斗的轮廓已基本完整,但可能有细小噪声或不规则边缘。
- 覆盖布(绿色):覆盖布分布在车斗顶部,但可能有间断或未完全覆盖的部分。
- 目标:
- 计算车斗的顶部投影面积(假设为车斗的完整轮廓面积)。
- 计算覆盖布的面积(绿色区域面积)。
- 比较覆盖布面积与车斗面积,判断覆盖率是否达到 80%。
这里给出算法代码
import cv2
import numpy as np
from PIL import Image# 加载图像
image_path = "truck_seg.png"
image = cv2.imread(image_path)# 转换为 RGB 格式(OpenCV 默认 BGR)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 转换为 HSV 格式以便颜色分割
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)# 定义颜色范围(基于红色车斗和绿色雨覆)
# 红色范围(车斗,可能为红色、蓝色、黄色等)
lower_red1 = np.array([0, 120, 70]) # 红色范围1
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 120, 70]) # 红色范围2
upper_red2 = np.array([180, 255, 255])
lower_blue = np.array([100, 120, 70]) # 蓝色范围
upper_blue = np.array([130, 255, 255])
lower_yellow = np.array([20, 120, 70]) # 黄色范围
upper_yellow = np.array([40, 255, 255])# 绿色范围(雨覆,可能为绿色、蓝色等)
lower_green = np.array([35, 40, 40])
upper_green = np.array([85, 255, 255])
lower_blue_tarp = np.array([100, 40, 40]) # 蓝色雨覆
upper_blue_tarp = np.array([130, 255, 255])# 颜色分割
mask_red1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
mask_truck = cv2.bitwise_or(cv2.bitwise_or(mask_red1, mask_red2), cv2.bitwise_or(mask_blue, mask_yellow))mask_green = cv2.inRange(hsv, lower_green, upper_green)
mask_blue_tarp = cv2.inRange(hsv, lower_blue_tarp, upper_blue_tarp)
mask_tarp = cv2.bitwise_or(mask_green, mask_blue_tarp)# 合并掩码:0=背景, 1=车斗, 2=雨覆
combined_mask = np.zeros((hsv.shape[0], hsv.shape[1]), dtype=np.uint8)
combined_mask[mask_truck > 0] = 1 # 车斗
combined_mask[mask_tarp > 0] = 2 # 雨覆# 应用形态学操作去除噪声并填充空洞
kernel = np.ones((5, 5), np.uint8)
closed_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel)
opened_mask = cv2.morphologyEx(closed_mask, cv2.MORPH_OPEN, kernel)# 增强车斗轮廓,捕捉完整边缘
truck_bed_mask = (opened_mask == 1).astype(np.uint8) * 255
kernel_large = np.ones((20, 20), np.uint8)
dilated_truck = cv2.dilate(truck_bed_mask, kernel_large, iterations=4)
eroded_truck = cv2.erode(dilated_truck, kernel_large, iterations=1)
closed_truck = cv2.morphologyEx(eroded_truck, cv2.MORPH_CLOSE, kernel_large)# 增强雨覆轮廓
tarp_mask = (opened_mask == 2).astype(np.uint8) * 255
dilated_tarp = cv2.dilate(tarp_mask, kernel_large, iterations=2)
closed_tarp = cv2.morphologyEx(dilated_tarp, cv2.MORPH_CLOSE, kernel_large)# 更新增强后的掩码
enhanced_mask = np.zeros_like(opened_mask, dtype=np.uint8)
enhanced_mask[closed_truck > 0] = 1 # 车斗
enhanced_mask[closed_tarp > 0] = 2 # 雨覆# 计算面积(像素数)
truck_bed_area = np.sum(enhanced_mask == 1) # 车斗面积
tarp_area = np.sum(enhanced_mask == 2) # 雨覆面积# 计算覆盖率
if truck_bed_area > 0:coverage_ratio = (tarp_area / truck_bed_area) * 100 # 覆盖率(百分比)
else:coverage_ratio = 0 # 如果车斗面积为0,覆盖率设为0# 打印面积和覆盖率
print(f"Truck bed area (pixels): {truck_bed_area}")
print(f"Tarp area (pixels): {tarp_area}")
print(f"Tarp coverage ratio: {coverage_ratio:.2f}%")
print(f"Coverage meets 80% requirement: {'Yes' if coverage_ratio >= 80 else 'No'}")# 可视化:提取车斗和雨覆到黑色背景
height, width = enhanced_mask.shape
black_background = np.zeros((height, width, 3), dtype=np.uint8)# 提取车斗(红色)
truck_mask = (enhanced_mask == 1).astype(np.uint8) * 255
black_background[truck_mask > 0] = [255, 0, 0] # 红色# 提取雨覆(绿色)
tarp_mask = (enhanced_mask == 2).astype(np.uint8) * 255
black_background[tarp_mask > 0] = [0, 255, 0] # 绿色# 保存结果
cv2.imwrite("truck_bed_on_black.png", cv2.cvtColor(black_background, cv2.COLOR_RGB2BGR))
print(f"Extracted truck bed on black background saved to truck_bed_on_black.png")
cv2.imwrite("tarp_on_black.png", cv2.cvtColor(black_background, cv2.COLOR_RGB2BGR))
print(f"Extracted tarp on black background saved to tarp_on_black.png")# 保存增强后的掩码以便检查
cv2.imwrite("enhanced_mask.png", enhanced_mask)
print(f"Enhanced mask saved to enhanced_mask.png")
- 图像加载和颜色分割:
-
加载 truck_seg.png,转换为 HSV 颜色空间。
-
使用预定义的红色(车斗)和绿色(雨覆)范围进行颜色分割,规则与之前一致,支持多种颜色变体(红、蓝、黄;绿、蓝)。
-
- 形态学操作和轮廓增强:
- 应用闭运算(MORPH_CLOSE)填补空洞,开运算(MORPH_OPEN)去除噪声。
- 增强车斗和雨覆轮廓,使用更大的内核(20x20)和更多迭代次数(车斗 4 次,雨覆 2 次)以捕捉完整边缘。
- 面积计算:
- 使用 np.sum 统计车斗(值为 1)和雨覆(值为 2)的像素数,单位为像素。
- 计算覆盖率:tarp_area / truck_bed_area * 100,判断是否 ≥ 80%。
- 可视化输出:
- 将车斗(红色)和雨覆(绿色)提取到黑色背景,分别保存为 truck_bed_on_black.png 和 tarp_on_black.png。
- 保存增强后的掩码 enhanced_mask.png 以便检查。
经过检测覆盖率没有达到80%
上面图像分割还需要再优化下,毕竟存在把非雨覆物体错误识别的地方,但整体思路可以分享出来供大家借鉴
相关文章:

DINOv2 + yolov8 + opencv 检测卡车的可拉拽雨覆是否完全覆盖
最近是接了一个需求咨询图像处理类的,甲方要在卡车过磅的地方装一个摄像头用检测卡车的车斗雨覆是否完全, 让我大致理了下需求并对技术核心做下预研究 开发一套图像处理软件,能够实时监控经过的卡车并判断其车斗的雨覆状态。 系统需具备以下…...

算法日记27:完全背包(DFS->记忆化搜索->倒叙DP->顺序DP->空间优化)
一、暴力搜索(DFS) O ( n 2 ) O(n^2) O(n2) 1.1)思路解析 1、注意和01背包的区别在于每个物品可以无限次选择 注意在完全背包中,当一个物品被选择过一次,我们仍然需要考虑是否继续选择这个物品 01背包: …...
Linux 命令大全完整版(14)
5. 文件管理命令 chgrp(change group) 功能说明:变更文件或目录的所属群组。语 法:chgrp [-cfhRv][–help][–version][所属群组][文件或目录…] 或 chgrp [-cfhRv][–help][–version][–reference<参考文件或目录>][文件或目录…]补充说明&…...

基于 DeepSeek LLM 本地知识库搭建开源方案(AnythingLLM、Cherry、Ragflow、Dify)认知
写在前面 博文内容涉及 基于 Deepseek LLM 的本地知识库搭建使用 ollama 部署 Deepseek-R1 LLM知识库能力通过 Ragflow、Dify 、AnythingLLM、Cherry 提供理解不足小伙伴帮忙指正 😃,生活加油 我站在人潮中央,思考这日日重复的生活。我突然想,…...
Could not initialize class io.netty.util.internal.Platfor...
异常信息: Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class io.netty.util.internal.PlatformDependent0 Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.reflect.InaccessibleObjec…...

【书生大模型实战营】玩转HF/魔搭/魔乐社区-L0G4000
本文是书生大模型实战营系列的第4篇,本文的主题是:玩转HF/魔搭/魔乐社区。 1.开源大模型社区总览 开源不仅仅是一种技术模式,更是一种精神的体现。它打破了知识的壁垒,让技术平权成为可能。近年来,开源大模型社区蓬勃…...
2025年华为手机解锁BL的方法
注:本文是我用老机型测试的,新机型可能不适用 背景 华为官方已经在2018年关闭了申请BL解锁码的通道,所以华为手机已经无法通过官方获取解锁码。最近翻出了一部家里的老手机华为畅玩5X,想着能不能刷个系统玩玩,但是卡…...

了解 RAG 第二部分:经典 RAG 的工作原理
在本系列的第一篇文章中,我们介绍了检索增强生成 (RAG) ,解释了扩展传统大型语言模型 (LLM)功能的必要性。我们还简要概述了 RAG 的核心思想:从外部知识库检索上下文相关的信息,以确保 LLM 生成准确且最新的信息,而不会…...
50周学习go语言:第四周 函数与错误处理深度解析
第四周 函数与错误处理深度解析 以下是第4周函数基础的深度教程,包含两个完整案例和详细实现细节: 第四周:函数与错误处理深度解析 一、函数定义与参数传递 1. 基础函数结构 // 基本语法 func 函数名(参数列表) 返回值类型 {// 函数体 }// …...
debian 12安装 postgresql 17
按照官方文档安装,即可安装成功 https://www.postgresql.org/download/linux/debian/ 添加存储库 #添加存储库 sudo apt install -y postgresql-common#执行 存储库内 命令,自动处理某些东西 sudo /usr/share/postgresql-common/pgdg/apt.postgresql.o…...
C++....................4
1. using namespace std; class mystring { private:char* p;int len;// 辅助函数:复制字符串void copy(const char* source) {len strlen(source);p new char[len 1];strcpy(p, source);}// 辅助函数:释放内存void release() {if (…...
图书馆系统源码详解
本项目是一个基于Scala语言开发的图书馆管理系统。系统主要由以下几个部分组成:数据访问层(DAO)、数据模型层(Models)、服务层(Service)以及用户界面层(UI)。以下是对项目…...
Node.js中如何修改全局变量的几种方式
Node.js中如何修改全局变量。我需要先理解他们的需求。可能他们是在开发过程中遇到了需要跨模块共享数据的情况,或者想要配置一些全局可访问的设置。不过,使用全局变量可能存在一些问题,比如命名冲突、难以维护和测试困难,所以我得…...

基于javaweb的SpringBoot个人博客系统设计和实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...

厦大团队:DeepSeek大模型概念、技术与应用实践 140页PDF完整版下载
DeepSeek使用教程系列: 厦门大学: DeepSeek大模型概念、技术与应用实践 140页PDF完整版文件 厦大团队:DeepSeek大模型概念、技术与应用实践(140页PPT读懂大模型).pdf https://pan.baidu.com/s/1de4UIxqPsvMBIYcpen_M-…...

【Blender】二、建模篇--05,阵列修改器与晶格形变
阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…...

#渗透测试#批量漏洞挖掘#畅捷通T+远程命令执行漏洞
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章读。 目录 一、漏洞概况 二、攻击特征 三、应急处置…...
【Python爬虫(23)】探秘Python爬虫数据存储:MongoDB实战指南
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
Pytorch使用手册-音频数据增强(专题二十)
音频数据增强 torchaudio 提供了多种方式来增强音频数据。 在本教程中,我们将介绍一种应用效果、滤波器、RIR(房间脉冲响应)和编解码器的方法。 最后,我们将从干净的语音合成带噪声的电话语音。 import torch import torchaudio import torchaudio.functional as Fprin…...
Linux 命令大全完整版(04)
1. 用户信息相关命令 who 功能说明:显示目前登入系统的用户信息。语 法:who [-Himqsw][--help][--version][am i][记录文件]补充说明:执行这项指令可得知目前有哪些用户登入系统,单独执行 who 指令会列出登入帐号、使用的终端…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
02-性能方案设计
需求分析与测试设计 根据具体的性能测试需求,确定测试类型,以及压测的模块(web/mysql/redis/系统整体)前期要与相关人员充分沟通,初步确定压测方案及具体的性能指标QA完成性能测试设计后,需产出测试方案文档发送邮件到项目组&…...