基于OpenCV灰度图像转GCode的双向扫描实现

  • 基于OpenCV灰度图像转GCode的双向扫描实现
    • 引言
    • 激光雕刻简介
    • OpenCV简介
    • 实现步骤
      • 1.导入必要的库
      • 2. 读取灰度图像
      • 3. 图像预处理
      • 4. 生成GCode
        • 1. 简化版的双向扫描
        • 2. 优化版的双向扫描
      • 5. 保存生成的GCode
      • 6. 灰度图像双向扫描代码示例
    • 总结

系列文章

  • ⭐深入理解G0和G1指令:C++中的实现与激光雕刻应用
  • ⭐基于二值化图像转GCode的单向扫描实现
  • ⭐基于二值化图像转GCode的双向扫描实现
  • ⭐基于二值化图像转GCode的斜向扫描实现
  • ⭐基于二值化图像转GCode的螺旋扫描实现
  • ⭐基于OpenCV灰度图像转GCode的单向扫描实现
  • ⭐基于OpenCV灰度图像转GCode的双向扫描实现
  • 基于OpenCV灰度图像转GCode的斜向扫描实现
  • 基于OpenCV灰度图像转GCode的螺旋扫描实现

系列文章GitHub仓库地址

基于OpenCV灰度图像转GCode的双向扫描实现

双向扫描优化版

引言

激光雕刻技术作为一种创新的制造方法,近年来在艺术、制作和教育领域崭露头角。本文将介绍如何使用OpenCV库实现灰度图像到GCode的双向扫描,为激光雕刻提供更灵活、更精细的图案生成方法。同时,我们将分享关键的代码片段,帮助读者理解并应用这一技术。

激光雕刻简介

激光雕刻是一种通过激光束切割或去除材料表面的工艺,通常用于制作艺术品、装饰品和原型。通过控制激光束的运动路径,可以在各种材料上创造出精细而复杂的图案。在这篇博客中,我们将使用OpenCV实现一种激光雕刻的图案生成方法,具体来说是灰度图像到GCode的双向扫描。

OpenCV简介

OpenCV是一个开源的计算机视觉库,广泛应用于图像处理、机器学习和计算机视觉领域。其强大的功能和易用性使得它成为实现图像处理任务的理想选择。在本文中,我们将使用OpenCV来处理灰度图像,并将其转换为GCode。

实现步骤

1.导入必要的库

首先,我们需要导入必要的库,包括OpenCV和一些用于图像处理的辅助库。以下是关键的CMake代码片段:

# 指向 OpenCV cmake 目录
list(APPEND CMAKE_PREFIX_PATH "~/opencv/build/x64/vc16/lib")find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
link_libraries(${OpenCV_LIBS})

把上述内容添加到 cmake 中,此时我们已经可以在 C++ 中使用 OpenCV 库

2. 读取灰度图像

使用OpenCV读取一张灰度图像,我们将其用于后续的处理。以下是代码片段:

cv::Mat mat = cv::imread(R"(~/ImageToGCode/image/tigger.jpg)", cv::IMREAD_GRAYSCALE);

确保替换 ~/ImageToGCode/image/tigger.jpg 为你自己的图像文件路径。

3. 图像预处理

在进行激光雕刻之前,我们需要对图像进行一些预处理,以确保得到清晰而准确的结果。这可能包括图像平滑、二值化、边缘检测等步骤,具体取决于你的图像和需求。以下是一个简单的翻转和二值化处理的代码片段:

cv::flip(mat, mat, 0);
cv::threshold(mat,mat,128,255,cv::ThresholdTypes::THRESH_BINARY);

4. 生成GCode

有了预处理后的图像,我们可以开始生成GCode了。GCode是一种机器语言,用于控制激光雕刻、数控机床和3D打印机等设备。

1. 简化版的双向扫描

以下是简化版的双向扫描生成GCode的代码片段:

cv::Mat image;
cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));
for(int y = 0; y < image.rows; ++y) {bool isEven = !(y & 1);int start   = isEven ? 0 : image.cols - 1;int end     = isEven ? image.cols : -1;int step    = isEven ? 1 : -1;command.emplace_back(G0 {std::nullopt, y / resolution, std::nullopt});for(int x = start; x != end; x += step) {if(auto const pixel = image.at<cv::uint8_t>(y, x); pixel == 255) {command.emplace_back(G0 {x / resolution, std::nullopt, std::nullopt});} else {auto power = static_cast<int>((1.0 - static_cast<double>(pixel) / 255.0) * 1000.0);command.emplace_back(G1(x / resolution, std::nullopt, power));}}
}

这个函数将生成一个包含GCode指令的列表,你可以将其保存到文件中,用于控制激光雕刻机器。
双向扫描简化版

2. 优化版的双向扫描

以下是优化版的双向扫描生成GCode的代码片段:

cv::Mat image;
cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));bool leftToRight {false};
bool rightToLeft {false};for(int y = 0; y < image.rows; ++y) {bool isEven = !(y & 1);int start   = isEven ? 0 : image.cols - 1;int end     = isEven ? image.cols : -1;int step    = isEven ? 1 : -1;for(int x = start; x != end; x += step) {if(auto const pixel = image.at<cv::uint8_t>(y, x); pixel == 255) {// 偶数从左到右扫描// 奇数从右到左扫描if(isEven) {// 从左到右寻找连续的G0// |----->int length {0};while(++x < end && image.at<cv::uint8_t>(y, x) == 255) {length++;}--x;// 使用 do{}while(false) 结构,最后统一判断是否会更好// findif(length) {// 起点存在连续G0if(x - length == 0) {// 此时需要把奇数行延迟的Y轴移动进行上移操作if(rightToLeft) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));rightToLeft = false;} else {// 偶数从左到右在起点永远不会向上移动,所以这里不需要 ycommand.emplace_back(G0 {x / resolution, std::nullopt, std::nullopt});}continue;}// 终点存在连续G0if(x == image.cols - 1) {// 终点需要向上移动,但这个移动我们放在奇数行处理,所以这里只需要做好标记即可。leftToRight = true;command.emplace_back(G0((x - length) / resolution, std::nullopt, std::nullopt));continue;}// 中间段存在连续从左到右方向的G0// 中间段不需要向上移动command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));} else {// 没有找到连续的G0// 终点唯一的G0,需要向上移动,这里做标记放到奇数行移动。if(x == image.cols - 1) {leftToRight = true;} else if(x == start) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));rightToLeft = false;continue;}command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));}} else {// <-----|// 从右到左寻找连续的G0 此时起点在右边,终点在左边int length {0};while(--x > end && image.at<cv::uint8_t>(y, x) == 255) {length++;}++x;if(length) {// 起点存在连续的G0if(x + length == start) {// 此时需要把偶数行延迟的Y轴移动进行上移操作if(leftToRight) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));leftToRight = false;} else {// 标记command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));}continue;}// 终点存在连续的G0if(x == 0) {rightToLeft = true;continue;}command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));} else {// 没有找到连续的G0// 终点需要向上移动if(x == 0) {rightToLeft = true;} else if(x == start) {// 起点也需要处理上一行的y轴移动if(leftToRight) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));leftToRight = false;}continue;}command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));}}} else {auto power = static_cast<int>((1.0 - static_cast<double>(pixel) / 255.0) * 1000.0);// 处理G1 开头和结尾情况if(isEven) {// 从左到右if(x == start) {if(rightToLeft) {command.emplace_back(G0 {x / resolution, y / resolution, power});  // 最大激光功率 S=1000rightToLeft = false;continue;}} else if(x == image.cols - 1) {// 终点需要标记leftToRight = true;}command.emplace_back(G1 {x / resolution, std::nullopt, power});  // 最大激光功率 S=1000} else {// 从右到左if(x == start) {if(leftToRight) {command.emplace_back(G0 {x / resolution, y / resolution, power});  // 最大激光功率 S=1000leftToRight = false;continue;}} else if(x == 0) {// 终点需要标记rightToLeft = true;}command.emplace_back(G1 {x / resolution, std::nullopt, power});  // 最大激光功率 S=1000}}  // end if G0}      // end for x
}          // end for y

这个函数将生成一个包含GCode指令的列表同时不包含非必要G0,你可以将其保存到文件中,用于控制激光雕刻机器。
双向扫描优化版

5. 保存生成的GCode

最后,我们将生成的GCode保存到文件中:

std::fstream file;
file.open(fileName, std::ios_base::out | std::ios_base::trunc);
if(!file.is_open()) {return;
}
for(auto &&v: command | std::views::transform([](auto item) { return item += "\n"; })) {file.write(v.c_str(), v.length());
}
return;

确保替换 ‘fileName’ 为你自己想要保存的文件路径。

6. 灰度图像双向扫描代码示例

#pragma once
#include <opencv2/opencv.hpp>
#include <fstream>
#include <print>
#include <vector>
#include <optional>
#include <ranges>struct G0 {std::optional<float> x, y;std::optional<int> s;std::string toString() {std::string command = "G0";if(x.has_value()) {command += std::format(" X{:.3f}", x.value());}if(y.has_value()) {command += std::format(" Y{:.3f}", y.value());}if(s.has_value()) {command += std::format(" S{:d}", s.value());}return command;}explicit  operator std::string() const {std::string command = "G0";if(x.has_value()) {command += std::format(" X{:.3f}", x.value());}if(y.has_value()) {command += std::format(" Y{:.3f}", y.value());}if(s.has_value()) {command += std::format(" S{:d}", s.value());}return command;}
};struct G1 {std::optional<float> x, y;std::optional<int> s;std::string toString() {std::string command = "G1";if(x.has_value()) {command += std::format(" X{:.3f}", x.value());}if(y.has_value()) {command += std::format(" Y{:.3f}", y.value());}if(s.has_value()) {command += std::format(" S{:d}", s.value());}return command;}explicit operator std::string() const {std::string command = "G1";if(x.has_value()) {command += std::format(" X{:.3f}", x.value());}if(y.has_value()) {command += std::format(" Y{:.3f}", y.value());}if(s.has_value()) {command += std::format(" S{:d}", s.value());}return command;}
};class ImageToGCode
{
public:// 激光模式enum class LaserMode {Cutting,    // 切割 M3 Constant PowerEngraving,  // 雕刻 M4 Dynamic Power};// 扫描方式enum class ScanMode {Unidirection,  // 单向Bidirection,   // 双向Diagonal,      // 斜向Spiral,        // 螺旋Block,         // 分块 根据像素的灰度级别进行扫描,例如255像素分8个级别,那么0-32就是一个级别,32-64就是另外一个级别,以此类推。// (Block scanning is performed based on the gray level of the pixels. For example, 255 pixels are divided into 8 levels, then 0-32 is one level, 32-64 is another level, and so on.)};struct kEnumToStringLaserMode {constexpr std::string_view operator[](const LaserMode mode) const noexcept {switch(mode) {case LaserMode::Cutting: return "M3";case LaserMode::Engraving: return "M4";}return {};}constexpr LaserMode operator[](const std::string_view mode) const noexcept {if(mode.compare("M3")) {return LaserMode::Cutting;}if(mode.compare("M4")) {return LaserMode::Engraving;}return {};}};ImageToGCode() = default;~ImageToGCode() = default;auto &setInputImage(const cv::Mat &mat) {this->mat = mat;return *this;}auto &setOutputTragetSize(double width, double height, double resolution = 10.0 /* lin/mm */) {this->width      = width;this->height     = height;this->resolution = resolution;return *this;}auto &builder() {command.clear();try {matToGCode();} catch(cv::Exception &e) {std::println("cv Exception {}", e.what());}std::vector<std::string> header;header.emplace_back("G17G21G90G54");                                                 // XY平面;单位毫米;绝对坐标模式;选择G54坐标系(XY plane; unit mm; absolute coordinate mode; select G54 coordinate system)header.emplace_back(std::format("F{:d}", 30000));                                // 移动速度 毫米/每分钟(Moving speed mm/min)header.emplace_back(std::format("G0 X{:.3f} Y{:.3f}", 0.f, 0.f));                // 设置工作起点及偏移(Set the starting point and offset of the work)header.emplace_back(std::format("{} S0", kEnumToStringLaserMode()[laserMode]));  // 激光模式(laser mode)if(airPump.has_value()) {header.emplace_back(std::format("M16 S{:d}", 300));  // 打开气泵(Turn on the air pump)}std::vector<std::string> footer;footer.emplace_back("M5");if(airPump.has_value()) {footer.emplace_back("M9");  // 关闭气泵,保持 S300 功率(Turn off air pump and maintain S300 power)}command.insert_range(command.begin(), header);command.append_range(footer);return *this;}bool exportGCode(const std::string &fileName) {std::fstream file;file.open(fileName, std::ios_base::out | std::ios_base::trunc);if(!file.is_open()) {return false;}for(auto &&v: command | std::views::transform([](auto item) { return item += "\n"; })) {file.write(v.c_str(), v.length());}return true;}auto setLaserMode(LaserMode mode) {laserMode = mode;return *this;}auto setScanMode(ScanMode mode) {scanMode = mode;return *this;}private:void matToGCode() {assert(mat.channels() == 1);assert(std::isgreaterequal(resolution, 1e-5f));assert(!((width * resolution < 1.0) || (height * resolution < 1.0)));// different conversion strategy functions are called herebidirectionOptStrategy();}// 双向扫描// Bidirectional scanningvoid bidirectionStrategy() {cv::Mat image;cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));for(int y = 0; y < image.rows; ++y) {bool isEven = !(y & 1);int start   = isEven ? 0 : image.cols - 1;int end     = isEven ? image.cols : -1;int step    = isEven ? 1 : -1;command.emplace_back(G0 {std::nullopt, y / resolution, std::nullopt});for(int x = start; x != end; x += step) {if(auto const pixel = image.at<cv::uint8_t>(y, x); pixel == 255) {command.emplace_back(G0 {x / resolution, std::nullopt, std::nullopt});} else {auto power = static_cast<int>((1.0 - static_cast<double>(pixel) / 255.0) * 1000.0);command.emplace_back(G1(x / resolution, std::nullopt, power));}}}}// 双向扫描优化// Bidirectional scanning optimizationvoid bidirectionOptStrategy() {cv::Mat image;cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));bool leftToRight {false};bool rightToLeft {false};// 可以使用 C++ 迭代器查找距离// https://en.cppreference.com/w/cpp/iterator/advance// https://en.cppreference.com/w/cpp/iterator/prevfor(int y = 0; y < image.rows; ++y) {bool isEven = !(y & 1);int start   = isEven ? 0 : image.cols - 1;int end     = isEven ? image.cols : -1;int step    = isEven ? 1 : -1;for(int x = start; x != end; x += step) {if(auto const pixel = image.at<cv::uint8_t>(y, x); pixel == 255) {// 偶数从左到右扫描// 奇数从右到左扫描if(isEven) {// 从左到右寻找连续的G0// |----->int length {0};while(++x < end && image.at<cv::uint8_t>(y, x) == 255) {length++;}--x;// 使用 do{}while(false) 结构,最后统一判断是否会更好// findif(length) {// 起点存在连续G0if(x - length == 0) {// 此时需要把奇数行延迟的Y轴移动进行上移操作if(rightToLeft) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));rightToLeft = false;} else {// 偶数从左到右在起点永远不会向上移动,所以这里不需要 ycommand.emplace_back(G0 {x / resolution, std::nullopt, std::nullopt});}continue;}// 终点存在连续G0if(x == image.cols - 1) {// 终点需要向上移动,但这个移动我们放在奇数行处理,所以这里只需要做好标记即可。leftToRight = true;command.emplace_back(G0((x - length) / resolution, std::nullopt, std::nullopt));continue;}// 中间段存在连续从左到右方向的G0// 中间段不需要向上移动command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));} else {// 没有找到连续的G0// 终点唯一的G0,需要向上移动,这里做标记放到奇数行移动。if(x == image.cols - 1) {leftToRight = true;} else if(x == start) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));rightToLeft = false;continue;}command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));}} else {// <-----|// 从右到左寻找连续的G0 此时起点在右边,终点在左边int length {0};while(--x > end && image.at<cv::uint8_t>(y, x) == 255) {length++;}++x;if(length) {// 起点存在连续的G0if(x + length == start) {// 此时需要把偶数行延迟的Y轴移动进行上移操作if(leftToRight) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));leftToRight = false;} else {// 标记command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));}continue;}// 终点存在连续的G0if(x == 0) {rightToLeft = true;;continue;}command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));} else {// 没有找到连续的G0// 终点需要向上移动if(x == 0) {rightToLeft = true;} else if(x == start) {// 起点也需要处理上一行的y轴移动if(leftToRight) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));leftToRight = false;}continue;}command.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));}}} else {auto power = static_cast<int>((1.0 - static_cast<double>(pixel) / 255.0) * 1000.0);// 处理G1 开头和结尾情况if(isEven) {// 从左到右if(x == start) {if(rightToLeft) {command.emplace_back(G0 {x / resolution, y / resolution, power});  // 最大激光功率 S=1000rightToLeft = false;continue;}} else if(x == image.cols - 1) {// 终点需要标记leftToRight = true;}command.emplace_back(G1 {x / resolution, std::nullopt, power});  // 最大激光功率 S=1000} else {// 从右到左if(x == start) {if(leftToRight) {command.emplace_back(G0 {x / resolution, y / resolution, power});  // 最大激光功率 S=1000leftToRight = false;continue;}} else if(x == 0) {// 终点需要标记rightToLeft = true;}command.emplace_back(G1 {x / resolution, std::nullopt, power});  // 最大激光功率 S=1000}}  // end if G0}      // end for x}          // end for y}// // Define additional strategy functions hereprivate:cv::Mat mat;                                 // 灰度图像double width {0};                            // 工作范围 x 轴double height {0};                           // 工作范围 y 轴double resolution {0};                       // 精度 lin/mmScanMode scanMode {ScanMode::Bidirection};   // 默认双向LaserMode laserMode {LaserMode::Engraving};  // 默认雕刻模式std::optional<int> airPump;                  // 自定义指令 气泵 用于吹走加工产生的灰尘 范围 [0,1000]// add more custom cmdstd::vector<std::string> command;            // G 代码
};int main() {cv::Mat mat = cv::imread(R"(~\ImageToGCode\image\tigger.jpg)", cv::IMREAD_GRAYSCALE);cv::flip(mat, mat, 0);cv::threshold(mat,mat,128,255,cv::ThresholdTypes::THRESH_BINARY);ImageToGCode handle;// 50x50 mm 1.0 line/mmhandle.setInputImage(mat).setOutputTragetSize(50,50,2).builder().exportGCode(R"(~\ImageToGCode\output\001.nc)");
}

总结

通过使用OpenCV库,我们成功实现了从灰度图像到GCode的双向扫描方法。这为激光雕刻提供了一种更加灵活、精细的图案生成方式。通过理解和应用上述代码片段,你可以根据自己的需求进一步调整和优化,实现更复杂的图案生成。激光雕刻的应用不仅仅局限于艺术品制作,还可以在教育和创客领域发挥巨大的创造力。希望这篇博客能够为你在激光雕刻领域的探索提供一些有用的指导。

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

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

相关文章

Matomo 访问图形显示异常

近期我们的把 PHP 系统完全升级后&#xff0c;访问 Matomo 的站点有关访问的曲线无法显示。 出现的情况如下图&#xff1a; 我们可以看到图片中有关的访问曲线无法显示。 如果具体直接访问链接的话&#xff0c;会有下面的错误信息。 问题和解决 出现上面问题的原因是缺少 ph…

我的创作128纪念日

机缘 起初我写博客是为了记录自己的学习过程,现在也是如此 实战项目中的经验分享日常学习过程中的记录通过文章进行技术交流通过文章加深学习和复习 收获 在创作过程中 获得了400多位粉丝的关注感谢大家的支持阅读数量也达到了3w在博客上认识仲秋大佬,感谢大佬对我的指导,我…

成都软件产业优势明显

是的&#xff0c;成都非常适合软件产业的发展。以下是一些原因&#xff1a; 人才储备丰富&#xff1a;成都拥有众多高等院校和科研机构&#xff0c;为软件产业提供了丰富的人才储备。这些机构培养了大量的软件人才&#xff0c;为成都软件产业的发展提供了有力支持。政策支持&a…

Java实现康复中心管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员模块 三、系统展示四、核心代码4.1 查询康复护理4.2 新增康复训练4.3 查询房间4.4 查询来访4.5 新增用药 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的康复中…

Intellij IDEA各种调试+开发中常见bug

Intellij IDEA中使用好Debug&#xff0c;主要包括如下内容&#xff1a; 一、Debug开篇 ①、以Debug模式启动服务&#xff0c;左边的一个按钮则是以Run模式启动。在开发中&#xff0c;我一般会直接启动Debug模式&#xff0c;方便随时调试代码。 ②、断点&#xff1a;在左边行…

如何在 Microsoft Azure 上部署和管理 Elastic Stack

作者&#xff1a;来自 Elastic Osman Ishaq Elastic 用户可以从 Azure 门户中查找、部署和管理 Elasticsearch。 此集成提供了简化的入门体验&#xff0c;所有这些都使用你已知的 Azure 门户和工具&#xff0c;因此你可以轻松部署 Elastic&#xff0c;而无需注册外部服务或配置…

NuxtJs安装Sass后出现ERROR:Cannot find module ‘webpack/lib/RuleSet‘

最近了解NuxtJs时&#xff0c;发现问题比较多&#xff0c;对于初学者来说是件比较头痛的事。这次是安装sass预处理器&#xff0c;通过命令安装后&#xff0c;出现了ERROR&#xff1a;Cannot find module webpack/lib/RuleSet 错误&#xff0c;于是根据之前经验&#xff0c;对版…

Python统计分析——参数估计

参考资料&#xff1a;用python动手学统计学 所谓参数就是总体分布的参数。 1、导入库 # 导入用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 导入用于绘图的库 from matplotlib import pyplot as plt import seabor…

vue前端+nodejs后端通信-简单demo

本文记录vue前端nodejs后端通讯最简单的方法&#xff0c;供广大网友最快速进入全栈开发。 技术架构 前端 vue axios 后端 nodejs express 一、前端部分-搭建VUE 项目 vue create Vnodenpm run serve 启动&#xff1b; 具体操作步骤&#xff0c;请自行百度&#xff0c;这里没…

minitouch王者荣耀按键百分比

minitouch王者荣耀按键百分比 3 技能英雄 原图 2376 x 1104 xy说明x百分比y百分比23761104总分辨率160444金币0.0673400673400670.402173913043478296440物品10.1245791245791250.398550724637681296566物品20.1245791245791250.51268115942029470864摇杆0.1978114478114480…

51单片机之LED灯模块篇

御风以翔 破浪以飏 &#x1f3a5;个人主页 &#x1f525;个人专栏 目录 点亮一盏LED灯 LED的组成原理 LED的硬件模型 点亮一盏LED灯的程序设计 LED灯闪烁 LED流水灯 独立按键控制LED灯亮灭 独立按键的组成原理 独立按键的硬件模型 独立按键控制LED灯状态 按键的抖动 独立按键…

机器学习本科课程 实验3 决策树处理分类任务

实验3.1 决策树处理分类任务 使用sklearn.tree.DecisionTreeClassifier完成肿瘤分类&#xff08;breast-cancer&#xff09;计算最大深度为10时&#xff0c;十折交叉验证的精度(accuracy)&#xff0c;查准率(precision)&#xff0c;查全率(recall)&#xff0c;F1值绘制最大深度…

Haas 开发板连接阿里云上传温湿度和电池电压

目录 一、在阿里云上创建一个产品 二、开发环境的介绍 三、创建wifi示例 四、编写SI7006和ADC驱动 五、wifi配网 六、主要源码 七、查看实现结果 一、在阿里云上创建一个产品 登录自己的阿里云账号&#xff0c; 应该支付宝&#xff0c;淘宝账号都是可以的。 接着根据需求…

设置 相关

记录使用过程中做的设置相关事宜。方便后续查询 vscode如何自动生成html格式&#xff1a; vscode快速生成html模板 --两种方法&#xff0c;亲测有用_vscode自动生成html模板-CSDN博客 使用第二个方式。存储html格式后缀。输入&#xff01;&#xff0c;vscode自动补全。 安装…

ywtool login guard命令

一.登录防护功能介绍 登录防护功能主要检查系统日志/var/log/secure&#xff0c;查看系统有没有被暴力登录。登录防护默认是检测3分钟内登录系统失败15次(次数可修改)后,视其为有攻击性,拉黑此IP(centos7通过系统文件阻止IP,centos8/9通过防火墙阻止IP)。此脚本只针对SSH访问,…

platform tree架构下i2c应用实例(HS3003)

目录 概述 1 探究platform tree下的i2c 1.1 platform tree下的i2c驱动 1.2 查看i2c总线下的设备 1.3 使用命令读写设备寄存器 2 认识HS3003 2.1 HS3003特性 2.2 HS3003寄存器 2.2.1 温湿度数据寄存器 2.2.2 参数寄存器 2.2.3 一个参数配置Demo 2.3 温湿度值转换 2.…

在工业制造方面,如何更好地实现数字化转型?

实现工业制造的数字化转型涉及利用数字技术来增强流程、提高效率并推动创新。以下是工业制造领域更好实现数字化转型的几个关键步骤&#xff1a; 1.定义明确的目标&#xff1a; 清楚地概述您的数字化转型目标。确定需要改进的领域&#xff0c;例如运营效率、产品质量或供应链…

Camunda流程引擎数据库架构

&#x1f496;专栏简介 ✔️本专栏将从Camunda(卡蒙达) 7中的关键概念到实现中国式工作流相关功能。 ✔️文章中只包含演示核心代码及测试数据&#xff0c;完整代码可查看作者的开源项目snail-camunda ✔️请给snail-camunda 点颗星吧&#x1f618; &#x1f496;数据库架构…

回归预测 | Matlab实现OOA-CNN-LSTM-Attention鱼鹰算法优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制)

回归预测 | Matlab实现OOA-CNN-LSTM-Attention鱼鹰算法优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&#xff09; 目录 回归预测 | Matlab实现OOA-CNN-LSTM-Attention鱼鹰算法优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&…

C#通过文件头判断flv文件

目录 效果 代码 效果 代码 private void button1_Click(object sender, EventArgs e) { string path Application.StartupPath "\\test.flv"; //3byte 总是FLV&#xff08;0x46 0x4C 0x56&#xff09; byte[] Type new byte[3]; using (FileStre…