返回顶部
首页 > 资讯 > 后端开发 > Python >利用OpenCV实现质心跟踪算法
  • 632
分享到

利用OpenCV实现质心跟踪算法

2024-04-02 19:04:59 632人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录质心跟踪算法步骤项目结构使用 OpenCV 实现质心跟踪实现对象跟踪驱动程序脚本限制和缺点目标跟踪的过程: 1、获取对象检测的初始集 2、为每个初始检测创建唯一的ID 3、然后在

目标跟踪的过程:

1、获取对象检测的初始集

2、为每个初始检测创建唯一的ID

3、然后在视频帧中跟踪每个对象的移动,保持唯一ID的分配

本文使用OpenCV实现质心跟踪,这是一种易于理解但高效的跟踪算法。

质心跟踪算法步骤

步骤1:接受边界框坐标并计算质心

质心跟踪算法假设我们为每一帧中的每个检测到的对象传入一组边界框 (x, y) 坐标。

这些边界框可以由任何类型的对象检测器(颜色阈值 + 轮廓提取、Haar 级联、HOG + 线性 SVM、SSD、Faster R-CNN 等)生成,前提是它们是针对 该视频。

通过边界框坐标,就可以计算“质心”(边界框的中心 (x, y) 坐标)。 如上图演示了通过一组边界框坐标计算出质心。

给初始的边界框分配ID。

步骤2:计算新边界框和现有对象之间的欧几里得距离

对于视频流中的每个后续帧,首先执行步骤1; 然后,我们首先需要确定是否可以将新的对象质心(黄色)与旧的对象质心(紫色)相关联,而不是为每个检测到的对象分配一个新的唯一 ID(这会破坏对象跟踪的目的)。 为了完成这个过程,我们计算每对现有对象质心和输入对象质心之间的欧几里得距离(用绿色箭头突出显示)。

上图可以看出,我们这次在图像中检测到了三个对象。 靠近的两对是两个现有对象。

然后我们计算每对原始质心(黄色)和新质心(紫色)之间的欧几里得距离。 但是我们如何使用这些点之间的欧几里得距离来实际匹配它们并关联它们呢?

答案在步骤3。

步骤 3:更新现有对象的 (x, y) 坐标

质心跟踪算法的主要假设是给定对象可能会在后续帧之间移动,但帧 F_t 和 F_{t + 1} 的质心之间的距离将小于对象之间的所有其他距离。

因此,如果我们选择将质心与后续帧之间的最小距离相关联,我们可以构建我们的对象跟踪器。

在上图中,可以看到质心跟踪器算法如何选择关联质心以最小化它们各自的欧几里得距离。

但是左下角的孤独点呢?

它没有与任何东西相关联——我们如何处理它?

步骤4:注册新对象

如果输入检测比跟踪的现有对象多,我们需要注册新对象。 “注册”仅仅意味着我们通过以下方式将新对象添加到我们的跟踪对象列表中:

为其分配一个新的对象 ID

存储该对象的边界框坐标的质心

然后我们可以返回到步骤2,重复执行。

上图演示了使用最小欧几里得距离关联现有对象 ID,然后注册新对象的过程。

步骤5:注销旧对象

当旧的对象超出范围时,注销旧对象、

项目结构

使用 OpenCV 实现质心跟踪

新建 centroidtracker.py,写入代码:

# import the necessary packages
from scipy.spatial import distance as dist
from collections import OrderedDict
import numpy as np

class CentroidTracker():
    def __init__(self, maxDisappeared=50):

        self.nextObjectID = 0
        self.objects = OrderedDict()
        self.disappeared = OrderedDict()
        # 存储给定对象被允许标记为“消失”的最大连续帧数,直到我们需要从跟踪中注销该对象
        self.maxDisappeared = maxDisappeared

导入了所需的包和模块:distance 、 OrderedDict 和 numpy 。

定义类 CentroidTracker 。构造函数接受一个参数maxDisappeared,即给定对象必须丢失/消失的最大连续帧数,如果超过这个数值就将这个对象删除。

四个类变量:

nextObjectID :用于为每个对象分配唯一 ID 的计数器。如果一个对象离开帧并且没有返回 maxDisappeared 帧,则将分配一个新的(下一个)对象 ID。

objects :使用对象 ID 作为键和质心 (x, y) 坐标作为值的字典。

disappeared:保持特定对象 ID(键)已被标记为“丢失”的连续帧数(值)。

maxDisappeared :在我们取消注册对象之前,允许将对象标记为“丢失/消失”的连续帧数。

让我们定义负责向我们的跟踪器添加新对象的 reGISter 方法:

def register(self, centroid):
        # 注册对象时,我们使用下一个可用的对象ID来存储质心
        self.objects[self.nextObjectID] = centroid
        self.disappeared[self.nextObjectID] = 0
        self.nextObjectID += 1

    def deregister(self, objectID):
        # 要注销注册对象ID,我们从两个字典中都删除了该对象ID
        del self.objects[objectID]
        del self.disappeared[objectID]

register 方法接受一个质心,然后使用下一个可用的对象 ID 将其添加到对象字典中。

对象消失的次数在消失字典中初始化为 0。

最后,我们增加 nextObjectID,这样如果一个新对象进入视野,它将与一个唯一的 ID 相关联。

与我们的注册方法类似,我们也需要一个注销方法。

deregister 方法分别删除对象和消失字典中的 objectID。

质心跟踪器实现的核心位于update方法中

 def update(self, rects):
        # 检查输入边界框矩形的列表是否为空
        if len(rects) == 0:
            # 遍历任何现有的跟踪对象并将其标记为消失
            for objectID in list(self.disappeared.keys()):
                self.disappeared[objectID] += 1
                # 如果达到给定对象被标记为丢失的最大连续帧数,请取消注册
                if self.disappeared[objectID] > self.maxDisappeared:
                    self.deregister(objectID)
            # 由于没有质心或跟踪信息要更新,请尽早返回
            return self.objects

        # 初始化当前帧的输入质心数组
        inputCentroids = np.zeros((len(rects), 2), dtype="int")
        # 在边界框矩形上循环
        for (i, (startX, startY, endX, endY)) in enumerate(rects):
            # use the bounding box coordinates to derive the centroid
            cX = int((startX + endX) / 2.0)
            cY = int((startY + endY) / 2.0)
            inputCentroids[i] = (cX, cY)

        # 如果我们当前未跟踪任何对象,请输入输入质心并注册每个质心
        if len(self.objects) == 0:
            for i in range(0, len(inputCentroids)):
                self.register(inputCentroids[i])
        # 否则,当前正在跟踪对象,因此我们需要尝试将输入质心与现有对象质心进行匹配
        else:
            # 抓取一组对象ID和相应的质心
            objectIDs = list(self.objects.keys())
            objectCentroids = list(self.objects.values())
            # 分别计算每对对象质心和输入质心之间的距离-我们的目标是将输入质心与现有对象质心匹配
            D = dist.cdist(np.array(objectCentroids), inputCentroids)
            # 为了执行此匹配,我们必须(1)在每行中找到最小值,然后(2)根据行索引的最小值对行索引进行排序,以使具有最小值的行位于索引列表的* front *处
            rows = D.min(axis=1).argsort()
            # 接下来,我们在列上执行类似的过程,方法是在每一列中找到最小值,然后使用先前计算的行索引列表进行排序
            cols = D.argmin(axis=1)[rows]
            # 为了确定是否需要更新,注册或注销对象,我们需要跟踪已经检查过的行索引和列索引
            usedRows = set()
            usedCols = set()

            # 循环遍历(行,列)索引元组的组合
            for (row, col) in zip(rows, cols):
                # 如果我们之前已经检查过行或列的值,请忽略它
                if row in usedRows or col in usedCols:
                    continue
                # 否则,获取当前行的对象ID,设置其新的质心,然后重置消失的计数器
                objectID = objectIDs[row]
                self.objects[objectID] = inputCentroids[col]
                self.disappeared[objectID] = 0
                # 表示我们已经分别检查了行索引和列索引
                usedRows.add(row)
                usedCols.add(col)
            # 计算我们尚未检查的行和列索引
            unusedRows = set(range(0, D.shape[0])).difference(usedRows)
            unusedCols = set(range(0, D.shape[1])).difference(usedCols)
            # 如果对象质心的数量等于或大于输入质心的数量
            # 我们需要检查一下其中的某些对象是否已潜在消失
            if D.shape[0] >= D.shape[1]:
                # loop over the unused row indexes
                for row in unusedRows:
                    # 抓取相应行索引的对象ID并增加消失的计数器
                    objectID = objectIDs[row]
                    self.disappeared[objectID] += 1
                    # 检查是否已将该对象标记为“消失”的连续帧数以用于注销该对象的手令
                    if self.disappeared[objectID] > self.maxDisappeared:
                        self.deregister(objectID)
            # 否则,如果输入质心的数量大于现有对象质心的数量,我们需要将每个新的输入质心注册为可跟踪对象
            else:
                for col in unusedCols:
                    self.register(inputCentroids[col])

        # return the set of trackable objects
        return self.objects

update方法接受边界框矩形列表。 rects 参数的格式假定为具有以下结构的元组: (startX, startY, endX, endY) 。

如果没有检测到,我们将遍历所有对象 ID 并增加它们的消失计数。 我们还将检查是否已达到给定对象被标记为丢失的最大连续帧数。 如果是,我们需要将其从我们的跟踪系统中删除。 由于没有要更新的跟踪信息,我们将提前返回。

否则,我们将初始化一个 NumPy 数组来存储每个 rect 的质心。 然后,我们遍历边界框矩形并计算质心并将其存储在 inputCentroids 列表中。 如果没有我们正在跟踪的对象,我们将注册每个新对象。

否则,我们需要根据最小化它们之间的欧几里得距离的质心位置更新任何现有对象 (x, y) 坐标。

接下来我们在else中计算所有 objectCentroids 和 inputCentroids 对之间的欧几里德距离:

获取 objectID 和 objectCentroid 值。

计算每对现有对象质心和新输入质心之间的距离。距离图 D 的输出 NumPy 数组形状将是 (# of object centroids, # of input centroids) 。要执行匹配,我们必须 (1) 找到每行中的最小值,以及 (2) 根据最小值对行索引进行排序。我们对列执行非常相似的过程,在每列中找到最小值,然后根据已排序的行对它们进行排序。我们的目标是在列表的前面具有最小对应距离的索引值。

下一步是使用距离来查看是否可以关联对象 ID:

初始化两个集合以确定我们已经使用了哪些行和列索引。

然后遍历 (row, col) 索引元组的组合以更新我们的对象质心:

如果我们已经使用了此行或列索引,请忽略它并继续循环。

否则,我们找到了一个输入质心:

  1. 到现有质心的欧几里得距离最小
  2. 并且没有与任何其他对象匹配
  3. 在这种情况下,我们更新对象质心(第 113-115 行)并确保将 row 和 col 添加到它们各自的 usedRows 和 usedCols 集合中。

在我们的 usedRows + usedCols 集合中可能有我们尚未检查的索引:

所以我们必须确定哪些质心索引我们还没有检查过,并将它们存储在两个新的方便集合(unusedRows 和 usedCols)中。

最终检查会处理任何丢失或可能消失的对象:

如果对象质心的数量大于或等于输入质心的数量:

我们需要循环遍历未使用的行索引来验证这些对象是否丢失或消失。

在循环中,我们将:

1. 增加他们在字典中消失的次数。

2. 检查消失计数是否超过 maxDisappeared 阈值,如果是,我们将注销该对象。

否则,输入质心的数量大于现有对象质心的数量,我们有新的对象要注册和跟踪.

循环遍历未使用的Cols 索引并注册每个新质心。 最后,我们将可跟踪对象集返回给调用方法。

实现对象跟踪驱动程序脚本

已经实现了 CentroidTracker 类,让我们将其与对象跟踪驱动程序脚本一起使用。

驱动程序脚本是您可以使用自己喜欢的对象检测器的地方,前提是它会生成一组边界框。 这可能是 Haar Cascade、HOG + 线性 SVM、YOLO、SSD、Faster R-CNN 等。

在这个脚本中,需要实现的功能:

1、使用实时 VideoStream 对象从网络摄像头中抓取帧

2、加载并使用 OpenCV 的深度学习人脸检测器

3、实例化 CentroidTracker 并使用它来跟踪视频流中的人脸对象并显示结果。

新建 object_tracker.py 插入代码:

from Model.centroidtracker import CentroidTracker
import numpy as np
import imutils
import time
import cv2

# 定义最低置信度
confidence_t=0.5
# 初始化质心跟踪器和框架尺寸
ct = CentroidTracker()
(H, W) = (None, None)
# 加载检测人脸的模型
print("[INFO] loading model...")
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel")
# 初始化视频流并允许相机传感器预热
print("[INFO] starting video stream...")
vs = cv2.VideoCapture('11.mp4')
time.sleep(2.0)
fps = 30    #保存视频的FPS,可以适当调整
size=(600,1066)#宽高,根据frame的宽和高确定。
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
videoWriter = cv2.VideoWriter('3.mp4',fourcc,fps,size)#最后一个是保存图片的尺寸

导入需要的包。

定义最低置信度。

加载人脸检测模型

初始化视频流或者相机(设置成相机对应的ID就会启动相机)

接下来定义cv2.VideoWriter的参数。

# 循环播放图像流中的帧
while True:
    # 从视频流中读取下一帧并调整其大小
    (grabbed, frame)  = vs.read()
    if not grabbed:
        break
    frame = imutils.resize(frame, width=600)
    print(frame.shape)
    # 如果帧中尺寸为“无”,则抓住它们
    if W is None or H is None:
        (H, W) = frame.shape[:2]
    # 从帧中构造一个Blob,将其通过网络,
    # 获取输出预测,并初始化边界框矩形的列表
    blob = cv2.dnn.blobFromImage(frame, 1.0, (W, H),
                                 (104.0, 177.0, 123.0))
    net.setInput(blob)
    detections = net.forward()
    rects = []
    # 循环检测
    for i in range(0, detections.shape[2]):
        # 通过确保预测的概率大于最小阈值来过滤掉弱检测
        if detections[0, 0, i, 2] >0.5:
            # 计算对象边界框的(x,y)坐标,然后更新边界框矩形列表
            box = detections[0, 0, i, 3:7] * np.array([W, H, W, H])
            rects.append(box.astype("int"))
            # 在对象周围画一个边界框,以便我们可视化它
            (startX, startY, endX, endY) = box.astype("int")
            cv2.rectangle(frame, (startX, startY), (endX, endY),
                          (0, 255, 0), 2)
    # 使用边界框矩形的计算集更新质心跟踪器
    objects = ct.update(rects)
    # 循环跟踪对象
    for (objectID, centroid) in objects.items():
        # 在输出帧上绘制对象的ID和对象的质心
        text = "ID {}".fORMat(objectID)
        cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
        cv2.circle(frame, (centroid[0], centroid[1]), 4, (0, 255, 0), -1)
    # 显示输出画面
    cv2.imshow("Frame", frame)
    videoWriter.write(frame)
    key = cv2.waiTKEy(1) & 0xFF
    # 如果按下“ q”键,则退出循环
    if key == ord("q"):
        break
videoWriter.release()
cv2.destroyAllwindows()
vs.release()

遍历帧并将它们调整为固定宽度(同时保持纵横比)。

然后将帧通过 CNN 对象检测器以获得预测和对象位置(。

初始化一个 rects 列表,即边界框矩形。

循环检测detections,如果检测超过我们的置信度阈值,表明检测有效,然后计算边框。

在质心跟踪器对象 ct 上调用 update 方法。

接下来在输出帧上绘制对象的ID和质心,将质心显示为一个实心圆圈和唯一的对象 ID 号文本。 现在我们将能够可视化结果并通过将正确的 ID 与视频流中的对象相关联来检查 CentroidTracker 是否正确地跟踪对象。

限制和缺点

虽然质心跟踪器在这个例子中工作得很好,但这种对象跟踪算法有两个主要缺点。

1、它要求在输入视频的每一帧上运行对象检测步骤。对于非常快速的对象检测器(即颜色阈值和 Haar 级联),必须在每个输入帧上运行检测器可能不是问题。但是,如果在 资源受限的设备上使用计算量大得多的对象检测器,例如 HOG + 线性 SVM 或基于深度学习的检测器,那么帧处理将大大减慢。

2、与质心跟踪算法本身的基本假设有关——质心必须在后续帧之间靠得很近。

这个假设通常成立,但请记住,我们用 2D 帧来表示我们的 3D 世界——当一个对象与另一个对象重叠时会发生什么?

答案是可能发生对象 ID 切换。如果两个或多个对象相互重叠到它们的质心相交的点,而是与另一个相应对象具有最小距离,则算法可能(在不知不觉中)交换对象 ID。重要的是要了解重叠/遮挡对象问题并非特定于质心跟踪 - 它也发生在许多其他对象跟踪器中,包括高级对象跟踪器。然而,质心跟踪的问题更为明显,因为我们严格依赖质心之间的欧几里得距离,并且没有额外的度量、启发式或学习模式。

以上就是利用OpenCV实现质心跟踪算法的详细内容,更多关于OpenCV质心跟踪算法的资料请关注编程网其它相关文章!

--结束END--

本文标题: 利用OpenCV实现质心跟踪算法

本文链接: https://lsjlt.com/news/162937.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 利用OpenCV实现质心跟踪算法
    目录质心跟踪算法步骤项目结构使用 OpenCV 实现质心跟踪实现对象跟踪驱动程序脚本限制和缺点目标跟踪的过程: 1、获取对象检测的初始集 2、为每个初始检测创建唯一的ID 3、然后在...
    99+
    2024-04-02
  • OpenCV实现对象跟踪的方法
    介绍 OpenCV 是一个很好的处理图像和视频的工具。无论你是想让你的照片呈现 90 年代的黑白效果,还是执行复杂的数学运算,OpenCV 都可以随时为你服务。 如果你对计算机视觉感...
    99+
    2024-04-02
  • python+opencv实现目标跟踪
    python-opencv3.0新增了一些比较有用的追踪器算法,这里根据官网示例写了一个追踪器类 程序只能运行在安装有opencv3.0以上版本和对应的contrib模块的python解释器   #encoding=utf-8 imp...
    99+
    2023-01-31
    目标 python opencv
  • python使用OpenCV实现多目标跟踪
    目录1 背景介绍2 基于MultiTracker的多目标跟踪2.1 创建单个对象跟踪器2.2 读取视频的第一帧2.3 在第一帧中确定我们跟踪的对象2.4 初始化MultiTracke...
    99+
    2024-04-02
  • python+opencv实现目标跟踪过程
    目录python opencv实现目标跟踪这里根据官网示例写了一个追踪器类python opencv实现目标跟踪 python-opencv3.0新增了一些比较有用的追踪器算法 这里...
    99+
    2024-04-02
  • python怎么使用OpenCV实现多目标跟踪
    这篇文章主要介绍“python怎么使用OpenCV实现多目标跟踪”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“python怎么使用OpenCV实现多目标跟踪”文章能帮助大家解决问题。1 背景介绍计算...
    99+
    2023-06-30
  • Python使用OPENCV的目标跟踪算法实现自动视频标注效果
    先上效果  1.首先,要使用opencv的目标跟踪算法,必须要有opencv环境  使用:opencv==4.4.0 和 opencv-contri...
    99+
    2024-04-02
  • 利用OpenCV进行对象跟踪的示例代码
    目录OpenCV 对象跟踪OpenCV 对象跟踪器物体跟踪总结OpenCV 对象跟踪 这篇文章使用 OpenCV 中内置的八种不同的对象跟踪算法,实现对物体的跟踪。 首先,介绍一下8...
    99+
    2024-04-02
  • 基于OpenCV目标跟踪实现人员计数器
    目录1.了解对象检测与对象跟踪2.结合对象检测和对象跟踪3.项目结构4.结合对象跟踪算法5.创建可追踪对象6.使用OpenCV+Python实现我们的人员计数器7.完整代码peopl...
    99+
    2024-04-02
  • OpenCV基于稠密光流如何实现视频跟踪
    这篇“OpenCV基于稠密光流如何实现视频跟踪”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“OpenCV基于稠密光流如何实现...
    99+
    2023-07-05
  • OpenCV怎么使用稀疏光流实现视频对象跟踪
    这篇“OpenCV怎么使用稀疏光流实现视频对象跟踪”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“OpenCV怎么使用稀疏光流...
    99+
    2023-07-05
  • 简单介绍SORT跟踪算法及其Python实现示例
    SORT(Simple Online and Realtime Tracking)是一种基于卡尔曼滤波的目标跟踪算法,它可以在实时场景中对移动目标进行鲁棒跟踪。SORT算法最初是由Alex Bewley等人在2016年提出的,它...
    99+
    2024-01-24
    机器学习 算法的概念
  • C++ opencv利用grabCut算法实现抠图示例
    目录前言一、grabCut函数二、compare函数三、代码前言 grabCut算法利用了图像中的纹理(颜色)信息和边界(反差)信息,只用少量的用户交互操作,即可得到比较好的分割结果...
    99+
    2024-04-02
  • C++ opencv如何利用grabCut算法实现抠图
    今天小编给大家分享一下C++ opencv如何利用grabCut算法实现抠图的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解...
    99+
    2023-06-30
  • 如何利用HTML+CSS实现跟踪鼠标移动功能
    这篇文章将为大家详细讲解有关如何利用HTML+CSS实现跟踪鼠标移动功能,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。随着用户越来越关注隐私并越来越注意防止在线跟踪,用户开始使用广告拦截器和脚本拦截器来阻...
    99+
    2023-06-08
  • sql server deadlock跟踪的4种实现方法
    前言 最近写程序常会遇到deadlock victim,每次一脸懵逼。研究了下怎么跟踪,写下来记录下。文中介绍的非常详细,对大家具有一定的参考学习价值,下面话不多说了,来一起看看详细的介绍吧 建测试数据 ...
    99+
    2024-04-02
  • OpenCV物体跟踪树莓派视觉小车实现过程学习
    目录物体跟踪效果展示一、初始化二、运动控制函数三、舵机角度控制四、摄像头&&图像处理1、打开摄像头2、把图像转换为灰度图3、 高斯滤波(去噪)4、亮度增强5、转换为二...
    99+
    2024-04-02
  • 使用MDC实现日志链路跟踪
    目录1.原理2.实现3.过滤器4.logback.xml5.返回体6.效果日志前言: 在微服务环境中,我们经常使用Skywalking、CAT等去实现整体请求链路的追踪,但是这个整体...
    99+
    2024-04-02
  • 如何利用Redis和Elixir实现实时地理位置跟踪功能
    如何利用Redis和Elixir实现实时地理位置跟踪功能引言:随着互联网和移动技术的不断发展,实时地理位置跟踪已经成为许多应用程序所需要的重要功能之一。无论是打车软件、外卖平台还是社交网络,都需要实时获取用户的地理位置信息。在本文中,我们将...
    99+
    2023-10-22
    redis Elixir 地理位置跟踪
  • OpenCV实现拼图算法
    本文实例为大家分享了OpenCV实现拼图算法的具体代码,供大家参考,具体内容如下 编程环境:VS2012+OpenCV2.4.6 功能: 第一种是将指定三幅图的指定位置的像素直接搬移...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作