如何在gazebo仿真中验证自己的yolov8识别代码,如何将自己建好的模型导入到gazebo中实现yolov8的识别功能

时间: 2025-07-08 13:28:11 AIGC 浏览: 29
<think>嗯,用户想在Gazebo里测试YOLOv8的目标检测代码,并且集成自定义模型进行识别。首先,我得回忆一下Gazebo和YOLOv8的工作流程。Gazebo是一个机器人仿真平台,而YOLOv8是目标检测模型,需要将两者结合。可能需要用到ROS来沟通Gazebo和YOLOv8的节点。 首先,用户需要确保Gazebo环境里有自定义的模型。Gazebo的模型通常放在~/.gazebo/models目录下,所以用户需要按照SDF或者URDF格式创建模型,可能需要用SolidWorks或Blender导出模型文件。然后,在Gazebo的世界文件中添加这些模型,或者通过ROS服务生成。 接下来是YOLOv8的集成。用户需要先在ROS中安装YOLOv8,可能用Ultralytics的库。然后,写一个ROS节点来订阅Gazebo的摄像头话题,比如/camera/image_raw,将图像数据传给YOLOv8进行检测。检测结果需要发布到另一个话题,比如/detection_results,可能用自定义的消息类型包含边界框、类别和置信度。 然后,可能需要将检测结果可视化,比如在Rviz中显示边界框,或者直接在图像上画框。这需要用到image_transport和cv_bridge来处理图像消息,把ROS的图像消息转成OpenCV格式,处理后再转回去发布。 另外,自定义模型的识别可能需要在YOLOv8的训练阶段加入用户自己的数据集。用户需要收集或生成带有标注的图像,训练YOLOv8模型,然后导出成PyTorch的.pt文件。在仿真中,确保Gazebo的环境光照和纹理与实际数据接近,以提高检测准确性。 可能遇到的问题包括:摄像头话题的名称不对,导致订阅不到图像;YOLOv8的依赖项安装问题;自定义模型在Gazebo中显示不正确;检测结果延迟过高。这时候需要检查话题列表,确认依赖安装正确,模型文件正确,以及优化代码性能,比如使用多线程或降低图像处理的分辨率。 用户可能还想知道如何自动化测试,比如在Gazebo中生成随机物体,然后验证YOLOv8的检测结果。或者如何将检测结果反馈给机器人控制节点,进行抓取或导航。这部分可能需要设计测试用例,或者结合MoveIt!等控制库。 最后,参考资料里的MoveIt!配置可能和这个问题关联不大,但用户提到的引用[1]可能是指集成过程中的配置步骤,比如ROS包的创建和节点配置。需要确保所有节点都正确编译和启动,可能用到launch文件来同时启动Gazebo、YOLOv8节点和其他必要的组件。</think>以下是在Gazebo中集成YOLOv8实现自定义模型目标检测仿真的步骤: ### 一、准备工作 1. 安装ROS Noetic/Melodic(推荐Noetic) 2. 配置Gazebo仿真环境: ```bash sudo apt-get install ros-$ROS_DISTRO-gazebo-ros-pkgs ``` 3. 创建自定义Gazebo模型: - 使用SolidWorks/Blender导出为STL/DAE格式 - 编写SDF文件定义模型属性(示例结构): ```xml <?xml version="1.0"?> <sdf version="1.7"> <model name="custom_object"> <link name="link"> <visual name="visual"> <geometry><mesh><uri>model://custom_object/meshes/object.dae</uri></mesh></geometry> </visual> </link> </model> </sdf> ``` 保存至`~/.gazebo/models/custom_object`[^1] ### 二、YOLOv8集成流程 1. 安装YOLOv8依赖: ```bash pip install ultralytics opencv-python-headless ``` 2. 创建ROS功能包: ```bash catkin_create_pkg yolo_detection rospy cv_bridge sensor_msgs ``` 3. 编写检测节点(`yolo_detector.py`): ```python #!/usr/bin/env python3 import rospy from sensor_msgs.msg import Image from cv_bridge import CvBridge from ultralytics import YOLO class YoloDetector: def __init__(self): self.model = YOLO("yolov8n.pt") # 替换为自定义模型路径 self.bridge = CvBridge() self.image_sub = rospy.Subscriber("/camera/image_raw", Image, self.callback) def callback(self, msg): cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8") results = self.model(cv_image) # 处理检测结果并发布到新话题 # ... if __name__ == "__main__": rospy.init_node("yolo_detector") YoloDetector() rospy.spin() ``` ### 三、仿真环境配置 1. 在Gazebo世界文件中添加相机传感器: ```xml <sensor name="camera" type="camera"> <camera> <horizontal_fov>1.047</horizontal_fov> <image> <width>640</width> <height>480</height> </image> </camera> </sensor> ``` 2. 启动仿真: ```bash roslaunch gazebo_ros empty_world.launch ``` ### 四、性能优化建议 1. 使用GPU加速:在NVIDIA显卡设备上安装CUDA支持 2. 调整检测频率: ```python # 在回调函数中添加节流控制 from functools import partial rospy.Timer(rospy.Duration(0.1), partial(self.callback, last_msg)) ``` 3. 使用多线程处理: ```python from concurrent.futures import ThreadPoolExecutor self.executor = ThreadPoolExecutor(max_workers=4) ``` ### 五、验证流程 1. 启动检测节点: ```bash rosrun yolo_detection yolo_detector.py ``` 2. 可视化检测结果: ```bash rqt_image_view /detection_result ``` **典型问题解决方案:** - 检测延迟高:降低图像分辨率至320x240 - 模型加载失败:检查PyTorch版本兼容性 - 相机无数据:验证话题名称`rostopic echo /camera/image_raw`
阅读全文

相关推荐

#include "ucar/ucar.hpp" #include <tf2/LinearMath/Quaternion.h> #include <ros/console.h> FileTransfer::FileTransfer(const std::string& baseDir ) : m_baseDir(baseDir), m_running(false), m_serverThread() { createDirectory(m_baseDir); } FileTransfer::~FileTransfer() { stopServer(); } // 功能1:保存字符串到本地文件 bool FileTransfer::saveToFile(const std::string& content, const std::string& filename) { std::string fullPath = m_baseDir + "/" + filename; std::ofstream file(fullPath); if (!file.is_open()) { std::cerr << "无法打开文件: " << fullPath << std::endl; return false; } file << content; file.close(); std::cout << "文件已保存到: " << fullPath << std::endl; return true; } // 功能2:发送文件到指定IP bool FileTransfer::sendTo(const std::string& serverIP, int port, const std::string& localFile, const std::string& remotePath ) { // 1. 读取文件内容 std::ifstream file(localFile, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "无法打开本地文件: " << localFile << std::endl; return false; } size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::vector<char> buffer(fileSize); if (!file.read(buffer.data(), fileSize)) { std::cerr << "读取文件错误: " << localFile << std::endl; return false; } file.close(); // 2. 创建Socket int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "创建Socket失败: " << strerror(errno) << std::endl; return false; } // 3. 连接服务器 sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port); if (inet_pton(AF_INET, serverIP.c_str(), &serverAddr.sin_addr) <= 0) { std::cerr << "无效的IP地址: " << serverIP << std::endl; close(sock); return false; } if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "连接服务器失败: " << strerror(errno) << std::endl; close(sock); return false; } // 4. 发送文件信息 if (!sendData(sock, remotePath, buffer.data(), fileSize)) { close(sock); return false; } close(sock); std::cout << "文件已发送到 " << serverIP << ":" << port << " 保存为 " << remotePath << std::endl; return true; } // 功能3:阻塞等待接收文件并读取内容 std::string FileTransfer::receiveAndRead(int port, int timeoutMs ) { // 确保服务器正在运行 if (!m_running) { startServer(port); } // 等待文件到达 ReceivedFile file; if (!waitForFile(file, timeoutMs)) { throw std::runtime_error("等待文件超时或服务器停止"); } stopServer(); // 返回文件内容 return std::string(file.content.data(), file.content.size()); } // 启动服务器(后台线程) void FileTransfer::startServer(int port) { if (m_running) { stopServer(); } m_running = true; m_serverThread = std::thread(&FileTransfer::serverThreadFunc, this, port); } // 停止服务器 void FileTransfer::stopServer() { if (m_running) { m_running = false; if (m_serverThread.joinable()) { m_serverThread.join(); } } } // 创建目录 bool FileTransfer::createDirectory(const std::string& path) { if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) { std::cerr << "无法创建目录: " << path << " - " << strerror(errno) << std::endl; return false; } return true; } // 发送数据到socket bool FileTransfer::sendData(int sock, const std::string& remotePath, const char* data, size_t size) { // 1. 发送路径长度和路径 uint32_t pathLen = htonl(static_cast<uint32_t>(remotePath.size())); if (send(sock, &pathLen, sizeof(pathLen), 0) != sizeof(pathLen)) { std::cerr << "发送路径长度失败" << std::endl; return false; } if (send(sock, remotePath.c_str(), remotePath.size(), 0) != static_cast<ssize_t>(remotePath.size())) { std::cerr << "发送路径失败" << std::endl; return false; } // 2. 发送文件长度和内容 uint32_t netSize = htonl(static_cast<uint32_t>(size)); if (send(sock, &netSize, sizeof(netSize), 0) != sizeof(netSize)) { std::cerr << "发送文件长度失败" << std::endl; return false; } ssize_t totalSent = 0; while (totalSent < static_cast<ssize_t>(size)) { ssize_t sent = send(sock, data + totalSent, size - totalSent, 0); if (sent < 0) { std::cerr << "发送文件内容失败: " << strerror(errno) << std::endl; return false; } totalSent += sent; } return true; } // 服务器线程函数 void FileTransfer::serverThreadFunc(int port) { // 1. 创建Socket int listenSock = socket(AF_INET, SOCK_STREAM, 0); if (listenSock < 0) { std::cerr << "创建Socket失败: " << strerror(errno) << std::endl; return; } // 2. 设置SO_REUSEADDR int opt = 1; setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 3. 绑定端口 sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "绑定端口失败: " << strerror(errno) << std::endl; close(listenSock); return; } // 4. 开始监听 if (listen(listenSock, 5) < 0) { std::cerr << "监听失败: " << strerror(errno) << std::endl; close(listenSock); return; } std::cout << "文件接收服务器启动,监听端口: " << port << std::endl; // 5. 接受连接循环 bool receivedFile = false; while (m_running&& !receivedFile) { // 设置超时以定期检查停止条件 fd_set readSet; FD_ZERO(&readSet); FD_SET(listenSock, &readSet); timeval timeout{0, 100000}; // 100ms int ready = select(listenSock + 1, &readSet, nullptr, nullptr, &timeout); if (ready < 0) { if (errno != EINTR) { std::cerr << "select错误: " << strerror(errno) << std::endl; } continue; } if (ready == 0) continue; // 超时,继续循环 // 6. 接受客户端连接 sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSock = accept(listenSock, (sockaddr*)&clientAddr, &clientLen); if (clientSock < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { std::cerr << "接受连接失败: " << strerror(errno) << std::endl; } continue; } char clientIP[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN); std::cout << "收到来自 " << clientIP << " 的文件传输请求" << std::endl; // 7. 接收文件 ReceivedFile file; if (receiveFile(clientSock, file)) { std::lock_guard<std::mutex> lock(m_mutex); m_receivedFiles.push(std::move(file)); receivedFile = true; m_cv.notify_one(); // 通知等待线程 } close(clientSock); } close(listenSock); std::cout << "文件接收服务器已停止" << std::endl; } // 接收文件 bool FileTransfer::receiveFile(int sock, ReceivedFile& file) { // 1. 接收路径长度和路径 uint32_t pathLen; if (recv(sock, &pathLen, sizeof(pathLen), 0) != sizeof(pathLen)) { std::cerr << "接收路径长度失败" << std::endl; return false; } pathLen = ntohl(pathLen); std::vector<char> pathBuffer(pathLen + 1); ssize_t received = recv(sock, pathBuffer.data(), pathLen, 0); if (received != static_cast<ssize_t>(pathLen)) { std::cerr << "接收路径失败" << std::endl; return false; } pathBuffer[pathLen] = '\0'; file.filename = pathBuffer.data(); // 2. 接收文件长度 uint32_t fileSize; if (recv(sock, &fileSize, sizeof(fileSize), 0) != sizeof(fileSize)) { std::cerr << "接收文件长度失败" << std::endl; return false; } fileSize = ntohl(fileSize); // 3. 接收文件内容 file.content.resize(fileSize); ssize_t totalReceived = 0; while (totalReceived < static_cast<ssize_t>(fileSize) && m_running) { ssize_t bytes = recv(sock, file.content.data() + totalReceived, fileSize - totalReceived, 0); if (bytes <= 0) { if (bytes < 0) { std::cerr << "接收文件内容失败: " << strerror(errno) << std::endl; } return false; } totalReceived += bytes; } std::cout << "成功接收文件: " << file.filename << " (" << fileSize << " 字节)" << std::endl; return true; } // 等待文件到达 bool FileTransfer::waitForFile(ReceivedFile& file, int timeoutMs) { std::unique_lock<std::mutex> lock(m_mutex); // 只要有文件就立即返回 auto pred = [&] { return !m_receivedFiles.empty(); // 只需检查队列是否非空 }; if (timeoutMs > 0) { if (!m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), pred)) { return false; // 超时 } } else { m_cv.wait(lock, pred); } if (m_receivedFiles.empty()) return false; file = std::move(m_receivedFiles.front()); m_receivedFiles.pop(); return true; } // 接收文件并读取两行内容 bool FileTransfer::receiveAndReadTwoStrings(int port, std::string& outStr1, std::string& outStr2, int timeoutMs) { // 确保服务器正在运行 if (!m_running) { startServer(port); } // 等待文件到达 ReceivedFile file; if (!waitForFile(file, timeoutMs)) { throw std::runtime_error("等待文件超时或服务器停止"); } // 读取文件内容 std::string content(file.content.data(), file.content.size()); // 查找第一行结尾 size_t pos = content.find('\n'); if (pos == std::string::npos) { // 没有换行符,整个内容作为第一行 outStr1 = content; outStr2 = ""; } else { // 提取第一行 outStr1 = content.substr(0, pos); // 提取第二行(跳过换行符) outStr2 = content.substr(pos + 1); // 移除第二行可能的多余换行符 if (!outStr2.empty() && outStr2.back() == '\n') { outStr2.pop_back(); } } return true; } bool Ucar::navigateTo(const geometry_msgs::Pose& target) { move_base_msgs::MoveBaseGoal goal; goal.target_pose.header.frame_id = "map"; goal.target_pose.pose = target; move_base_client_.sendGoal(goal); return move_base_client_.waitForResult(navigation_timeout_); } // void Ucar::recovery() { // setlocale(LC_ALL, ""); // std_srvs::Empty srv; // if (clear_costmaps_client_.call(srv)) { // ROS_INFO("代价地图清除成功"); // } else { // ROS_ERROR("代价地图清除服务调用失败"); // } // } Ucar::Ucar(ros::NodeHandle& nh) : nh_(nh), retry_count_(0), current_state_(State::INIT) , control_rate_(10) ,move_base_client_("move_base", true),is_find(false),aucro_yes_or_no(false) { latest_odom_.reset(); // 等待move_base服务器就绪(参考网页6的actionlib使用规范) if(!move_base_client_.waitForServer(ros::Duration(5.0))) { setlocale(LC_ALL, ""); ROS_ERROR("无法连接move_base服务器"); } scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); // 初始化代价地图清除服务 clear_costmaps_client_ = nh_.serviceClient<std_srvs::Empty>("/move_base/clear_costmaps"); setlocale(LC_ALL, ""); //20 nh_.param("pose1_x", pose_1.position.x, 1.67); nh_.param("pose1_y", pose_1.position.y, 0.04); pose_1.orientation.x = 0.0; pose_1.orientation.y = 0.0; pose_1.orientation.z = 0.707; pose_1.orientation.w = 0.707; nh_.param("pose2_x", pose_2.position.x, 1.83); nh_.param("pose2_y", pose_2.position.y, 0.380); pose_2.orientation.x = 0.0; pose_2.orientation.y = 0.0; pose_2.orientation.z = 1; pose_2.orientation.w = 0; nh_.param("pose3_x", pose_3.position.x, 1.0); nh_.param("pose3_y", pose_3.position.y, 0.494); pose_3.orientation.x = 0.0; pose_3.orientation.y = 0.0; pose_3.orientation.z = 1; pose_3.orientation.w = 0; nh_.param("pose4_x", pose_4.position.x, 0.15166891130059032); nh_.param("pose4_y", pose_4.position.y, 0.5446138339072089); pose_4.orientation.x = 0.0; pose_4.orientation.y = 0.0; pose_4.orientation.z = 0.707; pose_4.orientation.w = 0.707; nh_.param("pose5_x", pose_5.position.x, 0.387605134459779); nh_.param("pose5_y", pose_5.position.y, 0.949243539748394); pose_5.orientation.x = 0.0; pose_5.orientation.y = 0.0; pose_5.orientation.z = 0.0; pose_5.orientation.w = 1.0; nh_.param("pose6_x", pose_6.position.x, 1.0250469329539987); nh_.param("pose6_y", pose_6.position.y, 1.0430107266961729); pose_6.orientation.x = 0.0; pose_6.orientation.y = 0.0; pose_6.orientation.z = 0.0; pose_6.orientation.w = 1.0; nh_.param("pose7_x", pose_7.position.x, 1.715746358650675); nh_.param("pose7_y", pose_7.position.y, 1.0451169673664757); pose_7.orientation.x = 0.0; pose_7.orientation.y = 0.0; pose_7.orientation.z = 0.707; pose_7.orientation.w = 0.707; nh_.param("pose8_x", pose_8.position.x, 1.820954899866641); nh_.param("pose8_y", pose_8.position.y, 1.405578846446346); pose_8.orientation.x = 0.0; pose_8.orientation.y = 0.0; pose_8.orientation.z = 1; pose_8.orientation.w = 0; nh_.param("pose9_x", pose_9.position.x, 1.287663212010699); nh_.param("pose9_y", pose_9.position.y, 1.4502232396357953); pose_9.orientation.x = 0.0; pose_9.orientation.y = 0.0; pose_9.orientation.z = 1; pose_9.orientation.w = 0; nh_.param("pose10_x", pose_10.position.x, 0.283025661760874); nh_.param("pose10_y", pose_10.position.y, 1.5362058814619577); pose_10.orientation.x = 0.0; pose_10.orientation.y = 0.0; pose_10.orientation.z = 0.707; pose_10.orientation.w = 0.707; //开始进入视觉定位区域 中心点加上四个中线点对应四面墙 nh_.param("pose11_x", pose_11.position.x, 0.89); nh_.param("pose11_y", pose_11.position.y, 2.375); pose_11.orientation.x = 0.0; pose_11.orientation.y = 0.0; pose_11.orientation.z = 0.707; pose_11.orientation.w = 0.707; nh_.param("pose12_x", pose_12.position.x, 0.89); nh_.param("pose12_y", pose_12.position.y, 2.4775); pose_12.orientation.x = 0.0; pose_12.orientation.y = 0.0; pose_12.orientation.z = -0.707; pose_12.orientation.w = 0.707; nh_.param("pose13_x", pose_13.position.x, 0.89); nh_.param("pose13_y", pose_13.position.y, 3.9725); pose_13.orientation.x = 0.0; pose_13.orientation.y = 0.0; pose_13.orientation.z = 0.707; pose_13.orientation.w = 0.707; nh_.param("pose14_x", pose_14.position.x, 1.21775); nh_.param("pose14_y", pose_14.position.y, 3.225); pose_14.orientation.x = 0.0; pose_14.orientation.y = 0.0; pose_14.orientation.z = 0; pose_14.orientation.w = 1.0; nh_.param("pose15_x", pose_15.position.x, 0.5295325); nh_.param("pose15_y", pose_15.position.y, 3.225); pose_15.orientation.x = 0.0; pose_15.orientation.y = 0.0; pose_15.orientation.z = 1.0; pose_15.orientation.w = 0; //退出视觉定位区域进入路灯识别区域,路灯识别过程中是有角度要求的 //在多次测验中发现该地图大致逆时针偏向约60度,因此要消除60度的误差,因为此时地图的建模不标准 nh_.param("pose16_x", pose_16.position.x, 2.07); nh_.param("pose16_y", pose_16.position.y, 3.78); pose_16.orientation.x = -0.5; pose_16.orientation.y = 0.0; pose_16.orientation.z = 0; pose_16.orientation.w = 0.866; nh_.param("pose17_x", pose_17.position.x, 2.37); nh_.param("pose17_y", pose_17.position.y, 3.78); pose_17.orientation.x = -0.3536; pose_17.orientation.y = 0.3536; pose_17.orientation.z = 0.6124; pose_17.orientation.w = 0.6124; nh_.param("pose18_x", pose_18.position.x, 4.09); nh_.param("pose18_y", pose_18.position.y, 3.78); pose_18.orientation.x = -0.3536; pose_18.orientation.y = 0.3536; pose_18.orientation.z = 0.6124; pose_18.orientation.w = 0.6124; //退出路灯识别区域进入巡线区域,注意两个点位取一个,有方向要求 nh_.param("pose19_x", pose_19.position.x, 2.27); nh_.param("pose19_y", pose_19.position.y, 2.93); pose_19.orientation.x = 0; pose_19.orientation.y = 0; pose_19.orientation.z = 0; pose_19.orientation.w = 1.0; nh_.param("pose18_x", pose_20.position.x, 4.29); nh_.param("pose18_y", pose_20.position.y, 2.93); pose_20.orientation.x = 0; pose_20.orientation.y = 0; pose_20.orientation.z = 0; pose_20.orientation.w = 1.0; //result_sub_ = nh_.subscribe("result_of_object", 1, &Ucar::detectionCallback, this); result_client_ = nh_.serviceClient<rfbot_yolov8_ros::DetectGood>("object_realsense_recognization"); cmd_vel_pub = nh_.advertise<geometry_msgs::Twist>("/cmd_vel", 10); odom_sub = nh_.subscribe("/odom", 10, &Ucar::odomCallback, this); interaction_client = nh_.serviceClient<ucar::ObjectDetection>("object_detection"); ROS_INFO_STREAM("状态机初始化完成"); } void Ucar::navigate_to_aruco_place(){ setlocale(LC_ALL, ""); navigateTo(pose_1);ROS_INFO("到达pose_1"); navigateTo(pose_2);ROS_INFO("到达pose_2"); navigateTo(pose_3);ROS_INFO("到达pose_3"); } //走出迷宫,进入视觉定位区域 void Ucar::navigate_to_recongnition_place(){ setlocale(LC_ALL, ""); navigateTo(pose_4);ROS_INFO("到达pose_4"); navigateTo(pose_5);ROS_INFO("到达pose_5"); navigateTo(pose_6);ROS_INFO("到达pose_6"); navigateTo(pose_7);ROS_INFO("到达pose_7"); navigateTo(pose_8);ROS_INFO("到达pose_8"); navigateTo(pose_9);ROS_INFO("到达pose_9"); navigateTo(pose_10);ROS_INFO("到达pose_10"); current_state_=State::FIND2; } //在视觉定位区域中转,找到目标 void Ucar::navigate_to_recongnition_place2(){ setlocale(LC_ALL, ""); navigateTo(pose_15);ROS_INFO("到达pose_15"); spin_to_find(); if(!is_find){ navigateTo(pose_12);ROS_INFO("到达pose_12"); spin_to_find(); } if(!is_find){ navigateTo(pose_13);ROS_INFO("到达pose_13"); spin_to_find(); } if(!is_find){ navigateTo(pose_14);ROS_INFO("到达pose_14"); spin_to_find(); } if(!is_find){ navigateTo(pose_11);ROS_INFO("到达pose_11"); spin_to_find(); } current_state_ = State::GAZEBO; } //主从机交互阶段 void Ucar::navigate_to_recongnition_place3(){ setlocale(LC_ALL, ""); navigateTo(pose_11);ROS_INFO("到达pose_11"); } //退出视觉定位区域进入路灯识别区域 void Ucar::navigate_to_recongnition_place4(){ setlocale(LC_ALL, ""); navigateTo(pose_16);ROS_INFO("到达pose_16"); navigateTo(pose_17);ROS_INFO("到达pose_17"); rfbot_yolov8_ros::DetectGood srv; Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); light = srv.response.goodName[0]; } if(light=="red") {ans=0;navigateTo(pose_18);ROS_INFO("到达pose_18");playAudioFile("/home/ucar/ucar_ws/src/wav/2.wav");} else if(light=="green") {ans=1;playAudioFile("/home/ucar/ucar_ws/src/wav/1.wav");} current_state_ = State::LINE; } //退出路灯识别区域进入巡线区域 void Ucar::navigate_to_recongnition_place5(){ setlocale(LC_ALL, ""); if(ans==1) {navigateTo(pose_19);ROS_INFO("到达pose_19");} else if(ans==0) {navigateTo(pose_20);ROS_INFO("到达pose_20");} } void Ucar::run() { while (ros::ok()) { ros::spinOnce(); switch(current_state_) { case State::INIT: {handleInit(); break;} case State::ARUCO: {navigate_to_aruco_place(); ROS_INFO("导航到二维码区域"); while(!aucro_yes_or_no){ see_aruco(); moveForwardDistance(0.05,0.1); } if(target_object=="vegetable") playAudioFile("/home/ucar/ucar_ws/src/wav/sc.wav"); if(target_object=="fruit") playAudioFile("/home/ucar/ucar_ws/src/wav/sg.wav"); if(target_object=="dessert") playAudioFile("/home/ucar/ucar_ws/src/wav/tp.wav"); break;} case State::FIND: {navigate_to_recongnition_place(); break;} //在视觉定位区域中转,找到目标 case State::FIND2: {navigate_to_recongnition_place2();break;} //主从机交互阶段 case State::GAZEBO: {navigate_to_recongnition_place3(); gazebo(); break;} //退出视觉定位区域进入路灯识别区域 case State::LIGHT: {navigate_to_recongnition_place4(); break; } //退出路灯识别区域进入巡线区域 case State::LINE: {navigate_to_recongnition_place5(); break;} control_rate_.sleep(); } }} void Ucar::handleInit() { setlocale(LC_ALL, ""); ROS_DEBUG("执行初始化流程"); current_state_ = State::ARUCO; } void Ucar::see_aruco() { qr_found_ = false; image_transport::ImageTransport it(nh_); // 2. 创建订阅器,订阅/usb_cam/image_raw话题 image_transport::Subscriber sub = it.subscribe( "/usb_cam/image_raw", 1, &Ucar::imageCallback, this ); std::cout << "已订阅摄像头话题,等待图像数据..." << std::endl; // 3. 创建ZBar扫描器 scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); target_object = ""; // 重置结果 // 4. 设置超时检测 const int MAX_SCAN_DURATION = 30; // 最大扫描时间(秒) auto scan_start_time = std::chrono::steady_clock::now(); // 5. ROS事件循环 ros::Rate loop_rate(30); // 30Hz while (ros::ok() && !qr_found_) { // 检查超时 auto elapsed = std::chrono::steady_clock::now() - scan_start_time; if (std::chrono::duration_cast<std::chrono::seconds>(elapsed).count() > MAX_SCAN_DURATION) { std::cout << "扫描超时" << std::endl; break; } ros::spinOnce(); loop_rate.sleep(); } // 6. 清理资源 if (!qr_found_) target_object = ""; aucro_yes_or_no=true; } void Ucar::imageCallback(const sensor_msgs::ImageConstPtr& msg) { try { // 7. 将ROS图像消息转换为OpenCV格式 cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8); cv::Mat frame = cv_ptr->image; cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); // 8. 准备ZBar图像 cv::Mat gray_cont = gray.clone(); zbar::Image image(gray.cols, gray.rows, "Y800", gray_cont.data, gray.cols * gray.rows); // 9. 扫描二维码 if (scanner.scan(image) >= 0) { for (zbar::Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol) { // 获取原始结果 std::string rawResult = symbol->get_data(); // 在存储到全局变量前将首字母转换为小写 target_object = toLowerFirstChar(rawResult); ROS_INFO("识别到二维码: %s", target_object.c_str()); current_state_ = State::FIND; qr_found_ = true; // 在视频帧上绘制结果 std::vector<cv::Point> points; for (int i = 0; i < symbol->get_location_size(); i++) { points.push_back(cv::Point(symbol->get_location_x(i), symbol->get_location_y(i))); } // 绘制二维码边界框 cv::RotatedRect rect = cv::minAreaRect(points); cv::Point2f vertices[4]; rect.points(vertices); for (int i = 0; i < 4; i++) { cv::line(frame, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 255, 0), 2); } // 显示原始二维码内容 cv::putText(frame, rawResult, cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 0), 2); // 显示转换后的结果 cv::putText(frame, "Stored: " + target_object, cv::Point(10, 60), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 255), 2); // 显示最终结果画面 cv::imshow("QR Code Scanner", frame); cv::waitKey(250); // 短暂显示结果 } } // 显示当前帧 cv::imshow("QR Code Scanner", frame); cv::waitKey(1); } catch (cv_bridge::Exception& e) { ROS_ERROR("cv_bridge异常: %s", e.what()); } } //播放音频文件函数 bool Ucar::playAudioFile(const std::string& filePath) { // 创建子进程播放音频 pid_t pid = fork(); if (pid == 0) { // 子进程 // 尝试使用 aplay (适用于 WAV 文件) execlp("aplay", "aplay", "-q", filePath.c_str(), (char*)NULL); // 如果 aplay 失败,尝试 ffplay execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", filePath.c_str(), (char*)NULL); // 如果 ffplay 失败,尝试 mpg123 (适用于 MP3) execlp("mpg123", "mpg123", "-q", filePath.c_str(), (char*)NULL); // 如果所有播放器都不可用,退出 exit(1); } else if (pid > 0) { // 父进程 int status; waitpid(pid, &status, 0); return WIFEXITED(status) && WEXITSTATUS(status) == 0; } else { // fork 失败 perror("fork failed"); return false; } } // 辅助函数:将字符串首字母转换为小写 std::string Ucar::toLowerFirstChar(const std::string& str) { if (str.empty()) return str; std::string result = str; result[0] = static_cast<char>(std::tolower(static_cast<unsigned char>(result[0]))); return result; } // 在odomCallback中更新最新里程计数据 void Ucar::odomCallback(const nav_msgs::Odometry::ConstPtr& msg) { latest_odom_ = msg; // 保存最新里程计数据 if (!position_recorded) { initial_position = msg->pose.pose.position; position_recorded = true; //ROS_INFO("初始位置记录完成: (%.3f, %.3f)", // initial_position.x, initial_position.y); } } void Ucar::moveLeftDistance(double distance_m, double linear_speed) { // 订阅里程计话题(根据实际机器人配置修改话题名) // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始左移: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.y = linear_speed; // 全向移动Y轴速度控制左右移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.y = 0; cmd_vel_pub.publish(move_cmd); //ROS_INFO("左移完成"); } void Ucar::moveRightDistance(double distance_m, double linear_speed) { // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始右移: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.y = -linear_speed; // 全向移动Y轴速度控制左右移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.y = 0; cmd_vel_pub.publish(move_cmd); //ROS_INFO("右移完成"); } void Ucar::moveForwardDistance(double distance_m, double linear_speed) { // 确保速度为正值(前进方向) if (linear_speed < 0) { ROS_WARN("前进速度应为正值,自动取绝对值"); linear_speed = fabs(linear_speed); } // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始前进: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.x = linear_speed; // X轴速度控制前后移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.x = 0.0; cmd_vel_pub.publish(move_cmd); ROS_INFO("前进完成"); } //输入1逆时针,输入-1顺时针 void Ucar::rotateCounterClockwise5Degrees(int a) { // 设置旋转参数 const double angular_speed = 0.2; // rad/s (约11.5度/秒) const double degrees = 5.0; const double duration = degrees * (M_PI / 180.0) / angular_speed; // 约0.436秒 //if(a==1) //ROS_INFO("开始逆时针旋转5度: 速度=%.2f rad/s, 时间=%.3f秒", // angular_speed, duration); //else if(a==-1) //ROS_INFO("开始顺时针旋转5度: 速度=%.2f rad/s, 时间=%.3f秒", // angular_speed, duration); // 创建控制消息 geometry_msgs::Twist twist_msg; twist_msg.angular.z = angular_speed*a; // 正值表示逆时针 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.05).sleep(); // 20Hz控制周期 } // 发送停止命令(确保接收) twist_msg.angular.z = 0.0; for (int i = 0; i < 3; i++) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.02).sleep(); } if(a==1) ROS_INFO("逆时针旋转5度完成"); else if (a==-1) ROS_INFO("顺时针旋转5度完成"); } void Ucar::rotateCounterClockwise360Degrees() { // 设置旋转参数 const double angular_speed = 1; // rad/s (约11.5度/秒) const double degrees = 360.0; const double duration = degrees * (M_PI / 180.0) / angular_speed; // 约0.436秒 // 创建控制消息 geometry_msgs::Twist twist_msg; twist_msg.angular.z = angular_speed; // 正值表示逆时针 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.05).sleep(); // 20Hz控制周期 } // 发送停止命令(确保接收) twist_msg.angular.z = 0.0; for (int i = 0; i < 3; i++) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.02).sleep(); } } void Ucar::left_and_right_move_old(){ rfbot_yolov8_ros::DetectGood srv; while((find_object_x2_old+find_object_x1_old)/2>324){ if((find_object_x2_old+find_object_x1_old)>340) moveLeftDistance(0.15,0.1);//控制小车往左移动15cm else moveLeftDistance(0.05,0.1);//控制小车往左移动5cm Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } ROS_INFO("当前目标中心横坐标为:%.1f",(find_object_x2_old+find_object_x1_old)/2); } while((find_object_x2_old+find_object_x1_old)/2<316){ if((find_object_x2_old+find_object_x1_old)<300) moveRightDistance(0.15,0.1);//控制小车往右移动15cm else moveRightDistance(0.05,0.1);//控制小车往右移动5cm Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } ROS_INFO("当前目标中心横坐标为:%.1f",(find_object_x2_old+find_object_x1_old)/2); } ROS_INFO("左右移动完成"); } //前进函数(涵盖第二种停靠算法) void Ucar::go(){ rfbot_yolov8_ros::DetectGood srv; while(1){ moveForwardDistance(0.05,0.1);//控制小车前进 Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_new=srv.response.u1[j]; find_object_y1_new=srv.response.v1[j]; find_object_x2_new=srv.response.u2[j]; find_object_y2_new=srv.response.v2[j]; } else continue; } if(find_object_x1_new<=10&&find_object_y1_new<=10&&find_object_x2_new>=630&&find_object_y2_new>=360){ ROS_INFO("靠中间,不白头"); break; } //图像左边先到达边线——>逆时针往右 else if(find_object_x2_new>=630&&find_object_y1_new<=10&&find_object_x1_new!=0&&find_object_y2_new>=350){ if(find_object_x1_new>240&&find_object_y2_new>360){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.45,0.1); ROS_INFO("超级大摆头"); } else if(find_object_x1_new>240||(find_object_x2_new-find_object_x1_new)<=(find_object_y2_new-find_object_y1_new)||(find_object_y2_new<375&&find_object_x1_new>130)||(find_object_y2_new-find_object_x1_new>130)){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.35,0.1); ROS_INFO("大摆头"); break; } else if(find_object_x1_new>120||(find_object_y2_new<360&&find_object_x1_new>65)||(find_object_y2_new>360&&find_object_x1_new>110)){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.25,0.1); ROS_INFO("中摆头"); break; } else{ rotateCounterClockwise5Degrees(1);//逆时针 moveRightDistance(0.1,0.1); ROS_INFO("小摆头"); break; } } //图像右边先到达边线——>顺时针往左 else if(find_object_x1_new<=10&&find_object_y1_new<=10&&find_object_x2_new!=640&&find_object_y2_new>=350){ if(find_object_x2_new<400&&find_object_y2_new>360){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.45,0.1); ROS_INFO("超级大摆头"); } else if(find_object_x2_new<400||(find_object_x2_new-find_object_x1_new)<=(find_object_y2_new-find_object_y1_new)||(find_object_y2_new<345&&find_object_x2_new<510)||(find_object_y2_new-find_object_x2_new<130)){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.35,0.1); ROS_INFO("大摆头"); break; } else if(find_object_x2_new<520||(find_object_y2_new<360&&find_object_x2_new<585)||(find_object_y2_new>360&&find_object_x2_new<530)){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.25,0.1); ROS_INFO("中摆头"); break; } else{ rotateCounterClockwise5Degrees(-1);//顺时针 moveLeftDistance(0.1,0.1); ROS_INFO("小摆头"); break; } } } } void Ucar::visualservo(){ is_find=true; rfbot_yolov8_ros::DetectGood srv; Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } //左移右移 left_and_right_move_old(); //提取长宽比 Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { if(srv.response.goodName[j].find(target_object)!=std::string::npos){ ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[j]; find_object_y1_old=srv.response.v1[j]; find_object_x2_old=srv.response.u2[j]; find_object_y2_old=srv.response.v2[j]; } else continue; } changkuanbi_old=(find_object_x2_old-find_object_x1_old)/(find_object_y2_old-find_object_y1_old); ROS_INFO("长宽比为:%f",changkuanbi_old);go(); if(find_object=="dessert1") playAudioFile("/home/ucar/ucar_ws/src/wav/kl.wav"); if(find_object=="dessert2") playAudioFile("/home/ucar/ucar_ws/src/wav/dg.wav"); if(find_object=="dessert3") playAudioFile("/home/ucar/ucar_ws/src/wav/nn.wav"); if(find_object=="vegetable1") playAudioFile("/home/ucar/ucar_ws/src/wav/lj.wav"); if(find_object=="vegetable2") playAudioFile("/home/ucar/ucar_ws/src/wav/xhs.wav"); if(find_object=="vegetable3") playAudioFile("/home/ucar/ucar_ws/src/wav/td.wav"); if(find_object=="fruit1") playAudioFile("/home/ucar/ucar_ws/src/wav/xj.wav"); if(find_object=="fruit2") playAudioFile("/home/ucar/ucar_ws/src/wav/pg.wav"); if(find_object=="fruit3") playAudioFile("/home/ucar/ucar_ws/src/wav/xg.wav"); // if(abs(changkuanbi_old-1.666666667)<0.05){ // ROS_INFO("比较接近16:9,不需要旋转"); // //前进 // go(); // } // else { // //先逆时针转个10度 // rotateCounterClockwise5Degrees(1); // rotateCounterClockwise5Degrees(1); // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new); // if(changkuanbi_new<changkuanbi_old)//方向错了 // { // while(abs(changkuanbi_new-1.666666667)>0.4)//不准就再转 // { // rotateCounterClockwise5Degrees(-1); // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // //保持正对目标 // while((find_object_x2_new+find_object_x1_new)/2>324) // {moveLeftDistance(0.05,0.1);//控制小车往左移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new);} // while((find_object_x2_new+find_object_x1_new)/2<316) // {moveRightDistance(0.05,0.1);//控制小车往右移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new);} // } // } // } // else{//方向对了 // while(abs(changkuanbi_new-1.666666667)>0.4)//不准就再转 // { // rotateCounterClockwise5Degrees(1); // //保持正对目标 // while((find_object_x2_new+find_object_x1_new)/2>324) moveLeftDistance(0.05,0.1);//控制小车往左移动5cm // while((find_object_x2_new+find_object_x1_new)/2<316) moveRightDistance(0.05,0.1);//控制小车往右移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new); // } // } // //前进 // go(); // } ROS_INFO("导航完成"); ros::Duration(3).sleep(); current_state_ = State::GAZEBO; } void Ucar::spin_to_find(){ setlocale(LC_ALL, ""); //旋转扫描参数 const double rotation_speed=2.0; const double rotation_duration=2;//每次旋转时间 const double pause_duration=0.6;//move_base暂停时间 double total_rotation=0.0;//总旋转角度 //开始旋转 while(total_rotation<12.56){ geometry_msgs::Twist twist_cmd; twist_cmd.angular.z=rotation_speed; ros::Time start_rotate=ros::Time::now(); while ((ros::Time::now()-start_rotate).toSec()<rotation_duration) { cmd_vel_pub.publish(twist_cmd); ros::Duration(0.5).sleep(); } twist_cmd.angular.z=0.0; cmd_vel_pub.publish(twist_cmd); total_rotation+=rotation_speed*rotation_duration; //停顿进行目标的检测 ros::Time start_pause = ros::Time::now(); while ((ros::Time::now()-start_pause).toSec()90)){ ROS_INFO("目标检测成功"); find_object=srv.response.goodName[i]; //进入视觉定位函数 visualservo(); return; } else continue; } } ros::Duration(0.1).sleep(); } ROS_INFO("导航失败"); } } void Ucar::gazebo(){ 主从机通信 FileTransfer ft("/home/ucar/ucar_ws/src/ucar"); ft.saveToFile(target_object, "target.txt"); //电脑的ip ft.sendTo("192.168.1.100", 8080, "/home/ucar/ucar_ws/src/ucar/target.txt", "/home/zzs/gazebo_test_ws/src/ucar2/target.txt"); try { // 示例3:等待接收文件并读取内容 std::cout << "等待接收文件..." << std::endl; ft.receiveAndReadTwoStrings(8080, gazebo_object, gazebo_room); std::cout << "接收到的内容:\n" << gazebo_object << "\n"<<gazebo_room << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl;} ros::Duration(3).sleep();//停留3秒 current_state_ = State::LIGHT; } int main(int argc, char** argv) { ros::init(argc, argv, "multi_room_navigation_node"); ros::NodeHandle nh; Ucar ucar(nh); ucar.run(); return 0; }仿真任务结束后不进行下面阶段,帮我修改一下,同时解决在前往15,12,13,14等等电位时在这些点位上进行旋转时候导航发生了严重的偏移,帮我修改一下,给出解决上述两个问题的完整代码

import rclpy from rclpy.node import Node from sensor_msgs.msg import CompressedImage from geometry_msgs.msg import Twist from cv_bridge import CvBridge import cv2 import numpy as np import time # 假设YoloNV12Inferencer类在test1_yolo_bin模块中 from test1_yolo_bin import YoloNV12Inferencer class LineAndObstacleNode(Node): def __init__(self): super().__init__('line_and_obstacle_node') self.bridge = CvBridge() self.subscription = self.create_subscription( CompressedImage, '/image', self.image_callback, 10) self.publisher_ = self.create_publisher(Twist, '/cmd_vel', 10) # YOLO 模型加载 try: self.inferencer = YoloNV12Inferencer('/root/dev_ws/3w/bin/yolov5s_672x672_nv12.bin') self.get_logger().info("✅ YOLO模型加载成功") except Exception as e: self.get_logger().error(f"YOLO加载失败: {e}") self.inferencer = None # 避障参数 self.focal_length = 550.0 self.image_cx = 320.0 self.target_height = 0.3 self.repulsion_range = 0.5 self.state = 'normal' # normal / avoiding_turn / avoiding_straight / realignment self.avoid_start_time = 0 self.obstacle_list = [] # 车道线检测参数 self.min_speed = 0.25 self.lost_lane_counter = 0 self.lost_lane_threshold = 5 self.current_frame = None self.last_valid_alpha = 0.0 # 状态超时参数 self.REALIGN_TIMEOUT = 1.5 # 回正阶段最大超时时间 self.AVOID_TURN_DURATION = 0.6 # 避障转向持续时间 self.timer = self.create_timer(0.1, self.control_loop) def image_callback(self, msg): try: img = self.bridge.compressed_imgmsg_to_cv2(msg, desired_encoding='bgr8') # 保存原始图像用于YOLO检测 self.raw_image = img.copy() # --- 巡线区域处理 --- h1, width = img.shape[:2] crop = img[int(h1 * 0.4):, :] self.current_frame = cv2.resize(crop, (160, 120)) # --- YOLO处理 --- if self.inferencer: detections = self.inferencer.infer(self.raw_image) self.obstacle_list = [] for class_id, score, (x1, y1, x2, y2) in detections: h = y2 - y1 if h <= 0: continue distance = (self.target_height * self.focal_length) / h cx = (x1 + x2) / 2 offset_y = (cx - self.image_cx) * distance / self.focal_length if class_id == 0: # 假设0是障碍物类别 self.obstacle_list.append((distance, offset_y)) except Exception as e: self.get_logger().error(f"图像处理错误: {e}") def decide_avoid_direction(self, obstacle): _, y = obstacle return -1 if y > 0.1 else 1 def detect_lane_line(self, frame): """检测车道线并返回速度、转向角和检测状态""" if frame is None: return 0.0, 0.0, False try: hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 黑色线条 HSV 阈值 lower_black = np.array([0, 0, 0]) upper_black = np.array([180, 255, 50]) black_mask = cv2.inRange(hsv, lower_black, upper_black) # 形态学操作增强检测 kernel = np.ones((5, 5), np.uint8) black_mask = cv2.morphologyEx(black_mask, cv2.MORPH_OPEN, kernel) black_mask = cv2.dilate(black_mask, kernel, iterations=2) # 检测区域高度 detection_band = 40 x_coords = np.where(black_mask[-detection_band:] == 255)[1] if len(x_coords) > 50: # 确保有足够的像素点 self.lost_lane_counter = 0 mean_x = np.mean(x_coords) h, w = frame.shape[:2] alpha = float(-(mean_x - w / 2) / (w / 2)) self.last_valid_alpha = alpha # 保存有效转向角 return 0.2, alpha, True else: self.lost_lane_counter += 1 if self.lost_lane_counter >= self.lost_lane_threshold: return 0.0, 0.0, False else: # 使用最后有效的转向角 return 0.10, self.last_valid_alpha, False except Exception as e: self.get_logger().error(f"车道线检测错误: {e}") return 0.0, 0.0, False def control_loop(self): now = time.time() elapsed_time = now - self.avoid_start_time # 障碍物检测(仅考虑前方0.5米内的障碍物) valid_obs = [o for o in self.obstacle_list if 0 < o[0] < self.repulsion_range and abs(o[1]) < 0.5] # 状态机实现 if self.state == 'avoiding_turn': # 右转避障阶段 if elapsed_time < self.AVOID_TURN_DURATION: self.publish_cmd(0.15, -0.8) # 右转避障 else: self.state = 'realignment' self.avoid_start_time = now self.get_logger().info("🔄 进入回正阶段") elif self.state == 'realignment': # 回正阶段(优先检测车道线) v, alpha, lane_detected = self.detect_lane_line(self.current_frame) if lane_detected: self.state = 'normal' self.get_logger().info("✅ 检测到车道线,恢复巡线") self.publish_cmd(v, alpha) elif elapsed_time > self.REALIGN_TIMEOUT: self.state = 'normal' self.get_logger().warn("⏱️ 回正超时,强制恢复巡线") else: # 缓慢直行并轻微右转寻找车道线 self.publish_cmd(0.1, -0.3) elif valid_obs and self.state == 'normal': # 检测到障碍物,开始避障. self.state = 'avoiding_turn' self.avoid_start_time = now self.get_logger().warn(f"🚧 检测到障碍物,距离={min(o[0] for o in valid_obs):.2f}m") else: # 正常巡线模式 v, alpha, lane_detected = self.detect_lane_line(self.current_frame) if lane_detected: self.publish_cmd(v, alpha) else: # 未检测到车道线时的安全策略 self.publish_cmd(0.05, self.last_valid_alpha) def publish_cmd(self, speed, steer): """发布控制指令,带平滑过渡""" msg = Twist() msg.linear.x = float(speed) msg.angular.z = float(steer) self.publisher_.publish(msg) def main(args=None): rclpy.init(args=args) node = LineAndObstacleNode() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() 你帮我检查检查上述代码中的避障之后回正的部分,我在实体小车上试发现小车在识别到障碍物之后,先向右转了一个小弯,接着就不回正了,直接按原来转的弯走,在那里绕圈圈,这个改怎么解决

最新推荐

recommend-type

【地理信息科学】基于SRAI的荷兰土地利用处理优化:H3网格化与POI数据嵌入分析系统实现

内容概要:本文介绍了一个基于SRAI框架优化的荷兰土地利用数据处理流程,重点在于通过H3地理网格系统对荷兰区域进行区域化划分,并结合OpenStreetMap(OSM)中的POI数据,提取与土地利用相关的特定标签(如住宅、商业、工业、自然地貌等),进行高效的数据下载、空间交集分析与特征嵌入(embedding)训练。流程涵盖创建研究区域边界、生成H3网格、下载并处理POI数据、与网格进行空间连接、训练多种嵌入模型(如计数嵌入和Hex2Vec),并保存结果与生成可视化图示,提升了处理效率与错误容错能力。; 适合人群:具备地理信息系统(GIS)、Python编程及空间数据分析基础的科研人员或数据工程师,熟悉pandas、geopandas、H3等工具的用户;; 使用场景及目标:①实现城市尺度的土地利用特征提取与空间分布分析;②构建地理区域的低维向量表示用于下游机器学习任务;③优化大规模地理数据处理流程,提升数据加载与模型训练效率; 阅读建议:建议结合SRAI官方文档运行代码,注意配置好依赖环境(如含PyTorch的srai[torch]),并根据实际数据量调整H3分辨率与嵌入参数,建议在Linux或高性能计算环境中执行。
recommend-type

MATLAB中最大面积覆盖算法的实现_Implementation of a maximum area coverage

MATLAB中最大面积覆盖算法的实现_Implementation of a maximum area coverage algorithm in MATLAB.zip
recommend-type

RaspberryMatic与Docker整合:CCU2固件容器化操作指南

### Docker与CCU2固件整合 #### 知识点1:Docker容器技术 Docker是一种开源的容器化平台,它允许开发者将应用及其依赖打包到一个可移植的容器中,该容器可以在任何支持Docker的机器上运行。Docker容器和传统的虚拟机不同,它不需要完整的操作系统镜像,而是利用宿主机的操作系统内核,实现了轻量级的隔离,启动速度快,资源消耗低。 #### 知识点2:CCU2固件与OpenHAB CCU2(CCU代表Comet Control Unit)固件通常用在HomeMatic智能家居自动化系统中,它负责管理和控制HomeMatic的设备。CCU2运行的是一个基于Linux的自定义系统,专门优化用于与HomeMatic硬件和软件通信。当把CCU2固件用于Docker容器时,意味着你可以在任何支持Docker的设备上,通过容器化的方式部署和运行CCU2环境,从而支持HomeMatic设备的控制。 #### 知识点3:RaspberryMatic RaspberryMatic是为树莓派量身打造的一个项目,它允许用户在树莓派上运行CCU2固件。项目提供了一整套的HomeMatic体验,包括备份功能、Dutty-Cycle、LAN GW等。RaspberryMatic的一个显著优点是支持多种架构,包括x86_64/amd64、ARM和ARM64。 #### 知识点4:Docker容器部署脚本 "docker-ccu"项目提供了一套脚本,这些脚本能够自动化创建一个Docker容器来运行CCU2固件。通常这类脚本命名为`deploy.sh`,开发者或者最终用户可以通过运行这些脚本来快速部署和启动Docker容器,而无需手动配置和启动容器的每一个步骤。 #### 知识点5:数据备份与迁移 在使用Docker容器进行部署时,用户可能需要在不同环境下迁移数据或者保留原有数据。脚本中提到了数据保留的问题,如果用户之前使用的是其他方式部署,比如非Docker方式或者使用了特定的docker卷或者容器名称,那么在调用`deploy.sh`脚本部署时,需要对设置进行相应的调整,以保证数据的完整性。 #### 知识点6:仓库维护与开源社区 项目维护者提到了不再计划继续更新该存储库,并提出了将仓库设置为只读模式的想法。这在开源社区中是比较常见的情况,尤其是在维护者有新的兴趣点或者由于个人时间限制时。在此情况下,开源项目可以通过社区协作来继续维护,或者寻求其他维护者的接手。 #### 知识点7:Shell脚本编写 由于项目中提到了一个叫做`deploy.sh`的脚本文件,这说明脚本是用Shell语言编写的。Shell脚本非常适合于执行自动化任务,比如配置环境、启动服务、管理文件系统等,因此在自动化部署或系统管理中经常被使用。了解Shell脚本编写,对于自动化管理Docker容器等任务至关重要。 #### 知识点8:社区支持和反馈 项目维护者在描述中提到,如果在一个月内没有收到任何关于将官方CCU作为容器使用的反馈,将会把仓库设置为只读模式。这表明了开源社区中项目的发展很大程度上依赖于社区成员的反馈和支持。因此,了解如何与开源项目互动,提交问题、建议和补丁,是参与开源社区的重要途径。 #### 知识点9:固件概念与兼容性 CCU2固件特别设计用于某些特定硬件,但通过Docker化的方式,开发者可以跨平台运行CCU2固件,这增加了固件的兼容性。Docker的隔离性允许用户在一个通用的软件层面上运行原本可能受限于特定硬件的固件,从而扩展了固件的应用场景。 #### 知识点10:操作系统架构支持 项目支持包括x86_64/amd64、ARM和ARM64在内的多种架构,说明了Docker容器在不同硬件平台上的高度可移植性。这一特点使得开发者可以在各种硬件上部署相同的环境,简化了跨平台应用的开发和部署。 #### 结语 该文档提供了一个关于如何将特定固件整合入Docker容器的方案,并说明了项目维护者对于未来发展的规划。这些内容不仅对有志于尝试或扩展该项目的个人有指导意义,同时也展示了开源社区协作以及Docker技术在部署和管理复杂系统环境中的重要性和便利性。
recommend-type

手把手封装SDK:C#如何高效集成汉印D35BT打印功能

# 摘要 本文围绕C# SDK封装与汉印D35BT打印机集成的技术实践展开,系统阐述了SDK封装的理论基础、架构设计及面向对象设计原则的应用。文章分析了汉印D35BT打印机的通信协议与API调用方式,并详细介绍了在C#中实现蓝牙设备交互与数据发送的方法。通过核心打印功能的类封装、异步任务处理机制的设计,提升了SDK的易用性与扩展性。结合WinForm项目示例验证功能完整性后,进一步探讨了SDK的性能优化策略、测试方法及发布流程,构建了从设计、实现到部署的完整技术路径。 # 关键字 SDK封装;蓝牙通信;面向对象设计;异步打印;API调用;NuGet包发布 参考资源链接:[C#开
recommend-type

VM虚拟机ubuntu桥接主机无线网络

### 配置 VMware Ubuntu 桥接模式连接无线网络 在 VMware 中配置 Ubuntu 虚拟机通过桥接模式连接主机的无线网络,需要确保虚拟机与主机处于同一网络段,并能够通过主机的无线网卡直接访问外部网络。以下是详细的配置步骤: #### VMware 设置桥接模式 1. **以管理员权限运行 VMware**,进入 **编辑 > 虚拟网络编辑器**。 2. 在 **虚拟网络编辑器** 界面中,找到 **VMnet0(桥接模式)** 的设置部分。 3. 在 **“桥接到”** 的下拉菜单中,选择主机的无线网卡设备。 4. 勾选 **“自动设置桥接”** 选项,确保 VMwar
recommend-type

Ruby on Rails跳蚤市场应用开发详解

根据提供的文件信息,我们可以从中提炼出以下知识点: ### 标题知识点 - **freemarket_sample_72h** - 标题暗示这是一份关于名为“freemarket”的跳蚤市场应用程序的72小时开发样例或原型。 - 样例名称“freemarket_sample_72h”可能用于内部标识或者版本控制,表明该样本是在有限的时间内(即72小时内)完成的。 ### 描述知识点 - **网站结构** - 首页:应用程序的入口点,通常包含总体介绍和导航链接。 - 产品页面:展示产品的列表或者详细信息。 - 展览页:可能指专门展示某些特殊产品或促销产品的页面。 - 应用信息:关于应用程序的基本信息,如版本号、开发团队、联系方式等。 - 应用概述:对应用程序功能和目标用户群体的简介。 - **用户账户信息** - 测试账号:为开发者或测试者提供的虚拟用户账号信息,以便进行应用程序的内部测试。 - 购买者信息:提供了邮箱地址、密码以及购买卡信息,是进行交易和购买所必需的。 - 卖家信息:提供了卖家的邮箱地址和密码,用于登录卖家账户进行产品上架和管理。 - **功能列表** - 新用户注册:允许新用户创建账户。 - 登录功能:用户可以使用凭证登录应用程序。 - 产品列表功能:展示所有可购买的产品。 - 产品购买功能:用户可以购买产品,涉及到支付信息的处理。 - 产品类别注册和显示:允许用户查看不同的产品分类。 - 产品详细信息显示:展示每个产品的详细信息,如描述、价格等。 - 编辑和删除列出的产品:赋予管理员或卖家权利更新或移除产品信息。 - **开发环境** - Ruby 2.5.1:这是Ruby编程语言的一个版本。 - Ruby on Rails 5.4.2:这是一个使用Ruby语言编写的开源Web应用框架。 - MySQL 14.14:这是一个流行的开源关系型数据库管理系统。 - Github:一个用于代码托管和版本控制的平台。 - AWS:亚马逊提供的云服务平台,包括EC2(弹性计算云)和S3(简单存储服务)。 - Capistrano:是一个开源的自动化部署工具,常用于Ruby on Rails项目。 - **开发周期和工作时间** - 开发时间:约4周,说明了项目从开始到完成所需的时间。 - 每天平均工作时间:大约9小时,表明项目的紧凑和开发团队的努力。 - 开发系统人数:4,指出了参与该项目的开发人员数量。 - 敏捷类型:可能指的是一种开发过程,强调快速迭代和响应变化。 ### 标签知识点 - **Ruby** - 这个标签直接指向了Ruby编程语言,说明该应用程序是使用Ruby开发的。 ### 压缩包子文件的文件名称列表知识点 - **freemarket_sample_72h-master** - 这是源代码压缩包的文件名称,指示了一个版本控制中的主分支(master)。 通过这些信息,我们可以了解到该应用程序是一个在线跳蚤市场,它允许用户注册、登录、查看和购买产品,并且提供了产品管理功能。项目采用Ruby语言和Ruby on Rails框架开发,并使用了多种技术和平台,如MySQL数据库、Github、AWS云服务以及Capistrano部署工具。项目开发遵循敏捷开发原则,并在一个紧凑的时间表中完成。
recommend-type

【C#条码打印实战技巧】:汉印D35BT数据格式转换全攻略

# 摘要 本文围绕C#语言实现条码打印的技术方案展开,重点以汉印D35BT打印机为实践对象,系统性地讲解了条码打印中数据格式的基本原理与处理方法。文章分析了条码打印的数据流向、通信协议与命令集结构,探讨了在C#开发环境下实现数据格式转换、命令封装与容错机制的关键技术。通过完整的打印流程实现、性能优化策略以及常见问题排查方法的介绍,帮助开发者构建高效稳定的条码打印应用。同时,文章还展望了条码打印技术在多协议支持、云服务集成与企业级系统对接方面的拓展方向。 # 关键字 条码打印;数据格式;C#开发;通信协议;命令封装;容错机制 参考资源链接:[C#开发汉印D35BT条码打印机源代码
recommend-type

pikaqiu靶场搭建linux

在Linux系统上搭建名为“Pikachu”的靶场环境,主要涉及以下几个步骤: ### 下载并安装Pikachu靶场 首先,需要确保系统中已安装Git工具。如果尚未安装,可以通过以下命令安装Git: ```bash sudo yum install git ``` 接下来,使用Git从GitHub上克隆Pikachu靶场的源代码到本地: ```bash git clone https://github.com/zhuifengshaonianhanlu/pikachu.git ``` 完成下载后,将Pikachu靶场文件移动到Apache服务器的根目录下,以便于后续的Web访问:
recommend-type

jsTris: 探索俄罗斯方块的JavaScript实现

俄罗斯方块是一款经典的电子游戏,自1984年由苏联程序员阿列克谢·帕基特诺夫在计算机上首次开发以来,就以其简单而又富有挑战性的玩法吸引了世界各地的玩家。随着时间的发展,许多开发者开始尝试使用不同的编程语言和平台对俄罗斯方块进行重制或创新。本篇将详细介绍关于一个名为“jsTris”的俄罗斯方块游戏的JavaScript实现。 ### 1. JavaScript与Web游戏开发 JavaScript是一种广泛用于网页前端开发的脚本语言,它能够让网页拥有动态交互功能。自ECMAScript 5版本之后,JavaScript性能得到了显著的提升,使其逐渐成为开发Web游戏的理想选择。通过HTML5的Canvas API与JavaScript的结合,开发者可以创建出流畅、富有吸引力的图形界面,为用户带来良好的游戏体验。 ### 2.俄罗斯方块游戏机制 俄罗斯方块的基本玩法是玩家需要移动、旋转和放置一系列下落的方块,使它们在底部拼成完整的一行或多行,这样可以消除方块并获得分数。当方块堆积到屏幕顶部时,游戏结束。游戏难度会随着时间的推移而逐渐增加。 ### 3. jsTris项目概述 "jsTris"是俄罗斯方块的一个JavaScript版本,由一位不具名的开发者于2014年开发,并上传到了GitHub上进行开源。项目中包含了所有实现俄罗斯方块游戏逻辑的JavaScript代码,以及必要的HTML和CSS文件,用以构建游戏界面。 尽管作者自述代码“非常混乱”,而且表示自己没有回过头来清理过,这可能意味着对于初学者和后来的维护者来说,理解原始代码的结构和逻辑可能会有一定难度。不过,即使代码结构不佳,jsTris仍然可以作为一个学习的资源,开发者可以通过重构和优化来提升代码质量,同时也更好地理解游戏背后的逻辑。 ### 4. 音乐资源 在描述中提到了音乐来源,但并未给出具体的音乐文件信息。通常情况下,Web游戏会使用一些背景音乐和效果音来增强游戏体验。在jsTris项目中,音乐文件可能被嵌入到了项目中,或者通过外部链接引入。音乐的版权问题在此类开源项目中需要特别注意,开发者使用音乐时应确保拥有相应的使用权或音乐已经处于公共领域。 ### 5. 标签和文件结构 本项目的标签是"JavaScript",表明该项目完全是使用JavaScript进行开发的。关于"jsTris-master"这个文件名,它可能是项目中的主文件夹,包含了游戏的核心代码和资源文件。在一个典型的JavaScript项目结构中,可能包括以下部分: - HTML文件:定义游戏的结构和入口点。 - CSS文件:负责游戏的样式和视觉效果。 - JavaScript文件:包含游戏逻辑、控制和交互代码。 - 音频文件:用于游戏背景音乐和各种效果音。 - 图片文件:可能包括游戏中的图标、角色或背景。 ### 6. 开源与社区 该项目被上传到了GitHub,这是一个全球性的开源社区,允许开发者贡献代码,共同改进项目。在GitHub上,jsTris项目可能拥有自己的README文件,用于说明如何运行游戏、如何贡献代码或报告问题等。开源项目对于开发者来说是学习和实践编程技巧的宝贵资源,同时也可以通过社区获得帮助和反馈,从而改进项目。 ### 7. 清理与重构代码的重要性 提到jsTris的代码"非常混乱",对于任何类型的软件项目而言,可读性和可维护性都是极其重要的。混乱的代码会导致开发者难以理解,更不用说进行进一步的开发或优化。因此,对于jsTris或任何类似项目,代码重构是一个需要认真对待的过程。重构可以提高代码质量,降低维护成本,并可能修复一些潜在的错误。 ### 总结 jsTris项目作为一款使用JavaScript实现的俄罗斯方块游戏,向我们展示了如何利用Web技术进行游戏开发。虽然存在代码结构上的问题,但它无疑为有兴趣学习和改进的开发者提供了实践机会。通过深入分析和可能的代码重构,不仅可以提升jsTris项目的质量,也可以使开发者自己在JavaScript编程和游戏开发方面获得宝贵的经验。
recommend-type

从失败到稳定打印:汉印D35BT常见问题排查与解决方案大全

# 摘要 本文围绕汉印D35BT打印机的使用与故障排查展开系统研究,首先介绍其基本功能与典型应用场景,进而从打印质量、通信连接及任务处理等方面深入分析常见故障的成因,涵盖硬件适配、环境影响、数据通信及固件兼容性等多个维度。基于理论分析,本文进一步提出针对典型问题的实操解决方案,并探讨打印性能优化、固件升级及企业系统集成等高级应用策略。通过案例分析与实践经验总结,旨在提升设备运行稳定性与打印效率,为企业用户提供可靠的技术支持和运维指导。 # 关键字 汉印D35BT;打印质量;蓝牙通信;打印缓冲区;固件升级;数据格式适配 参考资源链接:[C#开发汉印D35BT条码打印机源代码及二次