目标跟踪中的卡尔曼滤波和匈牙利算法解读。

news2025/7/19 13:28:28

先解读Sort算法:Simple online and realtime tracking
论文地址 https://arxiv.org/abs/1602.00763
代码地址
https://github.com/abewley/sort
https://github.com/YunYang1994/openwork/tree/master/sort

SORT 流程简介:

整个流程如下图所示:在第 1 帧时,人体检测器 detector 输出 3 个 bbox(黑色),模型会分别为这 3 个 bbox 创建卡尔曼滤波追踪器【tracker】 kf1,kf2 和 kf3。所以注意第一帧的追踪器是用目标检测的框创建的,ID也是我们手动赋予的。
对应人的编号为 1,2,3 。在第 2 帧的过程 a 中,如下图frame-2a,这 3 个跟踪器【每个人都有一个tracker】会利用上一帧的状态分别输出棕红色 bbox、黄色 bbox 和 青绿色 bbox。
在这里插入图片描述
由于frame1中三个黑色bbox只是目标检测模型的输出,它们是没有bbox id的,所以,我们需要把目标检测框,和卡尔曼滤波的预测框进行一种关联,使得目标检测框的id就是滤波器的预测框id。
在SORT算法中,关联的核心是目标检测框和滤波器预测框之间的iou + 匈牙利算法的匹配。
我们计算一下Frame1 和 Frame2-a框之间的iou 如下:

iou黑色bbox1黑色bbox2黑色bbox3
棕红色 bbox0.9100
黄色 bbox00.980
青绿色 bbox000.99

上述表格可以抽象为一个矩阵。矩阵中每一个值都是目标检测框到滤波器预测框之间的iou 。
我们若要求使得这个iou矩阵的值最大,那么这个矩阵就被称为利益矩阵profit matrix,若要使之最小,则被称为花费矩阵cost matrix。我们如果想让跟踪状态完美,就要求得这个iou矩阵的最大利益矩阵。
而SORT算法对iou值进行了一个变换,将iou值都变成1-iou,求的是1-iou矩阵的最小值。
这里的1-iou被定义为:目标检测框到滤波器预测框之间的iou距离。并且使用匈牙利算法进行最小化求解,这里用1-iou 或者-iou 都可以算法复杂度为O(n^3)。例子如下:

import numpy as np
from scipy.optimize import linear_sum_assignment

cost_matrix = np.array([[0.09, 1.00, 1.00],
                          [1.00, 0.02, 1.00],
                          [1.00, 1.00, 0.01]])

rows, cols = linear_sum_assignment(cost_matrix)
matches = list(zip(rows, cols))        # [(0, 0), (1, 1), (2, 2)] 得到匹配队列

这样我们就根据iou的值得到了匹配队列。

图解1:
在这里插入图片描述
图解2
在这里插入图片描述

KalmanBoxTracker

注意对应代码一起看

2.1卡尔曼滤波参数

状态变量 x 的设定是一个 7维向量:x=[u, v, s, r, u^, v^, s^]T。u、v
分别表示目标框的中心点位置的 x、y 坐标,s 表示目标框的面积,r 表示目标框的宽高比。 u^ ,v^ ,s^ 分别表示横向、纵向 、面积 s 的运动变化速率

参数初始化
u、v、s、r 初始化:根据第一帧的观测结果进行初始化。
u^ ,v^ ,s^ 初始化:当第一帧开始的时候初始化为0,到后面帧时会根据预测的结果来进行变化。

转换函数1:
def convert_bbox_to_z(bbox): 将 bbox 从 [x1,y1,x2,y2] 格式变成 [x,y,s,r] 4x1格式
转换函数2:
def convert_x_to_bbox(x,score=None): 将 bbox 从 [x,y,s,r] 格式变成 [x1,y1,x2,y2]格式,score是optional项
定义如下:

def convert_bbox_to_z(bbox):            
    """
    将 bbox 从 [x1,y1,x2,y2] 格式变成 [u,v,s,r] 格式,s是框的面积,r是w/h比例
    """
    w = bbox[2] - bbox[0]
    h = bbox[3] - bbox[1]
    x = bbox[0] + w/2.
    y = bbox[1] + h/2.
    s = w * h    #scale is just area
    r = w / float(h)
    return np.array([x, y, s, r]).reshape((4, 1))


def convert_x_to_bbox(x,score=None):     
    """
    将 bbox 从 [u,v,s,r] 格式变成 [x1,y1,x2,y2] 格式
    """
    s = x[2] #w*h
    ratio = x[3] # w/h
    w = np.sqrt(s * r)
    h = s / w 
    if(score==None):
        return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.]).reshape((1,4))
    else:
        return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.,score]).reshape((1,5))

状态转移矩阵 F
F被定义为一个7x7的矩阵,跟踪的目标被一个匀速运动目标,通过 7x7 的状态转移矩阵F 乘以 7*1 的状态变量 x 即可得到一个更新后的 7x1 的状态更新向量x
观测矩阵H
H被定义为一个 4x7 的矩阵,乘以 7x1 的状态更新向量 x 即可得到一个 4x1 的 [u,v,s,r] 的估计值。
协方差矩阵 RPQ
测量噪声的协方差矩阵 R: diag([1,1,10,10].T)
先验估计的协方差矩阵 P:diag([10,10,10,10,1e4,1e4,1e4].T)
过程激励噪声的协方差矩阵 Q:diag([1,1,1,1,0.01,0.01,1e-4].T)

hits = 总的匹配次数
hit_streak 连续匹配次数
time_since_update 连续没有匹配到目标检测框的次数
id = KalmanBoxTracker.count 记录当前追踪器的id

2.2 predict 追踪器预测阶段

在预测阶段,追踪器不仅需要预测 bbox,还要记录它自己的当前匹配情况。如果这个追踪器连续多次预测而没有进行一次更新操作,那么表明该跟踪器可能已经“失活”了。因为它没有和检测框匹配上,说明它之前记录的目标有可能已经消失或者误匹配了。但是也不一定会发生这种情况,还一种结果是目标在连续几帧消失后又出现在画面里。
考虑到这种情况,使用 time_since_update 记录了追踪器连续没有匹配上的次数,该变量在每次 predict 时都会加 1,每次 update 时都会归 0。并且使用了 max_age 设置了追踪器的最大存活期限,如果跟踪器出现超过连续 max_age 帧都没有匹配关联上,
即当 tracker.time_since_update > max_age 时,该跟踪器则会被判定失活而被移除列表

2.3 update 更新阶段

大家都知道,卡尔曼滤波器的更新阶段是使用了观测值 z 来校正误差矩阵和更新卡尔曼增益,并计算出先验估计值和测量值之间的加权结果,该加权结果即为后验估计值。

class KalmanBoxTracker(object):
    """
    This class represents the internal state of individual tracked objects observed as bbox.
    """
    count = 0
    def __init__(self,bbox):
        """
       用初始目标检测框来初始化追踪器
        """
        #定义匀速运动模型
        self.kf = KalmanFilter(dim_x=7, dim_z=4) 
        #状态变量是7维, 观测值是4维的,按照需要的维度构建目标
        # 状态变量x的定义见下文
        self.kf.F = np.array([[1,0,0,0,1,0,0],      # 状态转移矩阵 7x7 维度
                              [0,1,0,0,0,1,0],
                              [0,0,1,0,0,0,1],
                              [0,0,0,1,0,0,0],
                              [0,0,0,0,1,0,0],
                              [0,0,0,0,0,1,0],
                              [0,0,0,0,0,0,1]])

        self.kf.H = np.array([[1,0,0,0,0,0,0],      # 观测矩阵,4x7 维度
                              [0,1,0,0,0,0,0],
                              [0,0,1,0,0,0,0],
                              [0,0,0,1,0,0,0]])

        self.kf.R[2:,2:] *= 10.
        self.kf.P[4:,4:] *= 1000. #give high uncertainty to the unobservable initial velocities
        self.kf.P *= 10.
        self.kf.Q[-1,-1] *= 0.01
        self.kf.Q[4:,4:] *= 0.01

        self.kf.x[:4] = convert_bbox_to_z(bbox)
        self.time_since_update = 0

        self.id = KalmanBoxTracker.count
        KalmanBoxTracker.count += 1

        self.history = [] # 存放着多个  [x1,y1,x2,y2]
        self.hits = 0 # 总的匹配次数
        self.hit_streak = 0 # 连续匹配次数
        self.age = 0

    def update(self,bbox):
        """
        用检测框更新追踪框
        """
        self.time_since_update = 0
        self.history = []
        self.hits += 1
        self.hit_streak += 1
        self.kf.update(convert_bbox_to_z(bbox))  # bbox 是观测值 [x1,y1,x2,y2] --> [u,v,s,r]

    def predict(self):
        """
        Advances the state vector and returns the predicted bounding box estimate.
        """
        if((self.kf.x[6]+self.kf.x[2])<=0):
            self.kf.x[6] *= 0.0
        self.kf.predict()
        self.age += 1
        if(self.time_since_update>0): # 一旦出现不匹配的情况,连续匹配次数归0
            self.hit_streak = 0
        self.time_since_update += 1 # 否则连续匹配次数+1
        self.history.append(convert_x_to_bbox(self.kf.x)) #  # [u,v,s,r] --> [x1,y1,x2,y2]
        return self.history[-1]

    def get_state(self):
        """
        Returns the current bounding box estimate.
        """
        return convert_x_to_bbox(self.kf.x)

3. bbox 关联匹配

bbox 的关联匹配过程在前面已经讲得很详细了,它是将 tracker 输出的预测框(注意是先验估计值)和 detector 输出的检测框相关联匹配起来。输入是 dets: [[x1,y1,x2,y2,score],…] 和 trks: [[x1,y1,x2,y2,tracking_id],…] 以及一个设定的 iou 阈值,该门槛是为了过滤掉那些低重合度的目标。
代码中linear assigment使用的匈牙利算法我们在本文最后面详细介绍。

def associate_detections_to_trackers(dets, trks, iou_threshold = 0.3):
    """
    Assigns detections to tracked object (both represented as bounding boxes)
        dets:
            [[x1,y1,x2,y2,score],...]
        trks:
            [[x1,y1,x2,y2,tracking_id],...]
    Returns 3 lists of matches, unmatched_detections and unmatched_trackers
    """

该过程返回三个列表:
matches(已经匹配成功的追踪器),
unmatched_detections(没有匹配成功的检测目标)
unmatched_trackers(没有匹配成功的跟踪器)

对于已经匹配成功的追踪器,则需要用观测值(目标检测框)去更新校正 tracker 并输出修正后的 bbox对于没有匹配成功的检测目标,则需要新增 tracker 与之对应
对于没有匹配成功的跟踪器,如果长时间处于失活状态,则可以考虑删除了。
所以整理多目标跟踪MOT算法的流程如下:

看完上图流程图,我们来仔细看看objec detection box和 Kalman Filter tracker之间关联的具体逻辑:

def associate_detections_to_trackers(detections,trackers,iou_threshold = 0.3):  

  """
  detections[ x1, y1 ,x2, y2, score, ... ]  Nx5
  trackers [:, x1, y1, x2, y3, tracker_id, ... ] Mx5
  图中IOU Match版块用于将检测与跟踪进行关联
  将目标检测框匹配到滤波器预测框tracker。
  返回 三个列表 matches, unmatched_detections and unmatched_trackers
  """
  if(len(trackers)==0):  #如果跟踪器为空
    return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int)
       
  #初始化检测器与跟踪器IOU Matrix
  iou_matrix = np.zeros((len(detections),len(trackers)),dtype=np.float32) 
  
  
  #计算det 和 tracker 之间的iou matrix
  for d, det in enumerate(detections):
    for t, trk in enumerate(trackers):
      iou_matrix[d,t] = iou(det,trk)  #计算检测器与跟踪器的IOU并赋值给 iou matrix 对应位置
  #最终iou_matrix的shape是NxM
  matched_indices = linear_assignment(-iou_matrix)  #只是粗匹配,后面还要过滤iou低的
  # 这里的linear assignment使用的就是匈牙利算法
  # 加上负号是因为linear_assignment求的是最小代价组合,而我们需要的是IOU最大的组合方式,所以取负号 
  # 参考的是:https://blog.csdn.net/herr_kun/article/details/86509591    
  
 
  unmatched_detections = []    #未匹配上的检测器
  for d, det in enumerate(detections):
    if(d not in matched_indices[:, 0]):  #如果检测器中第d个检测结果不在匹配结果索引中,则d未匹配上
      unmatched_detections.append(d)
      
  unmatched_trackers = []      #未匹配上的跟踪器
  for t, trk in enumerate(trackers):
    if(t not in matched_indices[:,1]):  #如果跟踪器中第t个跟踪结果不在匹配结果索引中,则t未匹配上
      unmatched_trackers.append(t)
 
  # filter out matched pair with low IOU,过滤掉那些IOU较小的匹配对
  matches = []  #存放过滤掉低iou之后的最终匹配结果
  for m in matched_indices:   #遍历粗匹配结果
    if(iou_matrix[m[0], m[1]] < iou_threshold):  
    # m[0]是检测框ID, m[1]是跟踪框ID,如果它们的IOU小于阈值则将它们视为未匹配成功
      unmatched_detections.append(m[0])
      unmatched_trackers.append(m[1])
    else:
      matches.append(m.reshape(1,2))         # 匹配上的则以 [[d,t]...] 形式放入 matches 矩阵
  if(len(matches)==0):           #如果过滤后匹配结果为空,那么返回空的匹配结果
    matches = np.empty((0,2),dtype=int)  
  else:      #如果过滤后匹配结果非空,则按0轴【纵向】继续添加匹配对
    matches = np.concatenate(matches,axis=0) 
 
  # 返回:跟踪成功的矩阵,新增物体的矩阵, 消失物体的矩阵
  return matches, np.array(unmatched_detections), np.array(unmatched_trackers)  
  # matches有2列,第1列是检测框id,第2列是预测框id
  # unmatched_detections有5列,前4列是xyxy,第5列是score
  # 其中跟踪器数组是5列的(最后一列是ID)

将上述所有流程串联起来就得到了SORT算法

class Sort(object):
    def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):
        """
        Sets key parameters for SORT
        """
        self.max_age = max_age      # 在没有目标检测关联的情况下追踪器存活的最大帧数
        self.min_hits = min_hits    # 追踪器初始化前的最小关联检测数量
        self.iou_threshold = iou_threshold

        self.trackers = []          # 用于存储卡尔曼滤波追踪器的列表
        self.frame_count = 0        # 当前追踪帧的编号

    def update(self, dets=np.empty((0, 5))):
        """
        Params:
        输入的是目标检测矩阵,形式是[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]
        这个方法对每一帧都必须使用一次,即使该帧没有检测到任何目标。
        返回相似度矩阵,矩阵最后一列是追踪框ID
        NOTE:输入的检测框个数 和 返回的框个数,可能是不同的
        """
        self.frame_count += 1 # 帧数+1
        trks = [] # 用于存放跟踪预测的 bbox: [x1,y1,x2,y2,id]
        
		# 初始化的时候self.trackers是空的,跳过下面的for循环
        for i, tracker in enumerate(self.trackers): # 遍历卡尔曼跟踪列表
            pos = tracker.predict()[0]   
            # 用卡尔曼跟踪器 trackers预测 bbox
            if not np.any(np.isnan(pos)):     # 如果卡尔曼的预测框有效
                trks.append([pos[0], pos[1], pos[2], pos[3], 0])   # 存放上一帧所有物体预测有效的 bbox
            else:
                self.trackers.remove(tracker)    # 如果无效, 删除该滤波器

        trks = np.array(trks)
        self.trks = trks     # 为了显示跟踪器预测的框,把它拿出来

        # 将目标检测的 bbox 和卡尔曼滤波预测的跟踪 bbox 匹配
        # 获得 跟踪成功的矩阵,新增物体的矩阵,消失物体的矩阵
        matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets, trks, self.iou_threshold)

        # 跟踪成功的物体 bbox 信息更新到对应的卡尔曼滤波器
        for m in matched:
            self.trackers[m[1]].update(dets[m[0], :])

        # 为新增物体创建新的卡尔曼滤波跟踪器
        for i in unmatched_dets:
            tracker = KalmanBoxTracker(dets[i,:])
            self.trackers.append(tracker)

        # 跟踪器更新校正后,输出最新的 bbox 和 id
        ret = []
        for tracker in self.trackers:
            if (tracker.time_since_update < 1) and (tracker.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
                d = tracker.get_state()[0]
                ret.append([d[0], d[1], d[2], d[3], tracker.id+1]) # +1 as MOT benchmark requires positive

            # 长时间离开画面/跟踪失败的物体从卡尔曼跟踪器列表中删除
            if(tracker.time_since_update > self.max_age):
                self.trackers.remove(tracker)

        # 返回当前画面中所有被跟踪物体的 bbox 和 id,矩阵形式为 [[x1,y1,x2,y2,id]...]
        return np.array(ret) if len(ret) > 0 else np.empty((0,5))

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

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

相关文章

网络是怎么连接笔记(二)用电信号传输TCP/IP数据

文章目录介绍创建套接字连接服务器收发数据从服务器断开并删除套接字IP与以太网的包收发操作&#xff08;待更新&#xff09;UDP协议的收发操作&#xff08;待更新&#xff09;介绍 在了解了HTTP请求是如何产生的&#xff0c;以及网址是如何跳转到我们想要看到的页面&#xff…

普元ESO 逻辑流/页面流数据类型选择java--浏览转圈卡死

不知道什么时候开始的 前阵子在选择数据类型需要选择一个普元基本类型里没有的java类型的时候 它卡住了,虽然我对lj普元的容忍度较高,但是在等了五分钟以后还在那转我就忍不了了 在经历了重启eos 重启电脑 关闭所有软件运行eos后,最后忘了咋折腾的反正是整上这个类型了当时确…

Unity使用NatML的NatDevice功能调用外部摄像机

Unity使用NatML的NatDevice功能调用外部摄像机NatCamNatDevice广泛和轻量级在Unity中使用NatDevice导入NatDevice指定访问密钥运行摄像头预览示例测试结果NatCam 最开始在Unity中使用WebCamTexture获取外部摄像机的画面。后来发现WebCamTexture的功能过于简单&#xff0c;外部…

拥塞管理与拥塞避免

拥塞管理与拥塞避免背景来源产生情景队列技术FIFO&#xff1a;First In First OutRR&#xff1a;Round RobinWRR&#xff1a;Weight Round RobinPQ&#xff08;Priority Queuing&#xff09;CQ&#xff08;Custom Queuing&#xff09;WFQ&#xff08;Weight Fair Queuing&#…

入托不焦虑 | 如何帮宝宝更好地融入托班?

上托班是教育重要的一环&#xff0c;是宝宝开始适应集体过程的起点&#xff0c;宝爸宝妈也是忧喜交加&#xff1a;高兴的是宝宝慢慢长大开始上学了&#xff1b;忧虑的是从没有离开家的宝宝会不会适应托班生活呢&#xff1f; 今天我们就来分享一下如何引导宝宝更快地适应园区生…

【Unity】程序集Assembly模块化开发

笔者按&#xff1a;使用Unity版本为2021.3LTS&#xff0c;与其他版本或有异同。请仅做参考 一、简述。 本文是笔者在学习使用Unity引擎的过程中&#xff0c;产学研的一个笔记。由笔者根据官方文档Unity User Manual 2021.3 (LTS)/脚本/Unity 架构/脚本编译/程序集定义相关部分结…

【Spring6】IoC容器之基于注解管理Bean

3.3、基于注解管理Bean&#xff08;☆&#xff09; 从 Java 5 开始&#xff0c;Java 增加了对注解&#xff08;Annotation&#xff09;的支持&#xff0c;它是代码中的一种特殊标记&#xff0c;可以在编译、类加载和运行时被读取&#xff0c;执行相应的处理。开发人员可以通过…

Java基本数据类型变量自动提升、强制类型转换、String基本类型使用

文章目录基本数据类型变量自动提升特殊情况强制类型转换String基本类型使用基本数据类型变量自动提升 规则&#xff1a; 将取值范围小&#xff08;或容量小&#xff09;的类型自动提升为取值范围大&#xff08;或容量大&#xff09;的类型 。 byte、short、char-->int-->…

HCIP---回顾HCIA

HCIA回顾&#xff1a; 抽象语言---编码 编码---二进制 二进制---电信号 处理电信号 OSI参考模型----OSI/RM (Open System Interconnect-----开放式系统互连) 应用层&#xff1a;为终端用户提供网络服务接口 表示层&#xff1a;提供数据格式转换服务 会话层&#xff1a…

基于深度学习的鸟类检测识别系统(含UI界面,Python代码)

摘要&#xff1a;鸟类识别是深度学习和机器视觉领域的一个热门应用&#xff0c;本文详细介绍基于YOLOv5的鸟类检测识别系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实现代码以及PyQt的UI界面。在界面中可以选择各种鸟类图片、视频以及开启摄像头进行检测识别…

基于同步整流技术的Buck开关电源设计方法

基于同步整流技术的Buck开关电源设计方法 典型的Buck电路 同步整流的Buck电路 摘要 B u c k 变换器作为一种基本的开关电源变换器&#xff0c;在电力变换场合具有广泛的应用。 为解决 B u c k 变换器工作在电感电流连续状态下&#xff0c;续流二极管关断时存在较大的反向电流…

重资产模式和物流网络将推动京东第四季度利润率增长

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 强劲的2022年第三季度财务业绩 2022年11月18日&#xff0c;京东&#xff08;JD&#xff09;公布了2022年第三季度财务业绩&#xff0c;净收入为2435亿元人民币&#xff0c;增长了11.4%。净服务收入为465亿元人民币&#xf…

【C++知识点】异常处理

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;C/C知识点 &#x1f4e3;专栏定位&#xff1a;整理一下 C 相关的知识点&#xff0c;供大家学习参考~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;…

深度学习知识点全面总结_深度学习总结

深度学习知识点全面总结_深度学习总结 神经网络与深度学习结构(图片选自《神经网络与深度学习》一邱锡鹏) 目录 常见的分类算法 一、深度学习概念 1.深度学习定义 2.深度学习应用 3.深度学习主要术语 二、神经网络基础 1. 神经网络组成 感知机 多层感知机 3.前向传播…

【JS代码优化二】ES6 数组和对象篇

Vue3 Vite VueRouter Pinia Axios Element Plus 项目实战&#xff08;持续更新中…&#xff09; 序&#xff1a;如何让代码看起来更优雅&#xff1f;代码是由文字堆叠起来的可以被机器执行的程序。它记载着相关信息&#xff08;状态&#xff09;、表达相关的情绪&#xf…

vue中引入路径用法及说明

引入路径的用法及说明Vue文件中引用路径的介绍1、路径 ././当前文件同级目录2、路径 …/…/当前文件上一级目录3、符号 的作用是在你引入模块时&#xff0c;可以使用 代替 /src 目录&#xff0c;避免易错的相对路径。Vue中使用1. vue.config.js配置文件中使用chainWebpack(con…

Go语言学习的第三天--上部分(基础用法)

前两天经过不断度娘&#xff0c;与对up主的跟踪学习了解了go的历史&#xff0c;今天开始了go的基础&#xff01;&#xff01;本章主要是go 的注释、变量及常量的梳理一、注释不管什么语言都有自己的注释&#xff0c;go也不例外 &#xff01;&#xff01;单行注释 // 多行注释 …

22- Pytorch实现天气分类 (Pytorch系列) (项目二十二)

项目要点 4种天气数据的分类: cloudy, rain, shine, sunrise.all_img_path glob.glob(rG:\01-project\08-深度学习\day 56 迁移学习\dataset/*.jpg) # 指定文件夹 # import glob获取随机数列: index np.random.permutation(len(all_img_path))建立数组和索引的关…

Java操作数据库基本原理

- 四年前存稿 Java操作数据库基本原理概述 全称Java Database Connectivity&#xff0c;Java的数据库连接&#xff0c;使用Java语言操作数据库&#xff0c;定义了操作所有关系型数据库规则(接口) 使用步骤 我的mysql是8版本的&#xff0c;使用jar包时必须使用8版本的&#x…

6年Android开发最终被优,事后加入车载开发,开启新起点~

如今传统Android 开发行业的岗位越发紧张了&#xff0c;经过去年一些互联网大厂的部门人员优化、开源截流等操作&#xff0c;加快了内卷的速度&#xff0c;原本坐山观虎斗我&#xff0c;没想到也被卷入其中。 1. Android 开发6年&#xff0c;无情中招 就去年年底&#xff0c;…