ultralytics框架实现ByteTrack目标追踪算法

news2024/11/16 0:55:39

ultralytics框架中,提供了两种用于目标追踪的算法,分别是ByteTrack算法与Botsort算法,这两种算法都是在Sort算法的基础上改进的,今天,我们学习一下ByteTrack算法。

存在问题

首先,我们看下ByteTrack所解决的问题:

  1. 遮挡目标
    低置信度的检测框有时确实可以指示目标的存在,尤其是在目标被部分遮挡或者遮挡物很大时。丢弃这些低置信度的检测框可能会导致漏检目标,因为算法无法正确地识别被遮挡的目标。

  2. 轨迹断裂
    移除低置信度的检测框可能导致轨迹的不连续性,因为目标在一帧中被识别出来,在另一帧中却被移除了。这可能会导致跟踪算法无法正确地维持目标的标识符,从而产生碎片化的轨迹,使跟踪结果不连贯。

解决方案

因此,ByteTrack采用高低检测框进行分级匹配,从而有效的解决上述问题,此外,ByteTrack算法是一种基于目标检测的追踪算法,和其他非ReID的算法一样,仅仅使用目标追踪所得到的bbox进行追踪,因此,其与Sort算法流程一致,使用卡尔曼滤波预测边界框,然后使用匈牙利算法进行目标和轨迹间的匹配。故而,其具有以下特点:

  1. 没有使用ReID特征计算外观相似度(DeepSort算法使用外观特征,因此其速度慢一些)
  2. 非深度方法,不需要训练(同上,DeepSort算法需要训练一个特征提取网络)
  3. 利用高分框和低分框之间的区别和匹配,有效解决遮挡问题(这是ByteTrack的一个创新点)

那么,其相较于Sort算法又有哪些改变呢(下图红色框部分)?

ByteTrack算法的最大创新点就是对低分框的使用,作者认为低分框可能是对物体遮挡时产生的框,直接将低分框抛弃会影响性能,所以作者使用低分框对追踪算法进行了二次匹配,有效优化了追踪过程中因为遮挡造成换id的问题:

结构流程

ByteTrack的流程图结构如下:

在这里插入图片描述

算法流程

ByteTrack算法框架流程如下:

在这里插入图片描述

DeBug流程

那么,在ultralytics框架中是如何实现的呢?我们通过DeBug的形式来看一下:

ultralytics\cfg\trackers\bytetrack.yaml

关于bytetrack的相关配置参数如下:

tracker_type: bytetrack # tracker type, ['botsort', 'bytetrack']
track_high_thresh: 0.5 # threshold for the first association
track_low_thresh: 0.1 # threshold for the second association
new_track_thresh: 0.6 # threshold for init new track if the detection does not match any tracks
track_buffer: 30 # buffer to calculate the time when to remove tracks
match_thresh: 0.8 # threshold for matching tracks

ultralytics\ultralytics\trackers\track.py的on_predict_start方法

随后,根据上述参数完成Tracker的初始化:

tracker = check_yaml(predictor.args.tracker)#读取配置文件
cfg = IterableSimpleNamespace(**yaml_load(tracker))

for _ in range(predictor.dataset.bs):
        tracker = TRACKER_MAP[cfg.tracker_type](args=cfg, frame_rate=30)
        trackers.append(tracker)
        if predictor.dataset.mode != "stream":  # only need one tracker for other modes.
            break

在这里插入图片描述

ultralytics\engine\predictor.py的stream_inference方法

上述的过程是完成了track的初始化,将video转换为dataset,进行检测:

for self.batch in self.dataset:#将video切分,变为dataset的形式,bs=1,即一张张进行
                self.run_callbacks("on_predict_batch_start")#batch内容如下:
                paths, im0s, s = self.batch  #video地址,每帧图像,video保存地址
                # Preprocess
                with profilers[0]:
                    im = self.preprocess(im0s)#图像前处理
                # Inference
                with profilers[1]:
                    preds = self.inference(im, *args, **kwargs)#将图像进行推理
                    if self.args.embed:
                        yield from [preds] if isinstance(preds, torch.Tensor) else preds  # yield embedding tensors
                        continue
                # Postprocess
                with profilers[2]:
                    self.results = self.postprocess(preds, im, im0s)#结果后处理
                self.run_callbacks("on_predict_postprocess_end")#执行相应的方法
                # Visualize, save, write results
                n = len(im0s)
                for i in range(n):
                    self.seen += 1
                    self.results[i].speed = {#这里是计算相应的速度
                        "preprocess": profilers[0].dt * 1e3 / n,#前处理速度
                        "inference": profilers[1].dt * 1e3 / n,#推理速度
                        "postprocess": profilers[2].dt * 1e3 / n,#后处理速度
                    }
                    if self.args.verbose or self.args.save or self.args.save_txt or self.args.show:
                        s[i] += self.write_results(i, Path(paths[i]), im, s)#输出视频保存,不断累计输入
                # Print batch results
                if self.args.verbose:
                    LOGGER.info("\n".join(s))
                self.run_callbacks("on_predict_batch_end")
                yield from self.results

从转换为datasetvideo中读取的batch

在这里插入图片描述

v

第一帧处理结果

图像的预测结果如下:

在这里插入图片描述

后处理的结果保存到self.results

在这里插入图片描述

self.run_callbacks("on_predict_postprocess_end")中包含的方法如下:

在这里插入图片描述

第二帧处理结果

在第二帧中,检测出的结果共有16个

ultralytics\ultralytics\trackers\track.py的on_predict_postprocess_end方法

顾名思义,该方法是预测的后处理过程,其执行跟踪器的更新操作

on_predict_postprocess_end方法定义如下:

New = 0
Tracked = 1
Lost = 2
Removed = 3
def on_predict_postprocess_end(predictor: object, persist: bool = False) -> None:
    path, im0s = predictor.batch[:2]#获取batch中的信息
    is_obb = predictor.args.task == "obb"
    is_stream = predictor.dataset.mode == "stream"
    for i in range(len(im0s)):#第一帧在只有一张图像
        tracker = predictor.trackers[i if is_stream else 0]
        vid_path = predictor.save_dir / Path(path[i]).name
        if not persist and predictor.vid_path[i if is_stream else 0] != vid_path:
            tracker.reset()
            predictor.vid_path[i if is_stream else 0] = vid_path
        det = (predictor.results[i].obb if is_obb else predictor.results[i].boxes).cpu().numpy()#从predictor的结果中获取检测结果
        if len(det) == 0:
            continue
        tracks = tracker.update(det, im0s[i])#更新轨迹,tracks的维度为(10,8),即10个目标,8为信息
        if len(tracks) == 0:
            continue
        idx = tracks[:, -1].astype(int)
        predictor.results[i] = predictor.results[i][idx]#更新predictor中的值,
        update_args = {"obb" if is_obb else "boxes": torch.as_tensor(tracks[:, :-1])}
        predictor.results[i].update(**update_args)#更新值,这个值便是存储用于展示的轨迹信息

第一帧图像轨迹结果

检测结果共有17个,其中10个为高置信度,7个位低置信度

随后,经过ByteTrack方法后,得到的tracks的值如下,其中前4位是box位置,后面依次为track_id、置信度、类别以及索引

在这里插入图片描述

更新完predictor的值后为10个

在这里插入图片描述

第二帧图像轨迹结果

检测结果有16个,其中高置信度为11个目标

在这里插入图片描述

第三帧图像轨迹结果

检测结果共有20个,最终的轨迹如下:

在这里插入图片描述

ultralytics\trackers\byte_tracker.py的update方法

ByteTrack的具体更新方法如下:

def update(self, results, img=None):
        """Updates object tracker with new detections and returns tracked object bounding boxes."""
        self.frame_id += 1
        activated_stracks = []
        refind_stracks = []
        lost_stracks = []
        removed_stracks = []

        scores = results.conf
        bboxes = results.xywhr if hasattr(results, "xywhr") else results.xywh
        # Add index
        bboxes = np.concatenate([bboxes, np.arange(len(bboxes)).reshape(-1, 1)], axis=-1)
        cls = results.cls
		#根据置信度划分高低
        remain_inds = scores >= self.args.track_high_thresh#保留的boxid,即高置信度
        inds_low = scores > self.args.track_low_thresh
        inds_high = scores < self.args.track_high_thresh
		#按照high和low来划分box
        inds_second = inds_low & inds_high#低置信度id,其值位True&False列表
        #对应的box,score以及cls
        dets_second = bboxes[inds_second]
        dets = bboxes[remain_inds]
        scores_keep = scores[remain_inds]
        scores_second = scores[inds_second]
        cls_keep = cls[remain_inds]
        cls_second = cls[inds_second]
		#初始化track
        detections = self.init_track(dets, scores_keep, cls_keep, img)
        # Add newly detected tracklets to tracked_stracks  添加新的检测目标放到轨迹中
        unconfirmed = []
        tracked_stracks = []  # type: list[STrack]  第一帧不执行
        for track in self.tracked_stracks:#事实上是上一帧的目标
            if not track.is_activated:
                unconfirmed.append(track)
            else:
                tracked_stracks.append(track)
        # Step 2: First association, with high score detection boxes 利用high进行首次匹配
        strack_pool = self.joint_stracks(tracked_stracks, self.lost_stracks)  #strack_pool的值来源于tracked_stracks,即上一帧
        # Predict the current location with KF   利用KF进行预测,其与Sort方法是一致的
        self.multi_predict(strack_pool) #更新strack_pool中的mean和covariance
        if hasattr(self, "gmc") and img is not None:#不执行
            warp = self.gmc.apply(img, dets)
            STrack.multi_gmc(strack_pool, warp)
            STrack.multi_gmc(unconfirmed, warp)

        dists = self.get_dists(strack_pool, detections)#创建代价矩阵 特征维度(10,11)
        matches, u_track, u_detection = matching.linear_assignment(dists, thresh=self.args.match_thresh)#计算目标与轨迹的匹配,这里的轨迹与Sort中一样,也是使用的预测完的轨迹

        for itracked, idet in matches:#根据匹配结果进行修正更新track
            track = strack_pool[itracked]
            det = detections[idet]
            if track.state == TrackState.Tracked:
                track.update(det, self.frame_id)#修改track
                activated_stracks.append(track)#激活的track
            else:
                track.re_activate(det, self.frame_id, new_id=False)
                refind_stracks.append(track)#重新找到的目标
        # Step 3: Second association, with low score detection boxes association the untrack to the low score detections
        detections_second = self.init_track(dets_second, scores_second, cls_second, img)#第二次匹配的检测结果初始化
        r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]#未匹配上的轨迹重新匹配,此处没有未匹配上的,故为 【】
        # TODO
        dists = matching.iou_distance(r_tracked_stracks, detections_second)#构造代价矩阵,未匹配轨迹与低置信度目标故维度 为[0,5]
        matches, u_track, u_detection_second = matching.linear_assignment(dists, thresh=0.5)
        for itracked, idet in matches:#更新第二次匹配成果的结果
            track = r_tracked_stracks[itracked]
            det = detections_second[idet]
            if track.state == TrackState.Tracked:
                track.update(det, self.frame_id)#修改轨迹
                activated_stracks.append(track)
            else:
                track.re_activate(det, self.frame_id, new_id=False)
                refind_stracks.append(track)

        for it in u_track:#没有匹配的轨迹(目标消失了)
            track = r_tracked_stracks[it]
            if track.state != TrackState.Lost:
                track.mark_lost()
                lost_stracks.append(track)
        # Deal with unconfirmed tracks, usually tracks with only one beginning frame
        detections = [detections[i] for i in u_detection]#没有匹配的目标
        dists = self.get_dists(unconfirmed, detections)#创建代价矩阵 ,没有确定态的轨迹和未匹配的目标
        matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)#匹配结果,未匹配态轨迹以及未匹配检测框
        for itracked, idet in matches:#匹配成果更正修改轨迹
            unconfirmed[itracked].update(detections[idet], self.frame_id)
            activated_stracks.append(unconfirmed[itracked])
        for it in u_unconfirmed:#未确认态
            track = unconfirmed[it]
            track.mark_removed()
            removed_stracks.append(track)
        # Step 4: Init new stracks
        for inew in u_detection:#未匹配的检测框
            track = detections[inew]
            if track.score < self.args.new_track_thresh:
                continue
            track.activate(self.kalman_filter, self.frame_id)
            activated_stracks.append(track)
        # Step 5: Update state
        for track in self.lost_stracks:
            if self.frame_id - track.end_frame > self.max_time_lost:
                track.mark_removed()
                removed_stracks.append(track)

        self.tracked_stracks = [t for t in self.tracked_stracks if t.state == TrackState.Tracked]#用于显示的 track
        self.tracked_stracks = self.joint_stracks(self.tracked_stracks, activated_stracks)
        self.tracked_stracks = self.joint_stracks(self.tracked_stracks, refind_stracks)
        self.lost_stracks = self.sub_stracks(self.lost_stracks, self.tracked_stracks)
        self.lost_stracks.extend(lost_stracks)
        self.lost_stracks = self.sub_stracks(self.lost_stracks, self.removed_stracks)
        self.tracked_stracks, self.lost_stracks = self.remove_duplicate_stracks(self.tracked_stracks, self.lost_stracks)
        self.removed_stracks.extend(removed_stracks)
        if len(self.removed_stracks) > 1000:
            self.removed_stracks = self.removed_stracks[-999:]  # clip remove stracks to 1000 maximum

        return np.asarray([x.result for x in self.tracked_stracks if x.is_activated], dtype=np.float32)

第一帧轨迹更新结果

第一帧预测的目标的box:

在这里插入图片描述

高置信度与低置信度目标

在这里插入图片描述

按照high和low来划分box,第二次匹配的box

在这里插入图片描述

这里我们看到detection的值如下,这个OT_0之类的 代表什么意思呢,我们通过init_track方法可知其到底是如何做的:

detections = self.init_track(dets, scores_keep, cls_keep, img)
def init_track(self, dets, scores, cls, img=None):
        """Initialize object tracking with detections and scores using STrack algorithm."""
        return [STrack(xyxy, s, c) for (xyxy, s, c) in zip(dets, scores, cls)] if len(dets) else []
class STrack(BaseTrack):
    """
    Attributes:
        shared_kalman (KalmanFilterXYAH): Shared Kalman filter that is used across all STrack instances for prediction.
        _tlwh (np.ndarray): Private attribute to store top-left corner coordinates and width and height of bounding box.
        kalman_filter (KalmanFilterXYAH): Instance of Kalman filter used for this particular object track.
        mean (np.ndarray): Mean state estimate vector.
        covariance (np.ndarray): Covariance of state estimate.
        is_activated (bool): Boolean flag indicating if the track has been activated.
        score (float): Confidence score of the track.
        tracklet_len (int): Length of the tracklet.
        cls (any): Class label for the object.
        idx (int): Index or identifier for the object.
        frame_id (int): Current frame ID.
        start_frame (int): Frame where the object was first detected.
    """

    shared_kalman = KalmanFilterXYAH()

    def __init__(self, xywh, score, cls):
        """Initialize new STrack instance."""
        super().__init__()
        # xywh+idx or xywha+idx
        assert len(xywh) in {5, 6}, f"expected 5 or 6 values but got {len(xywh)}"
        self._tlwh = np.asarray(xywh2ltwh(xywh[:4]), dtype=np.float32)
        self.kalman_filter = None
        self.mean, self.covariance = None, None
        self.is_activated = False

        self.score = score
        self.tracklet_len = 0
        self.cls = cls
        self.idx = xywh[-1]
        self.angle = xywh[4] if len(xywh) == 6 else None

在这里插入图片描述
在这里插入图片描述

由于是第一帧,因此大多数都是空。

第二帧轨迹跟踪结果

高置信度匹配结果

检测结果共有16个,其中高置信度的有11个,在上一帧中,目标有17个,高置信度的有10个
根据匹配结果,得到第一帧轨迹与第二帧目标的匹配结果如下:

没有匹配上的检测目标是9号

在这里插入图片描述

经过更新后的track中的值,可以看到mean后面的四位不再是0了,这与Sort算法是一样的

在这里插入图片描述

跟踪上的track

在这里插入图片描述

算法流程

算法的主要思路就是创建追踪的轨迹,然后利用追踪的轨迹对每一帧的目标进行匹配,逐帧来匹配目标,从而形成完整的轨迹。

首先,我们需要先理清关键的两个单元,第一个是追踪的轨迹,是代码从第一帧开始创建,包含了持续追踪和中断追踪的一切轨迹;第二个是当前帧的边界框,当前帧的边界框是仅从当前帧中获取到的边界框,不包含任何以往帧的信息。

激活状态:激活追踪了两帧以上的目标框(包括第一帧时目标框新建的轨迹)

未激活状态:在视频中间出现的新轨迹,并且暂未匹配到轨迹的第二点

新轨迹:新生成的轨迹

已追踪轨迹:在前一帧成功追踪的轨迹

失追轨迹:在前n帧失去追踪的轨迹(n<=30)

已删除轨迹:在前n帧失去追踪的轨迹(n>30)

当开始扫描第一帧时,此时还没有任何的轨迹出现:算法会将所有的目标框都创建轨迹对象,并储存起来。
注意:此时所有创建的轨迹都会被标注为已追踪轨迹。

从第二帧开始,算法就会逐步构建轨迹,步骤如下:

一、 对追踪轨迹和边界框进行分类

对所有的追踪轨迹分为激活和未激活两类(激活追踪了两帧以上的目标框(包括第一帧时目标框新建的轨迹))

对所有的当前帧边界框分为高分和低分两类(按照边界框的得分阈值进行分类(官方是0.5))

二、对轨迹进行第一次追踪(仅针对激活状态的轨迹的高分匹配)

1.将所有的已追踪轨迹和失追轨迹合并,称为初步追踪轨迹
2.预测初步追踪轨迹的下一帧边界框可能的位置和大小(使用卡尔曼滤波预测边界框,在上一篇文章中已介绍,这里就不再赘述了)
3.计算初步追踪轨迹预测的下一帧边界框与当前帧高分边界框之间的IoU(交并比)值,获取一个两两之间IoU的关系损失矩阵(IoU越小表示关联程度越大,IoU最大值为1,代表边界框间没有交集)
4.根据IoU损失矩阵,使用匈牙利算法对初步追踪轨迹和当前帧高分边界框进行匹配,获得三个结果:已匹配的轨迹与边界框,未成功匹配的轨迹,未成功匹配的当前帧边界框。(匈牙利算法可以根据损失矩阵对两两之间进行一对一的匹配,返回匹配成功和不成功的结果)
使用已成功匹配的当前帧边界框更新初步追踪轨迹(把初步追踪轨迹中的框改为当前帧边界框,id还是原来的id

三、对轨迹进行第二次追踪(仅针对激活状态的轨迹的低分匹配)

找出第一次匹配中没匹配到的轨迹,筛选出其中的已追踪轨迹(因为低分匹配不匹配那些已经失追的轨迹,可能作者认为这样比较合理)
因为这些轨迹之前也已经预测过下一帧的边界框了,所以这里不用预测
计算上述轨迹和当前帧的低分边界框之间的IoU
使用匈牙利算法对上述追踪轨迹和当前帧低分边界框进行匹配
使用已成功匹配的当前帧边界框更新上述追踪轨迹
将此时还未成功追踪的轨迹标记为失追轨迹(扔给下一帧去追踪了)

四、对未激活状态的轨迹进行追踪

个人理解:我觉得这一步存在的意义可能是作者认为在视频中间突然出现的目标可信度可能不太高(可能出现重复轨迹什么的),所以对中间才新出现的轨迹比较谨慎处理

1.找出第一步中未成功匹配的当前帧边界框(没有成功匹配的高分边界框),并且找出未激活的轨迹
2.计算上述轨迹和当前帧的边界框之间的IoU
3.使用匈牙利算法对上述追踪轨迹和边界框进行匹配
4.使用已成功匹配的当前帧边界框更新上述追踪轨迹
5.此时将未成功追踪的未激活轨迹直接标记为已删除轨迹

五、新建轨迹

如果到现在还没有成功匹配的高分边界框,就能认为是新出现的东西了,会给它分配一个新的轨迹以及新的id(低分框就之间当成误判扔掉了,不会生成新轨迹)。

注意:此时不是第一帧了,新增的轨迹都是未激活状态,如果下一帧不能成功匹配的话就会被无情删除了

六、返回结果

此时就可以放回所有已追踪的轨迹了(当然不包含失追轨迹和删除轨迹),所有轨迹都有一个唯一的id,这个结果就可以拿去作为每一帧追踪的结果了!

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

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

相关文章

《数字信号处理》学习04-离散时间系统中的线性时不变系统

目录 一&#xff0c;系统及离散时间系统 二&#xff0c;离散时间系统中的线性时不变系统 1&#xff0c;线性系统 1) 可加性 2) 比例性(齐次性) 3&#xff09;叠加原理(叠加性质) 2&#xff0c;时不变系统(移不变系统) 通过前几篇文章的学习&#xff0c;此时我对序列的相关概…

毒枸杞事件启示录:EasyCVR视频AI智能监管方案如何重塑食品卫生安全防线

一、方案背景 近年来&#xff0c;食品安全问题频发&#xff0c;引发了社会各界的广泛关注。其中&#xff0c;毒枸杞事件尤为引人关注。新闻报道&#xff0c;在青海格尔木、甘肃靖远等地&#xff0c;部分商户为了提升枸杞的品相&#xff0c;违规使用焦亚硫酸钠和工业硫磺进行“…

深度学习5从0到1理解RNN(包括LTSM,GRU等):内容丰富(下)

续 5.4.4 LSTM 举例 网络里面只有一个 LSTM 的单元&#xff0c;输入都是三维的向量&#xff0c;输出都是一维的输出。这三维的向量跟输出还有记忆元的关系是这样的。假设 x2 的值是 1 时&#xff0c; x1 的值就会被写到记忆元里&#xff1b;假设 x2 的值是-1 时&#xff0c;就…

时序数据库 IoTDB 为什么选择 TPCx-IoT 基准测评?

IoTDB 在 TPCx-IoT 榜单的 What 与 Why 解答&#xff01; 去年&#xff0c;我们发布了 IoTDB 多项性能表现位居国际数据库性能测试排行榜 benchANT&#xff08;Time Series: DevOps&#xff09;第一名的好消息。 刚刚落幕的数据库顶级会议 VLDB 上&#xff0c;我们又收获了一则…

如何优化浏览器缓存

每当用户访问您的网站&#xff0c;他他们的浏览器需要从服务器上下载页面显示所需的资源&#xff08;图片、CSS、JavaScript 和字体等&#xff09;&#xff0c;这些资源的下载会占用带宽&#xff0c;并需要一定的传输时间。但通过正确配置&#xff0c;您可以告知用户的浏览器保…

JavaScript面向对象小挑战

// 编码挑战 #4 /* 重新创建挑战 #3&#xff0c;但这次使用 ES6 类&#xff1a;为 “CarCl ”类创建一个 “EVCl ”子类将 “charge ”属性设为私有&#xff1b; 实现对该类的 “accelerate ”和 “chargeBattery ”方法进行链式处理的功能&#xff0c;同时更新 “CarCl ”类…

IOS 21 发现界面(UITableView)单曲列表(UITableView)实现

发现界面完整效果 本文实现歌单列表效果 文章基于IOS 20 发现界面&#xff08;UITableView&#xff09;歌单列表&#xff08;UICollectionView&#xff09;实现 继续实现发现界面单曲列表效果 单曲列表Cell实现 实现流程&#xff1a; 1.创建Cell&#xff0c;及在使用UITable…

datagrip链接sql server2005报错

错误信息 第一次报 DBMS: Microsoft SQL Server (no ver.) Case sensitivity: plainmixed, delimitedexact [08S01] 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client pr…

汽车电子行业知识:关于车载中控屏

文章目录 1. 车载中控屏的功能2. 最新技术3. 最新产品4. 未来趋势5. 车载中控屏的供应商5.1. 电子元件制造商5.2. 显示技术公司5.3. 软件和系统集成商5.4. 汽车制造商5.5. 新兴科技公司 车载中控屏是现代汽车中不可或缺的组成部分&#xff0c;它不仅提供了车辆信息的显示&#…

爬楼梯[简单]

优质博文&#xff1a;IT-BLOG-CN 题目 假设你正在爬楼梯。需要n阶你才能到达楼顶。 每次你可以爬1或2个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1阶…

前端模拟面试:如何检查JavaScript对象属性是否存在?

你正在参加一场关键的前端开发面试&#xff0c;面试官提出了一个经典的JavaScript问题&#xff1a;“在JavaScript中&#xff0c;如何检查对象是否包含某个属性&#xff1f;请你详细介绍几种不同的方法&#xff0c;并解释它们的区别。” 这个问题不仅考验你对JavaScript的基础掌…

注册表分析

目录 介绍步骤regsports 介绍 RegRipper 是一个开源工具&#xff0c;用 Perl 编写&#xff0c;用于从注册表中提取/解析信息&#xff08;键、值、数据&#xff09;并将其呈现以供分析。 RegRipper 由两个基本工具组成&#xff0c;它们都提供类似的功能。 RegRipper GUI 允许分…

Mac工程动态库配置和加载探究

缘起 最近在做Mac程序的打包&#xff0c;其中涉及到Mac程序引用了Hoops的第三方动态库。在之前的工程配置中&#xff0c;Project的Run Script是这么来处理动态库的&#xff1a; FRAMEWORKS_DIR${TARGET_BUILD_DIR}/${EXECUTABLE_NAME}.app/Contents/Frameworks/ mkdir -p ${F…

FastDFS分布式存储:概念、集群案例

FastDFS FastDFS&#xff1a;Fast DistributedFileSystem&#xff0c;快速分布式文件系统 FastDFS是一个开源的轻量级分布式文件系统&#xff0c;它对文件进行管理&#xff0c;功能包括&#xff1a;文件存储、文件同步、文件访问&#xff08;文件上传、文件下载&#xff09;等&…

在x86上拉取ARM架构的镜像

添加–platform 参数 docker pull --platform linux/arm64 nginx:1.27.1查看镜像架构 docker inspect nginx:1.27.1 | grep Architecture

智慧社区管理系统平台:架构全新升级,Java商业版OEM开源定制开发

智慧社区综合管理平台&#xff0c;致力于打造以党建为引领&#xff0c;精细化治理 个性化服务于一体的智慧社区平台。 平台整体包含智慧社区综合管理云平台数字孪生大屏可视化APP微信小程序&#xff0c;满足智慧街道、智慧社区标准化功能建设。数字孪生倾斜摄影&#xff0c;支…

云计算之网络

目录 一、VPC&#xff1a;云网络的基石 1.1 VPC产品介绍 1.2 vswitch交换机 1.3 vrouter路由器 1.4 产品架构 1.5 常见问题解答及处理 1.5.1 VPC内如何查询某个IP归属? 1.5.2 网络ACL阻断导致ECS访问CLB不通 1.5.3 EIP秒级突发/分布式限速丢包 1.5.4 NAT网关的流量监…

C# 窗体小实验 点击确定按钮返回文本框显示

1.1创建c项目 1.2配置新项目 1.3打开工具箱 1.4 创建按钮和文本框 拖至到窗体中 右键确定按钮 点击属性 设置Text(确定)文本显示 &#xff0c;buttton2同理 设置退出 设置完成效果 双击确定按钮 进入编辑代码窗口 编写代码如下&#xff1a; 然后设置退出的 代码&#xff1a; 单…

btrace 开源!基于 Systrace 高性能 Trace 工具

android.os.Trace#beginSection 会调用 nativeTraceBegin 方法&#xff0c;该方法实现参考 frameworks/base/core/jni/android_os_Trace.cpp。 static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass, jlong tag, jstring nameStr) { withString(env, nameStr…

实验报告: lookie-lookie 项目测试与分析

目录 一、实验目的 二、实验环境 三、实验步骤 1. 下载与准备项目 1.1 从 GitHub 获取项目 1.2 查看项目文件结构 2. 运行项目 2.1 启动项目 2.2 浏览器设置 3. 项目体验 3.1 功能测试 3.2 运行截图 4. 文件结构分析 4.1 总体结构 4.2 主要文件和目录说明 5. 数…