深度学习篇---Pytorch框架下OC-SORT实现

news2025/6/2 10:57:51

下面将详细介绍如何基于 PyTorch 框架实现 OC-SORT(Observation-Centric SORT)算法。OC-SORT 是一种高性能的多目标跟踪算法,特别适用于复杂场景下的目标跟踪。我们将从算法原理到具体实现逐步展开。

1. 算法概述与核心原理

OC-SORT 在传统 SORT 算法的基础上,引入了三个关键创新点:

  • 以观测为中心的在线平滑(OOS):解决长时间遮挡导致的轨迹漂移问题
  • 以观测为中心的恢复(ORU):处理短期遮挡后的轨迹恢复
  • 以观测为中心的动量(OCM):通过运动方向一致性优化数据关联

2. 环境准备与依赖安装

首先需要安装必要的依赖库:

pip install torch torchvision torchaudio  # PyTorch基础库
pip install numpy scipy matplotlib  # 科学计算与可视化
pip install opencv-python  # 计算机视觉任务

3. 核心模块实现

下面我们将实现 OC-SORT 的核心组件:

3.1 卡尔曼滤波器实现
import torch
import numpy as np

class KalmanFilter:
    """
    卡尔曼滤波器实现,用于目标状态的预测和更新
    状态向量: [x, y, a, h, vx, vy, va, vh]
    其中(x,y)是边界框中心,a是宽高比,h是高度,vx,vy,va,vh是对应的速度
    """
    def __init__(self):
        # 状态转移矩阵 (8x8)
        self.F = torch.eye(8, dtype=torch.float32)
        dt = 1.0  # 时间间隔
        self.F[:4, 4:] = torch.eye(4, dtype=torch.float32) * dt
        
        # 观测矩阵 (4x8) - 只观测位置和宽高
        self.H = torch.zeros((4, 8), dtype=torch.float32)
        self.H[:4, :4] = torch.eye(4, dtype=torch.float32)
        
        # 过程噪声协方差
        self.Q = torch.eye(8, dtype=torch.float32)
        self.Q[:4, :4] *= 0.01  # 位置噪声
        self.Q[4:, 4:] *= 0.001  # 速度噪声
        
        # 观测噪声协方差
        self.R = torch.eye(4, dtype=torch.float32) * 0.01
        
    def initiate(self, measurement):
        """
        初始化轨迹状态
        measurement: [x1, y1, x2, y2] 检测框坐标
        """
        # 转换为 [x, y, a, h] 格式
        x1, y1, x2, y2 = measurement
        cx = (x1 + x2) / 2
        cy = (y1 + y2) / 2
        w = x2 - x1
        h = y2 - y1
        a = w / h
        
        # 初始化状态向量 [x, y, a, h, vx, vy, va, vh]
        mean = torch.tensor([cx, cy, a, h, 0, 0, 0, 0], dtype=torch.float32)
        
        # 初始化协方差矩阵
        covariance = torch.eye(8, dtype=torch.float32) * 1000.0
        covariance[4:, 4:] *= 100.0
        
        return mean, covariance
    
    def predict(self, mean, covariance):
        """
        预测下一时刻的状态
        """
        # 状态预测
        mean = torch.matmul(self.F, mean)
        
        # 协方差预测
        covariance = torch.matmul(torch.matmul(self.F, covariance), self.F.T) + self.Q
        
        return mean, covariance
    
    def project(self, mean, covariance):
        """
        将状态向量投影到观测空间
        """
        # 计算观测预测
        projected_mean = torch.matmul(self.H, mean)
        
        # 计算观测协方差
        projected_covariance = torch.matmul(torch.matmul(self.H, covariance), self.H.T) + self.R
        
        return projected_mean, projected_covariance
    
    def update(self, mean, covariance, measurement):
        """
        基于观测更新状态估计
        """
        # 计算卡尔曼增益
        projected_mean, projected_covariance = self.project(mean, covariance)
        chol_factor, lower = torch.linalg.cholesky_ex(projected_covariance)
        kalman_gain = torch.cholesky_solve(
            torch.matmul(covariance, self.H.T), chol_factor, upper=not lower
        ).T
        
        # 计算状态更新
        innovation = measurement - projected_mean
        new_mean = mean + torch.matmul(innovation, kalman_gain.T)
        
        # 计算更新后的协方差
        I = torch.eye(mean.size(0), dtype=torch.float32)
        new_covariance = torch.matmul(I - torch.matmul(kalman_gain, self.H), covariance)
        
        return new_mean, new_covariance
3.2 轨迹管理类
class TrackState:
    """轨迹状态枚举类"""
    Tentative = 1  # 暂定状态
    Confirmed = 2  # 确认状态
    Deleted = 3    # 已删除状态

class Track:
    """
    单个目标轨迹管理类
    """
    def __init__(self, mean, covariance, track_id, n_init, max_age, 
                 feature=None, oc_sort_config=None):
        self.mean = mean  # 状态向量
        self.covariance = covariance  # 协方差矩阵
        self.track_id = track_id  # 轨迹ID
        self.hits = 1  # 命中次数
        self.age = 1  # 轨迹存在时间
        self.state = TrackState.Tentative  # 初始状态为暂定
        self.n_init = n_init  # 确认轨迹所需的连续命中次数
        self.max_age = max_age  # 最大未命中次数
        
        # 轨迹历史
        self.history = [mean.clone()]
        self.observations = []  # 观测历史
        self.features = []  # 特征历史
        if feature is not None:
            self.features.append(feature)
            
        # OC-SORT特定配置
        self.oc_sort_config = oc_sort_config or {
            'momentum': 0.2,  # 运动方向一致性权重
            'deltat': 3,  # 计算运动方向的时间窗口
            'asso_func': 'iou',  # 关联函数类型
            'inertia': 0.2  # 运动惯性权重
        }
        
        # 运动方向相关
        self.velocity = None  # 当前速度向量
        self.direction = None  # 当前运动方向
        
    def predict(self, kf):
        """
        使用卡尔曼滤波器预测下一时刻状态
        """
        self.mean, self.covariance = kf.predict(self.mean, self.covariance)
        self.history.append(self.mean.clone())
        self.age += 1
        
        # 更新运动方向
        self._update_direction()
        
    def update(self, kf, detection, feature=None):
        """
        根据检测结果更新轨迹
        """
        self.mean, self.covariance = kf.update(self.mean, self.covariance, detection)
        self.history.append(self.mean.clone())
        self.observations.append(detection.clone())
        self.hits += 1
        
        if feature is not None:
            self.features.append(feature)
            
        # 更新状态
        if self.state == TrackState.Tentative and self.hits >= self.n_init:
            self.state = TrackState.Confirmed
            
        # 更新运动方向
        self._update_direction()
            
    def mark_missed(self):
        """
        标记轨迹未匹配到检测
        """
        if self.state == TrackState.Tentative:
            self.state = TrackState.Deleted
        elif self.age > self.max_age:
            self.state = TrackState.Deleted
            
    def is_tentative(self):
        return self.state == TrackState.Tentative
    
    def is_confirmed(self):
        return self.state == TrackState.Confirmed
    
    def is_deleted(self):
        return self.state == TrackState.Deleted
    
    def to_tlbr(self):
        """
        将状态向量转换为边界框格式 [x1, y1, x2, y2]
        """
        ret = self.mean.clone()
        w = ret[2] * ret[3]  # 宽 = 宽高比 * 高
        h = ret[3]           # 高
        ret[0] = ret[0] - w / 2  # x1 = x - w/2
        ret[1] = ret[1] - h / 2  # y1 = y - h/2
        ret[2] = ret[0] + w      # x2 = x1 + w
        ret[3] = ret[1] + h      # y2 = y1 + h
        return ret[:4]
    
    def _update_direction(self):
        """
        更新轨迹运动方向
        """
        if len(self.history) < self.oc_sort_config['deltat'] + 1:
            return
            
        # 计算当前位置与deltat帧前位置的差
        current_pos = self.history[-1][:2]
        prev_pos = self.history[-self.oc_sort_config['deltat'] - 1][:2]
        direction = current_pos - prev_pos
        
        # 归一化方向向量
        norm = torch.norm(direction)
        if norm > 1e-6:
            self.direction = direction / norm
            # 计算速度 (位置变化/时间)
            self.velocity = direction / self.oc_sort_config['deltat']

 

3.3 数据关联模块

def iou_batch(bboxes1, bboxes2):
    """
    计算两组边界框之间的IoU矩阵
    bboxes1: [N, 4] 格式为 [x1, y1, x2, y2]
    bboxes2: [M, 4] 格式为 [x1, y1, x2, y2]
    返回: [N, M] IoU矩阵
    """
    # 扩展维度以广播计算
    bboxes1 = bboxes1.unsqueeze(1)  # [N, 1, 4]
    bboxes2 = bboxes2.unsqueeze(0)  # [1, M, 4]
    
    # 计算交集区域
    inter_min = torch.max(bboxes1[..., :2], bboxes2[..., :2])  # [N, M, 2]
    inter_max = torch.min(bboxes1[..., 2:], bboxes2[..., 2:])  # [N, M, 2]
    inter_wh = torch.clamp(inter_max - inter_min, min=0)       # [N, M, 2]
    inter_area = inter_wh[..., 0] * inter_wh[..., 1]            # [N, M]
    
    # 计算各自的面积
    area1 = (bboxes1[..., 2] - bboxes1[..., 0]) * \
            (bboxes1[..., 3] - bboxes1[..., 1])  # [N, 1]
    area2 = (bboxes2[..., 2] - bboxes2[..., 0]) * \
            (bboxes2[..., 3] - bboxes2[..., 1])  # [1, M]
    
    # 计算并集面积
    union_area = area1 + area2 - inter_area  # [N, M]
    
    # 计算IoU
    iou = inter_area / torch.clamp(union_area, min=1e-6)  # [N, M]
    
    return iou

def linear_assignment(cost_matrix, thresh):
    """
    匈牙利算法解决最优分配问题
    """
    if cost_matrix.size(0) == 0 or cost_matrix.size(1) == 0:
        return np.empty((0, 2), dtype=int), tuple(range(cost_matrix.size(0))), tuple(range(cost_matrix.size(1)))
    
    cost_matrix = cost_matrix.cpu().numpy()
    row_ind, col_ind = linear_sum_assignment(cost_matrix)
    
    matches, unmatched_a, unmatched_b = [], [], []
    
    for i in range(len(row_ind)):
        if cost_matrix[row_ind[i], col_ind[i]] > thresh:
            unmatched_a.append(row_ind[i])
            unmatched_b.append(col_ind[i])
        else:
            matches.append([row_ind[i], col_ind[i]])
    
    if len(matches) == 0:
        matches = np.empty((0, 2), dtype=int)
    else:
        matches = np.array(matches)
        
    if len(unmatched_a) == 0:
        unmatched_a = tuple()
    else:
        unmatched_a = tuple(unmatched_a)
        
    if len(unmatched_b) == 0:
        unmatched_b = tuple()
    else:
        unmatched_b = tuple(unmatched_b)
    
    return matches, unmatched_a, unmatched_b

def associate_detections_to_tracks(detections, tracks, iou_threshold=0.3, 
                                  velocities=None, previous_obs=None, vdc_weight=0.2):
    """
    将检测结果与轨迹进行关联
    """
    if len(tracks) == 0:
        return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0,), dtype=int)
    
    # 计算IoU矩阵
    iou_matrix = iou_batch(detections, torch.stack([t.to_tlbr() for t in tracks]))
    
    # 如果提供了速度信息,则计算运动方向一致性
    if velocities is not None and previous_obs is not None and vdc_weight > 0:
        # 计算当前检测与历史观测之间的方向
        detection_centers = (detections[:, :2] + detections[:, 2:]) / 2
        prev_obs_centers = previous_obs[:, :2]
        
        # 计算方向向量
        directions = detection_centers - prev_obs_centers
        norms = torch.norm(directions, dim=1, keepdim=True)
        directions = directions / torch.clamp(norms, min=1e-6)
        
        # 计算方向一致性代价
        velocity_cost = torch.zeros_like(iou_matrix)
        for i in range(len(detections)):
            for j in range(len(tracks)):
                if tracks[j].direction is not None:
                    # 计算方向余弦相似度 (值越大越相似)
                    cos_sim = torch.dot(directions[i], tracks[j].direction)
                    # 转换为代价 (值越小越相似)
                    velocity_cost[i, j] = 1.0 - cos_sim
        
        # 合并IoU和方向一致性代价
        cost_matrix = (1 - vdc_weight) * (1 - iou_matrix) + vdc_weight * velocity_cost
    else:
        # 仅使用IoU作为代价
        cost_matrix = 1 - iou_matrix
    
    # 设置阈值并进行匈牙利算法分配
    matches, unmatched_dets, unmatched_tracks = linear_assignment(cost_matrix, thresh=1 - iou_threshold)
    
    return matches, unmatched_dets, unmatched_tracks

 

3.4 OC-SORT 主类实现

class OCSORT:
    """
    OC-SORT算法实现
    """
    def __init__(self, det_thresh=0.4, max_age=30, min_hits=3, 
                 iou_threshold=0.3, delta_t=3, asso_func="iou", inertia=0.2,
                 use_byte=False):
        self.det_thresh = det_thresh
        self.max_age = max_age
        self.min_hits = min_hits
        self.iou_threshold = iou_threshold
        self.delta_t = delta_t
        self.asso_func = asso_func
        self.inertia = inertia
        self.use_byte = use_byte
        
        self.kf = KalmanFilter()
        self.tracks = []
        self._next_id = 1
        
        # 存储上一帧的观测结果,用于计算运动方向
        self.previous_obs = {}
        
    def update(self, dets, scores, classes=None, features=None):
        """
        更新跟踪结果
        dets: 检测框 [N, 4],格式为 [x1, y1, x2, y2]
        scores: 置信度 [N]
        classes: 类别 [N] (可选)
        features: 特征 [N, feature_dim] (可选)
        """
        # 过滤低分检测
        valid_indices = scores > self.det_thresh
        dets = dets[valid_indices]
        scores = scores[valid_indices]
        if classes is not None:
            classes = classes[valid_indices]
        if features is not None:
            features = features[valid_indices]
            
        # 提取当前帧的检测中心
        current_obs = {}
        
        # 预测轨迹
        for track in self.tracks:
            track.predict(self.kf)
            
        # 第一阶段关联:IoU匹配
        if len(dets) > 0 and len(self.tracks) > 0:
            # 准备用于关联的轨迹信息
            track_indices = [i for i, track in enumerate(self.tracks) if track.is_confirmed()]
            confirmed_tracks = [self.tracks[i] for i in track_indices]
            
            # 提取上一帧的观测结果用于运动方向计算
            velocities = torch.zeros((len(confirmed_tracks), 2), dtype=torch.float32)
            previous_obs = torch.zeros((len(confirmed_tracks), 4), dtype=torch.float32)
            has_velocity = [False] * len(confirmed_tracks)
            
            for i, track in enumerate(confirmed_tracks):
                if track.track_id in self.previous_obs and track.velocity is not None:
                    velocities[i] = track.velocity
                    previous_obs[i] = self.previous_obs[track.track_id]
                    has_velocity[i] = True
            
            # 关联检测与轨迹
            matches, unmatched_dets, unmatched_tracks = associate_detections_to_tracks(
                dets, [self.tracks[i] for i in track_indices], 
                iou_threshold=self.iou_threshold,
                velocities=velocities if any(has_velocity) else None,
                previous_obs=previous_obs if any(has_velocity) else None,
                vdc_weight=self.inertia
            )
            
            # 转换为全局轨迹索引
            matches = [(track_indices[i], j) for i, j in matches]
            unmatched_tracks = [track_indices[i] for i in unmatched_tracks]
            
            # 更新匹配的轨迹
            for track_idx, det_idx in matches:
                self.tracks[track_idx].update(
                    self.kf, dets[det_idx], features[det_idx] if features is not None else None
                )
                # 记录当前观测
                current_obs[self.tracks[track_idx].track_id] = dets[det_idx]
        else:
            matches = []
            unmatched_dets = list(range(len(dets)))
            unmatched_tracks = list(range(len(self.tracks)))
            
        # 处理未匹配的检测
        for det_idx in unmatched_dets:
            mean, covariance = self.kf.initiate(dets[det_idx])
            self.tracks.append(Track(
                mean, covariance, self._next_id, self.min_hits, self.max_age,
                features[det_idx] if features is not None else None,
                oc_sort_config={
                    'momentum': self.inertia,
                    'deltat': self.delta_t,
                    'asso_func': self.asso_func,
                    'inertia': self.inertia
                }
            ))
            self._next_id += 1
            # 记录当前观测
            current_obs[self.tracks[-1].track_id] = dets[det_idx]
            
        # 处理未匹配的轨迹
        for track_idx in unmatched_tracks:
            self.tracks[track_idx].mark_missed()
            
        # 应用以观测为中心的恢复机制 (ORU)
        if self.use_byte and len(unmatched_tracks) > 0 and len(unmatched_dets) > 0:
            # 提取未匹配的轨迹和检测
            tracks = [self.tracks[i] for i in unmatched_tracks if not self.tracks[i].is_tentative()]
            detections = dets[unmatched_dets]
            detection_features = features[unmatched_dets] if features is not None else None
            
            if len(tracks) > 0 and len(detections) > 0:
                # 计算外观相似度 (这里简化处理,实际应用中可使用更复杂的ReID模型)
                if detection_features is not None:
                    track_features = [torch.cat(t.features[-3:]) if len(t.features) > 0 else torch.zeros_like(detection_features[0]) for t in tracks]
                    track_features = torch.stack(track_features)
                    
                    # 计算余弦相似度
                    sim_matrix = torch.matmul(detection_features, track_features.T)
                    
                    # 关联
                    matches_oru, unmatched_dets_oru, unmatched_tracks_oru = linear_assignment(
                        1 - sim_matrix, thresh=0.7  # 外观相似度阈值
                    )
                    
                    # 更新匹配的轨迹
                    for i, j in matches_oru:
                        track_idx = unmatched_tracks[unmatched_tracks_oru[j]]
                        det_idx = unmatched_dets[unmatched_dets_oru[i]]
                        self.tracks[track_idx].update(
                            self.kf, dets[det_idx], features[det_idx] if features is not None else None
                        )
                        # 记录当前观测
                        current_obs[self.tracks[track_idx].track_id] = dets[det_idx]
        
        # 移除已删除的轨迹
        self.tracks = [t for t in self.tracks if not t.is_deleted()]
        
        # 更新上一帧观测结果
        self.previous_obs = current_obs
        
        # 输出确认的轨迹和暂定轨迹
        output_results = []
        for track in self.tracks:
            if track.is_confirmed() or (track.is_tentative() and track.hits >= 1):
                bbox = track.to_tlbr()
                track_id = track.track_id
                output_results.append({
                    'bbox': bbox.cpu().numpy(),
                    'track_id': track_id,
                    'score': scores.max().item() if len(scores) > 0 else 1.0,
                    'class': classes[0].item() if classes is not None and len(classes) > 0 else 0
                })
                
        return output_results

 

4. 使用示例

下面是一个简单的使用示例,展示如何将 OC-SORT 集成到目标检测流程中:

import cv2
import torch

# 假设这是你的目标检测模型
def detect_objects(frame):
    """返回检测框、置信度和类别"""
    # 这里应该是实际的目标检测代码
    # 简化示例,随机生成一些检测结果
    num_detections = torch.randint(3, 10, (1,)).item()
    detections = torch.rand(num_detections, 4) * torch.tensor([frame.shape[1], frame.shape[0], frame.shape[1], frame.shape[0]])
    scores = torch.rand(num_detections)
    classes = torch.zeros(num_detections, dtype=torch.long)  # 假设所有类别都是0
    
    # 确保检测框格式正确 [x1, y1, x2, y2]
    detections[:, 2:] += detections[:, :2]
    
    return detections, scores, classes

# 初始化OC-SORT跟踪器
tracker = OCSORT(det_thresh=0.5, max_age=30, min_hits=3, 
                iou_threshold=0.3, delta_t=3, inertia=0.2)

# 打开视频文件或摄像头
cap = cv2.VideoCapture(0)  # 0表示默认摄像头

while True:
    ret, frame = cap.read()
    if not ret:
        break
        
    # 转换为PyTorch张量
    frame_tensor = torch.from_numpy(frame).permute(2, 0, 1).float() / 255.0
    
    # 目标检测
    detections, scores, classes = detect_objects(frame)
    
    # 多目标跟踪
    tracks = tracker.update(detections, scores, classes)
    
    # 可视化结果
    for track in tracks:
        bbox = track['bbox'].astype(int)
        track_id = track['track_id']
        cls = track['class']
        
        # 绘制边界框
        cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
        
        # 绘制跟踪ID和类别
        cv2.putText(frame, f"ID: {track_id} Cls: {cls}", (bbox[0], bbox[1] - 10),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
    
    # 显示结果
    cv2.imshow('OC-SORT Tracking', frame)
    
    # 按ESC键退出
    if cv2.waitKey(1) == 27:
        break

cap.release()
cv2.destroyAllWindows()

5. 参数调优建议

OC-SORT 有几个关键参数会影响跟踪性能,建议根据实际场景调整:

  1. 检测阈值 (det_thresh):默认 0.4,值越高过滤掉的低置信度检测越多
  2. 最大未匹配帧数 (max_age):默认 30,值越大允许目标长时间遮挡后重新关联
  3. 确认轨迹所需命中次数 (min_hits):默认 3,值越小轨迹确认越快但可能不稳定
  4. IoU 阈值 (iou_threshold):默认 0.3,值越高关联越严格
  5. 运动惯性权重 (inertia):默认 0.2,控制运动方向一致性在关联中的重要性

6. 性能优化建议

  1. 使用更高效的目标检测器(如 YOLOv5/YOLOv8)
  2. 考虑使用轻量级 ReID 模型增强外观匹配能力
  3. 对于实时性要求高的场景,可降低 delta_t 参数值
  4. 在嵌入式设备上部署时,考虑使用模型量化和剪枝技术

通过以上步骤,在 PyTorch 框架下实现一个完整的 OC-SORT 多目标跟踪系统,适用于各种复杂场景下的目标跟踪任务。

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法题(159):快速幂

审题&#xff1a; 本题需要我们计算出(a^b)%c的值&#xff0c;并按照规定格式输出 思路&#xff1a; 方法一&#xff1a;暴力解法 我们直接循环b次计算出a^b,然后再取余c&#xff0c;从而得出最终结果 时间上&#xff1a;会进行2^31次&#xff0c;他的数量级非常大&#xff0c;…

【新品发布】嵌入式人工智能实验箱EDU-AIoT ELF 2正式发布

在万物互联的智能化时代&#xff0c;将AI算法深度植入硬件终端的技术&#xff0c;正悄然改变着工业物联网、智慧交通、智慧医疗等领域的创新边界。为了助力嵌入式人工智能在教育领域实现高质量发展&#xff0c;飞凌嵌入式旗下教育品牌ElfBoard&#xff0c;特别推出嵌入式人工智…

基于javaweb的SpringBoot体检管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

IPD的基础理论与框架——(四)矩阵型组织:打破部门壁垒,构建高效协同的底层

在传统的组织架构中&#xff0c;企业多采用直线职能制&#xff0c;就像一座等级森严的金字塔&#xff0c;信息沿着垂直的层级传递&#xff0c;员工被划分到各个职能部门。这种架构职责清晰、分工明确&#xff0c;在稳定的市场环境中&#xff0c;能让企业高效运作&#xff0c;发…

小程序为什么要安装SSL安全证书

小程序需要部署SSL安全证书&#xff0c;这是小程序开发及运营的强制性要求&#xff0c;也是保障用户数据安全、提升用户体验和满足平台规范的必要措施。 一、平台强制要求 微信小程序官方规范 微信小程序明确要求所有网络请求必须通过HTTPS协议传输&#xff0c;服务器域名需配…

python打卡训练营打卡记录day40

知识点回顾&#xff1a; 彩色和灰度图片测试和训练的规范写法&#xff1a;封装在函数中展平操作&#xff1a;除第一个维度batchsize外全部展平dropout操作&#xff1a;训练阶段随机丢弃神经元&#xff0c;测试阶段eval模式关闭dropout 作业&#xff1a;仔细学习下测试和训练代码…

【清晰教程】利用Git工具将本地项目push上传至GitHub仓库中

Git 是一个分布式版本控制系统&#xff0c;由 Linus Torvalds 创建&#xff0c;用于有效、高速地处理从小到大的项目版本管理。GitHub 是一个基于 Git 的代码托管平台&#xff0c;提供了额外的协作和社交功能&#xff0c;使项目管理更加高效。它们为项目代码管理、团队协作和持…

20250529-C#知识:静态类、静态构造函数和拓展方法

C#知识&#xff1a;静态类、静态构造函数和拓展方法 静态类一般用来编写工具类 1、静态类 用static关键字修饰的类一般充当工具类只能包含静态成员,不能包含静态索引器不能被实例化静态方法只能使用静态成员非静态方法既可以使用非静态成员&#xff0c;也可以使用静态成员 sta…

实验设计与分析(第6版,Montgomery)第4章随机化区组,拉丁方, 及有关设计4.5节思考题4.18~4.19 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第章随机化区组&#xff0c;拉丁方&#xff0c; 及有关设计4.5节思考题4.18~4.19 R语言解题。主要涉及方差分析&#xff0c;拉丁方。 batch <- c(rep("batch1",5), rep(&quo…

【吾爱】逆向实战crackme160学习记录(一)

前言 最近想拿吾爱上的crackme程序练练手&#xff0c;发现论坛上已经有pk8900总结好的160个crackme&#xff0c;非常方便&#xff0c;而且有很多厉害的前辈已经写好经验贴和方法了&#xff0c;我这里只是做一下自己练习的记录&#xff0c;欢迎讨论学习&#xff0c;感谢吾爱论坛…

vue2 + webpack 老项目升级 node v22 + vite + vue2 实战全记录

前言 随着这些年前端技术的飞速发展&#xff0c;几年前的一些老项目在最新的环境下很可能会出现烂掉的情况。如果项目不需要升级&#xff0c;只需要把编译后的文件放在那里跑而不用管的话还好。但是&#xff0c;某一天产品跑过来给你讲要升级某一个功能&#xff0c;你不得不去…

STM32的HAL编码流程总结(上部)

目录 一、GPIO二、中断系统三、USART串口通信四、I2C通信五、定时器 一、GPIO 1.选择调试类型 在SYS中Debug选择Serial Wire模式 2.选择时钟源 在RCC中将HSE和LSH都选择为内部晶振 3.时钟树配置 4.GPIO配置 在芯片图上选择开启的引脚和其功能 配置引脚的各自属性 5.工…

深度学习|pytorch基本运算

【1】引言 pytorch是深度学习常用的包&#xff0c;顾名思义&#xff0c;就是python适用的torch包&#xff0c;在python里面使用时直接import torch就可以调用。 需要注意的是&#xff0c;pytorch包与电脑配置、python版本有很大关系&#xff0c;一定要仔细阅读安装要求、找到…

替代 WPS 的新思路?快速将 Word 转为图片 PDF

在这个数字化办公日益普及的时代&#xff0c;越来越多的人开始关注文档处理工具的功能与体验。当我们习惯了某些便捷操作时&#xff0c;却发现一些常用功能正逐渐变为付费项目——比如 WPS 中的一项实用功能也开始收费了。 这款工具最特别的地方在于&#xff0c;可以直接把 W…

【K8S】K8S基础概念

一、 K8S组件 1.1 控制平面组件 kube-apiserver&#xff1a;公开 Kubernetes HTTP API 的核心组件服务器。 etcd&#xff1a;具备一致性和高可用性的键值存储&#xff0c;用于所有 API 服务器的数据存储。 kube-scheduler&#xff1a;查找尚未绑定到节点的 Pod&#xff0c;并将…

包含Javascript的HTML静态页面调取本机摄像头

在实际业务开发中&#xff0c;需要在带有摄像头的工作机上拍摄施工现场工作过程的图片&#xff0c;然后上传到服务器备存。 这便需要编写可以运行在浏览器上的代码&#xff0c;并在代码中实现Javascript调取摄像头、截取帧保存为图片的功能。 为了使用户更快掌握JS调取摄像头…

PCB设计实践(三十一)PCB设计中机械孔的合理设计与应用指南

一、机械孔的基本概念与分类 机械孔是PCB设计中用于实现机械固定、结构支撑、散热及电气连接的关键结构元件&#xff0c;其分类基于功能特性、制造工艺和应用场景的差异&#xff0c;主要分为以下几类&#xff1a; 1. 金属化机械孔 通过电镀工艺在孔内壁形成导电层&#xff0c;…

【Linux篇章】Linux 进程信号2:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲捕捉信号及OS运行机制)

本篇文章将以一个小白视角&#xff0c;通俗易懂带你了解信号在产生&#xff0c;保存之后如何进行捕捉&#xff1b;以及在信号这个话题中&#xff1b;OS扮演的角色及背后是如何进行操作的&#xff1b;如何理解用户态内核态&#xff1b;还有一些可以引出的其他知识点&#xff1b;…

多功能秒达开源工具箱源码|完全开源的中文工具箱

源码介绍 完全开源的中文工具箱永远的自由软件轻量级运行全平台支持&#xff08;包括ARMv8&#xff09;类似GPT的智能支持高效UI高度集成提供Docker映像和便携式版本支持桌面版开源插件库 下载地址 百度网盘下载 提取码&#xff1a;p9ck ▌本文由 6v6-博客网 整理分享 ▶ 更多…

用nz-tabel写一个合并表格

用nz-tabel写一个合并表格 <nz-table #basicTable [nzData]"tableSearchStatus.dataList" nzBordered><thead><tr><th>班级</th><th>姓名</th><th>年龄</th><th>电话</th></tr></thead&…