【图像算法 - 13】基于 YOLO12 与 OpenCV 的实时目标点击跟踪系统(系统介绍 + 源码详细)

基于 YOLO12 与 OpenCV 的实时点击目标跟踪系统

在计算机视觉领域,目标检测与跟踪是两个核心任务。本文将介绍一个结合 YOLO 目标检测模型与 OpenCV 跟踪算法的实时目标跟踪系统,该系统允许用户通过鼠标交互选择特定目标进行持续跟踪,支持多种跟踪算法切换,适用于视频监控、行为分析等场景。

【图像算法 - 13】基于 YOLO12 与 OpenCV 的实时目标点击跟踪系统

系统功能概述

该系统主要实现以下功能:

  • 使用 YOLO 模型对视频帧进行目标检测,识别出画面中的各类物体

  • 支持用户通过左键点击选择特定目标进行跟踪

  • 提供 MOSSE、CSRT、KCF 三种经典跟踪算法供选择

  • 实时显示跟踪状态、目标类别及 FPS 等信息

  • 支持右键点击停止跟踪,回到目标检测模式

点击跟踪

技术原理

系统采用 “检测 + 跟踪” 的混合架构:

  1. 首先利用 YOLO 模型进行目标检测,获取画面中所有目标的边界框和类别信息
  2. 当用户选择特定目标后,启动选定的跟踪算法对该目标进行持续跟踪
  3. 跟踪过程中实时更新目标位置,若跟踪失败则提示 “Lost”
  4. 整个过程通过可视化界面展示,支持用户交互操作

这种架构结合了 YOLO 检测精度高和传统跟踪算法速度快的优点,在保证一定精度的同时兼顾了实时性。

代码解析

核心依赖库

import cv2          # 用于视频处理和跟踪算法
import numpy as np  # 用于数值计算
import argparse     # 用于命令行参数解析
from ultralytics import YOLO  # 用于YOLO目标检测

全局变量与交互设计

定义全局变量存储跟踪状态和用户交互信息:

selected_box = None  # 选中的目标边界框
tracking = False     # 跟踪状态标志
target_class = None  # 目标类别
click_x, click_y = -1, -1  # 鼠标点击坐标
mouse_clicked = False      # 鼠标点击标志
debug = False              # 调试模式标志

鼠标回调函数处理用户交互:

def mouse_callback(event, x, y, flags, param):global click_x, click_y, mouse_clicked, trackingif event == cv2.EVENT_LBUTTONDOWN:  # 左键点击选择目标click_x, click_y = x, ymouse_clicked = Trueif debug:print(f"Left click at: ({x}, {y})")elif event == cv2.EVENT_RBUTTONDOWN:  # 右键点击停止跟踪tracking = Falseif debug:print("Tracking stopped")

跟踪器创建

针对不同的跟踪算法,创建对应的跟踪器实例(注意 OpenCV 新版本中跟踪器位于 legacy 模块):

def create_tracker(tracker_type):"""使用cv2.legacy模块创建跟踪器,兼容新版OpenCV"""try:if tracker_type == "mosse":return cv2.legacy.TrackerMOSSE_create()elif tracker_type == "csrt":return cv2.legacy.TrackerCSRT_create()elif tracker_type == "kcf":return cv2.legacy.TrackerKCF_create()else:print(f"Unsupported tracker: {tracker_type}, using MOSSE")return cv2.legacy.TrackerMOSSE_create()except AttributeError as e:print(f"Failed to create tracker: {e}")print("Please check OpenCV installation (must include opencv-contrib-python)")return None
OpenCV 跟踪demo源码
#!/usr/bin/env python
'''
Tracker demoFor usage download models by following links
For GOTURN:goturn.prototxt and goturn.caffemodel: https://github.com/opencv/opencv_extra/tree/c4219d5eb3105ed8e634278fad312a1a8d2c182d/testdata/tracking
For DaSiamRPN:network:     https://www.dropbox.com/s/rr1lk9355vzolqv/dasiamrpn_model.onnx?dl=0kernel_r1:   https://www.dropbox.com/s/999cqx5zrfi7w4p/dasiamrpn_kernel_r1.onnx?dl=0kernel_cls1: https://www.dropbox.com/s/qvmtszx5h339a0w/dasiamrpn_kernel_cls1.onnx?dl=0
For NanoTrack:nanotrack_backbone: https://github.com/HonglinChu/SiamTrackers/blob/master/NanoTrack/models/nanotrackv2/nanotrack_backbone_sim.onnxnanotrack_headneck: https://github.com/HonglinChu/SiamTrackers/blob/master/NanoTrack/models/nanotrackv2/nanotrack_head_sim.onnxUSAGE:tracker.py [-h] [--input INPUT_VIDEO][--tracker_algo TRACKER_ALGO (mil, goturn, dasiamrpn, nanotrack, vittrack)][--goturn GOTURN_PROTOTXT][--goturn_model GOTURN_MODEL][--dasiamrpn_net DASIAMRPN_NET][--dasiamrpn_kernel_r1 DASIAMRPN_KERNEL_R1][--dasiamrpn_kernel_cls1 DASIAMRPN_KERNEL_CLS1][--nanotrack_backbone NANOTRACK_BACKBONE][--nanotrack_headneck NANOTRACK_TARGET][--vittrack_net VITTRACK_MODEL][--vittrack_net VITTRACK_MODEL][--tracking_score_threshold TRACKING SCORE THRESHOLD FOR ONLY VITTRACK][--backend CHOOSE ONE OF COMPUTATION BACKEND][--target CHOOSE ONE OF COMPUTATION TARGET]
'''# Python 2/3 compatibility
from __future__ import print_functionimport sysimport numpy as np
import cv2 as cv
import argparsefrom video import create_capture, presetsbackends = (cv.dnn.DNN_BACKEND_DEFAULT, cv.dnn.DNN_BACKEND_HALIDE, cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_BACKEND_OPENCV,cv.dnn.DNN_BACKEND_VKCOM, cv.dnn.DNN_BACKEND_CUDA)
targets = (cv.dnn.DNN_TARGET_CPU, cv.dnn.DNN_TARGET_OPENCL, cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD,cv.dnn.DNN_TARGET_VULKAN, cv.dnn.DNN_TARGET_CUDA, cv.dnn.DNN_TARGET_CUDA_FP16)class App(object):def __init__(self, args):self.args = argsself.trackerAlgorithm = args.tracker_algoself.tracker = self.createTracker()def createTracker(self):if self.trackerAlgorithm == 'mil':tracker = cv.TrackerMIL_create()elif self.trackerAlgorithm == 'goturn':params = cv.TrackerGOTURN_Params()params.modelTxt = self.args.goturnparams.modelBin = self.args.goturn_modeltracker = cv.TrackerGOTURN_create(params)elif self.trackerAlgorithm == 'dasiamrpn':params = cv.TrackerDaSiamRPN_Params()params.model = self.args.dasiamrpn_netparams.kernel_cls1 = self.args.dasiamrpn_kernel_cls1params.kernel_r1 = self.args.dasiamrpn_kernel_r1params.backend = args.backendparams.target = args.targettracker = cv.TrackerDaSiamRPN_create(params)elif self.trackerAlgorithm == 'nanotrack':params = cv.TrackerNano_Params()params.backbone = args.nanotrack_backboneparams.neckhead = args.nanotrack_headneckparams.backend = args.backendparams.target = args.targettracker = cv.TrackerNano_create(params)elif self.trackerAlgorithm == 'vittrack':params = cv.TrackerVit_Params()params.net = args.vittrack_netparams.tracking_score_threshold = args.tracking_score_thresholdparams.backend = args.backendparams.target = args.targettracker = cv.TrackerVit_create(params)else:sys.exit("Tracker {} is not recognized. Please use one of three available: mil, goturn, dasiamrpn, nanotrack.".format(self.trackerAlgorithm))return trackerdef initializeTracker(self, image):while True:print('==> Select object ROI for tracker ...')bbox = cv.selectROI('tracking', image)print('ROI: {}'.format(bbox))if bbox[2] <= 0 or bbox[3] <= 0:sys.exit("ROI selection cancelled. Exiting...")try:self.tracker.init(image, bbox)except Exception as e:print('Unable to initialize tracker with requested bounding box. Is there any object?')print(e)print('Try again ...')continuereturndef run(self):videoPath = self.args.inputprint('Using video: {}'.format(videoPath))camera = create_capture(cv.samples.findFileOrKeep(videoPath), presets['cube'])if not camera.isOpened():sys.exit("Can't open video stream: {}".format(videoPath))ok, image = camera.read()if not ok:sys.exit("Can't read first frame")assert image is not Nonecv.namedWindow('tracking')self.initializeTracker(image)print("==> Tracking is started. Press 'SPACE' to re-initialize tracker or 'ESC' for exit...")while camera.isOpened():ok, image = camera.read()if not ok:print("Can't read frame")breakok, newbox = self.tracker.update(image)#print(ok, newbox)if ok:cv.rectangle(image, newbox, (200,0,0))cv.imshow("tracking", image)k = cv.waitKey(1)if k == 32:  # SPACEself.initializeTracker(image)if k == 27:  # ESCbreakprint('Done')if __name__ == '__main__':print(__doc__)parser = argparse.ArgumentParser(description="Run tracker")parser.add_argument("--input", type=str, default="vtest.avi", help="Path to video source")parser.add_argument("--tracker_algo", type=str, default="nanotrack", help="One of available tracking algorithms: mil, goturn, dasiamrpn, nanotrack, vittrack")parser.add_argument("--goturn", type=str, default="goturn.prototxt", help="Path to GOTURN architecture")parser.add_argument("--goturn_model", type=str, default="goturn.caffemodel", help="Path to GOTERN model")parser.add_argument("--dasiamrpn_net", type=str, default="dasiamrpn_model.onnx", help="Path to onnx model of DaSiamRPN net")parser.add_argument("--dasiamrpn_kernel_r1", type=str, default="dasiamrpn_kernel_r1.onnx", help="Path to onnx model of DaSiamRPN kernel_r1")parser.add_argument("--dasiamrpn_kernel_cls1", type=str, default="dasiamrpn_kernel_cls1.onnx", help="Path to onnx model of DaSiamRPN kernel_cls1")parser.add_argument("--nanotrack_backbone", type=str, default="nanotrack_backbone_sim.onnx", help="Path to onnx model of NanoTrack backBone")parser.add_argument("--nanotrack_headneck", type=str, default="nanotrack_head_sim.onnx", help="Path to onnx model of NanoTrack headNeck")parser.add_argument("--vittrack_net", type=str, default="vitTracker.onnx", help="Path to onnx model of  vittrack")parser.add_argument('--tracking_score_threshold', type=float,  help="Tracking score threshold. If a bbox of score >= 0.3, it is considered as found ")parser.add_argument('--backend', choices=backends, default=cv.dnn.DNN_BACKEND_DEFAULT, type=int,help="Choose one of computation backends: ""%d: automatically (by default), ""%d: Halide language (http://halide-lang.org/), ""%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), ""%d: OpenCV implementation, ""%d: VKCOM, ""%d: CUDA"% backends)parser.add_argument("--target", choices=targets, default=cv.dnn.DNN_TARGET_CPU, type=int,help="Choose one of target computation devices: "'%d: CPU target (by default), ''%d: OpenCL, ''%d: OpenCL fp16 (half-float precision), ''%d: VPU, ''%d: VULKAN, ''%d: CUDA, ''%d: CUDA fp16 (half-float preprocess)'% targets)args = parser.parse_args()App(args).run()cv.destroyAllWindows()

目标检测与处理

使用 YOLO 模型进行目标检测,并将结果转换为便于处理的格式:

def detect_objects(frame, model, confidence):results = model(frame, conf=confidence)boxes = []  # 存储边界框 (x, y, w, h)class_names = []  # 存储目标类别for result in results:for box in result.boxes:x1, y1, x2, y2 = box.xyxy[0].tolist()boxes.append((int(x1), int(y1), int(x2 - x1), int(y2 - y1)))class_names.append(model.names[int(box.cls[0])])return boxes, class_names

判断鼠标点击是否在某个目标框内:

def point_in_box(x, y, box):bx, by, bw, bh = boxreturn bx <= x <= bx + bw and by <= y <= by + bh

主程序逻辑

主函数协调整个系统的工作流程:

def main():global selected_box, tracking, target_class, mouse_clicked, debugargs = parse_args()debug = args.debug# 验证跟踪器是否可用test_tracker = create_tracker(args.tracker)if test_tracker is None:returndel test_tracker# 加载YOLO模型try:model = YOLO(args.model)except Exception as e:print(f"Failed to load YOLO model: {e}")return# 打开视频源cap = cv2.VideoCapture(args.video)if not cap.isOpened():print(f"Cannot open video source: {args.video}")return# 创建窗口和鼠标回调window_name = "Tracker"cv2.namedWindow(window_name)cv2.setMouseCallback(window_name, mouse_callback)tracker = Nonefont = cv2.FONT_HERSHEY_SIMPLEXwhile True:ret, frame = cap.read()if not ret:print("End of video")break# 检测目标boxes, class_names = detect_objects(frame, model, args.confidence)# 处理点击选择目标if mouse_clicked and not tracking:for i, (box, cls) in enumerate(zip(boxes, class_names)):if point_in_box(click_x, click_y, box):selected_box = boxtarget_class = clstracker = create_tracker(args.tracker)if tracker:tracker.init(frame, selected_box)tracking = Trueprint(f"Tracking {target_class} with {args.tracker}")breakmouse_clicked = False# 跟踪逻辑if tracking and tracker:ok, bbox = tracker.update(frame)if ok:x, y, w, h = [int(v) for v in bbox]cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)cv2.putText(frame, f"Track: {target_class}", (x, y - 10),font, 0.5, (0, 255, 0), 2)else:cv2.putText(frame, "Lost", (10, 30), font, 0.7, (0, 0, 255), 2)tracking = False# 未跟踪时显示所有目标if not tracking:for box, cls in zip(boxes, class_names):x, y, w, h = boxcv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)cv2.putText(frame, cls, (x, y - 10), font, 0.5, (255, 0, 0), 2)# 显示提示信息hint = "Right click to stop" if tracking else "Left click to select"cv2.putText(frame, hint, (10, 30), font, 0.7, (0, 255, 255), 2)cv2.putText(frame, f"Tracker: {args.tracker}", (10, frame.shape[0] - 30),font, 0.5, (255, 255, 0), 2)cv2.putText(frame, f"FPS: {int(cap.get(cv2.CAP_PROP_FPS))}",(10, frame.shape[0] - 10), font, 0.5, (255, 255, 0), 2)# 显示窗口cv2.imshow(window_name, frame)# 退出条件if cv2.waitKey(1) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindows()

使用方法

环境配置

首先安装必要的依赖库:

pip install opencv-python opencv-contrib-python ultralytics numpy

运行参数

程序支持以下命令行参数:

  • --video:视频源路径,默认为 “xxxxx.mp4”,使用 “0” 可调用摄像头
  • --confidence:目标检测置信度阈值,默认 0.5
  • --model:YOLO 模型路径,默认 “yolo12n.pt”(会自动下载)
  • --tracker:跟踪算法选择,可选 “mosse”、“csrt”、“kcf”,默认 “mosse”
  • --debug:启用调试模式,打印额外信息

操作指南

  1. 运行程序后,系统默认处于目标检测模式,所有检测到的目标用蓝色框标记
  2. 左键点击某个目标框,系统将开始用绿色框跟踪该目标
  3. 右键点击可停止跟踪,回到目标检测模式
  4. 按 “q” 键退出程序

算法特性对比

三种跟踪算法各有特点:

  • MOSSE:速度最快,适合实时性要求高的场景,但精度相对较低
  • CSRT:精度最高,但速度较慢,适合对精度要求高的场景
  • KCF:介于前两者之间,平衡了速度和精度

根据实际应用场景选择合适的跟踪算法可以获得更好的效果。

总结与扩展

本文介绍的目标跟踪系统结合了 YOLO 的强检测能力和传统跟踪算法的高效性,通过简单的交互实现了灵活的目标跟踪功能。该系统可进一步扩展,例如:

  • 增加多目标跟踪功能
  • 结合 ReID(重识别)技术解决目标遮挡问题
  • 加入目标行为分析模块
  • 优化跟踪失败后的自动重新检测机制

通过这个系统,不仅可以快速实现实用的目标跟踪应用,也有助于理解目标检测与跟踪相结合的技术路线,为更复杂的计算机视觉系统开发奠定基础。

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

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

相关文章

【数据库】 MySQL 表的操作详解

在 MySQL 数据库的日常开发与维护中&#xff0c;表的操作是最基础且最常用的部分。本文将从 创建表、查看表结构、修改表 以及 删除表 等方面进行详细讲解&#xff0c;并附上对应的 SQL 语句示例&#xff0c;方便在实际项目中直接应用。一、创建表 1.1 创建表语法 CREATE TABLE…

DiT: Transformer上的扩散模型

论文&#xff08;ICCV 2023&#xff09;&#xff1a;Scalable Diffusion Models with Transformers 代码和工程网页&#xff1a;https://www.wpeebles.com/DiT.html DiTs&#xff08;Diffusion Transformers&#xff09;是首个基于Transformer架构的扩散模型&#xff01;它在…

MySQL 索引:索引为什么使用 B+树?(详解B树、B+树)

文章目录一、二叉查找树(BST)&#xff1a;不平衡二、平衡二叉树(AVL)&#xff1a;旋转耗时三、红黑树&#xff1a;树太高由一个例子总结索引的特点基于哈希表实现的哈希索引高效的查找方式&#xff1a;二分查找基于二分查找思想的二叉查找树升级版的BST树&#xff1a;AVL 树四、…

ESP32入门开发·VScode空白项目搭建·点亮一颗LED灯

目录 1. 环境搭建 2. 创建项目 3. 调试相关介绍 4. 代码编写 4.1 包含头文件 4.2 引脚配置 4.3 设置输出电平 4.4 延时函数 4.5 调试 1. 环境搭建 默认已经搭建好环境&#xff0c;如果未搭建好可参考&#xff1a; ESP32入门开发Windows平台下开发环境的搭建…

ONLYOFFICE AI 智能体上线!与编辑器、新的 AI 提供商等进行智能交互

ONLYOFFICE AI 插件​迎来重要更新&#xff0c;带来了新功能和更智能的交互体验。随着 AI 智能体&#xff08;现为测试版&#xff09;的上线、带来更多 AI 提供商支持以及其他新功能&#xff0c;AI 插件已经成为功能强大的文档智能助理。 关于 ONLYOFFICE ONLYOFFICE 文档是多…

【C++进阶学习】第十一弹——C++11(上)——右值引用和移动语义

前言&#xff1a; 前面我们已经将C的重点语法讲的大差不差了&#xff0c;但是在C11版本之后&#xff0c;又出来了很多新的语法&#xff0c;其中有一些作用还是非常大的&#xff0c;今天我们就先来学习其中一个很重要的点——右值引用以及它所扩展的移动定义 目录 一、左值引用和…

【IoTDB】363万点/秒写入!IoTDB凭何领跑工业时序数据库赛道?

【作者主页】Francek Chen 【专栏介绍】⌈⌈⌈大数据与数据库应用⌋⌋⌋ 大数据是规模庞大、类型多样且增长迅速的数据集合&#xff0c;需特殊技术处理分析以挖掘价值。数据库作为数据管理的关键工具&#xff0c;具备高效存储、精准查询与安全维护能力。二者紧密结合&#xff0…

IEEE 2025 | 重磅开源!SLAM框架用“法向量+LRU缓存”,将三维重建效率飙升72%!

一、前言 当前研究领域在基于扩散模型的文本到图像生成技术方面取得了显著进展&#xff0c;尤其在视觉条件控制方面。然而&#xff0c;现有方法&#xff08;如ControlNet&#xff09;在组合多个视觉条件时存在明显不足&#xff0c;主要表现为独立控制分支在去噪过程中容易引入…

无人机遥控器教练模式技术要点

一、技术要点1.控制权仲裁机制&#xff1a;核心功能&#xff1a;清晰定义主控权归属逻辑&#xff08;默认为学员&#xff0c;但教练随时可接管&#xff09;。切换方式&#xff1a;通常通过教练遥控器上的物理开关&#xff08;瞬时或锁定型&#xff09;或软件按钮触发。切换逻辑…

【跨服务器的数据自动化下载--安装公钥,免密下载】

跨服务器的数据自动化下载功能介绍&#xff1a;上代码&#xff1a;发现好久没写csdn了&#xff0c;说多了都是泪~~ 以后会更新一些自动化工作的脚本or 小tricks&#xff0c;欢迎交流。分享一个最近在业务上写的较为实用的自动化脚本&#xff0c;可以批量从远端服务器下载指定数…

C++-->stl: list的使用

前言list的认识list是可以在固定时间&#xff08;O&#xff08;1&#xff09;&#xff09;内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0…

本地WSL部署接入 whisper + ollama qwen3:14b 总结字幕

1. 实现功能 M4-1 接入 whisper ollama qwen3:14b 总结字幕 自动下载视频元数据如果有字幕&#xff0c;只下载字幕使用 ollama 的 qwen3:14b 对字幕内容进行总结 2.运行效果 source /root/anaconda3/bin/activate ytdlp &#x1f50d; 正在提取视频元数据… &#x1f4dd; 正在…

《Linux运维总结:Shell脚本高级特性之变量间接调用》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、变量间接调用 在Shell脚本中&#xff0c;变量间接调用是一种高级特性&#xff0c;它允许你通过另一个变量的值来动态地访问…

ABP VNext + Akka.NET:高并发处理与分布式计算

ABP VNext Akka.NET&#xff1a;高并发处理与分布式计算 &#x1f680; 用 Actor 模型把高并发写入“分片→串行化”&#xff0c;把锁与竞态压力转回到代码层面的可控顺序处理&#xff1b;依托 Cluster.Sharding 横向扩容&#xff0c;Persistence 宕机可恢复&#xff0c;Strea…

[激光原理与应用-250]:理论 - 几何光学 - 透镜成像的优缺点,以及如克服缺点

透镜成像是光学系统中应用最广泛的技术&#xff0c;其通过折射原理将物体信息转换为图像&#xff0c;但存在像差、环境敏感等固有缺陷。以下是透镜成像的优缺点及针对性改进方案&#xff1a;一、透镜成像的核心优点高效集光能力透镜通过曲面设计将分散光线聚焦到一点&#xff0…

测试匠谈 | AI语音合成之大模型性能优化实践

「测试匠谈」是优测云服务平台倾心打造的内容专栏&#xff0c;汇集腾讯各大产品的顶尖技术大咖&#xff0c;为大家倾囊相授开发测试领域的知识技能与实践&#xff0c;让测试工作变得更加轻松高效。 本期嘉宾介绍 Soren&#xff0c;腾讯TEG技术事业群质量工程师&#xff0c;负责…

用天气预测理解分类算法-从出门看天气到逻辑回归

一、生活中的决策难题&#xff1a;周末郊游的「天气判断」 周末计划郊游时&#xff0c;你是不是总会打开天气预报反复确认&#xff1f;看到 "25℃、微风、无雨" 就兴奋收拾行李&#xff0c;看到 "35℃、暴雨" 就果断取消计划。这个判断过程&#xff0c;其…

HTTPS服务

HTTPS服务 一、常见的端口 http ------ 80 明文 https ------ 443 数据加密 dns ------ 53 ssh ------ 22 telent ------ 23 HTTPS http ssl或者tls &#xff08;安全模式&#xff09; 二、原理&#xff1a; c&#xff08;客户端…

【Android笔记】Android 自定义 TextView 实现垂直渐变字体颜色(支持 XML 配置)

Android 自定义 TextView 实现垂直渐变字体颜色&#xff08;支持 XML 配置&#xff09; 在 Android UI 设计中&#xff0c;字体颜色的渐变效果能让界面看起来更加精致与现代。常见的渐变有从左到右、从上到下等方向&#xff0c;但 Android 的 TextView 默认并不支持垂直渐变。…

CANopen Magic调试软件使用

一、软件安装与硬件连接1.1 系统要求操作系统&#xff1a;Windows 7/10/11 (64位)硬件接口&#xff1a;支持Vector/PEAK/IXXAT等主流CAN卡推荐配置&#xff1a;4GB内存&#xff0c;2GHz以上CPU1.2 安装步骤运行安装包CANopen_Magic_Setup.exe选择安装组件&#xff08;默认全选&…