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

  • 基于OpenCV灰度图像转GCode的单向扫描实现
    • 引言
    • 单向扫描存在的问题
    • 灰度图像单向扫描代码示例
    • 结论

系列文章

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

⭐**系列文章GitHub仓库地址**

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

本文将介绍如何使用OpenCV库将灰度图转换为GCode,并通过单向扫描实现对图像的激光雕刻。GCode是一种用于控制数控机床和3D打印机的指令语言,而OpenCV是一种开源计算机视觉库。通过结合这两者,我们可以实现从图像到GCode的转换,进而在机器上实现图像的物理输出。

引言

在数字制造时代,将图像转换为GCode是实现自动化加工和打印的关键步骤。本文将探讨如何利用OpenCV库将灰度图转换为GCode,并通过单向扫描的方式实现对图像的激光雕刻。
未优化的单向扫描
上图是未做任何处理,直接从灰度图转换成GCode。
已优化的单向扫描
优化后生成的GCode如上所示:
原始图像
原始图像如上所示:

单向扫描存在的问题

单向操作存在来回折返空行程问题,导致加工时间变长。
本文主要通过使用以下形式的代码,删除了多余的行程(空跑没任何意义的G0)。

while(++x < image.cols && image.at<std::uint8_t>(y, x) == 255) {length++;
}
--x;

实现了未优化版本和优化版本的单向扫描,两者加工时间从生成的GCode代码上,可以看出有了很大差异。

红色是 G0,绿色是加工部分 G1。

当然如果使用双向扫描方向,加工时间差异会更大。

灰度图像单向扫描代码示例

编译器要求最低 C++23

#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,   // 双向};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坐标系header.emplace_back(std::format("F{:d}", 30000));                                // 移动速度 毫米/每分钟header.emplace_back(std::format("G0 X{:.3f} Y{:.3f}", 0.f, 0.f));                // 设置工作起点及偏移header.emplace_back(std::format("{} S0", kEnumToStringLaserMode()[laserMode]));  // 激光模式if(airPump.has_value()) {header.emplace_back(std::format("M16 S{:d}", 300));  // 打开气泵}std::vector<std::string> footer;footer.emplace_back("M5");if(airPump.has_value()) {footer.emplace_back("M9");  // 关闭气泵,保持 S300 功率}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)));unidirectionStrategy();}void internal(cv::Mat &image, auto x /*width*/, auto y /*height*/) {auto pixel = image.at<cv::uint8_t>(y, x);if(pixel == 255) {command.emplace_back(G0(x / resolution, y / resolution, std::nullopt));} else {auto power = static_cast<int>((1.0 - static_cast<double>(pixel) / 255.0) * 1000.0);command.emplace_back(G1(x / resolution, y / resolution, power));}}// 单向扫描// 未做任何优化处理,像素和G0、G1一一映射对应。void unidirectionStrategy() {cv::Mat image;cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));cv::imshow("mat",image);cv::waitKey(0);for(int y = 0; y < image.rows; ++y) {command.emplace_back(G0(0, y / resolution, std::nullopt).toString());for(int x = 0; x < image.cols; ++x) {auto pixel = image.at<uchar>(y, x);if(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));}}}}// 单向扫描优化版本V1// 删除多余空行程,这里空行程指连续的无用的G0。void unidirectionOptStrategy() {cv::Mat image;cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));int offset = 0;  // The frist consecutive G0int length = 0;for(int y = 0; y < image.rows; ++y) {command.emplace_back(G0(offset / resolution, y / resolution, std::nullopt).toString());for(int x = 0; x < image.cols; ++x) {auto pixel = image.at<uchar>(y, x);length     = 0;if(pixel == 255) {while(++x < image.cols && image.at<std::uint8_t>(y, x) == 255) {length++;}--x;// Whether continuous GO existsif(length) {if(x - length == 0) {  // skip The frist consecutive G0offset = length;command.emplace_back(G0((x) / resolution, std::nullopt, std::nullopt));continue;}if(x == image.cols - 1) {  // skip The last consecutive G0command.emplace_back(G0((x - length) / resolution, std::nullopt, std::nullopt));continue;}// Continuous GOcommand.emplace_back(G0(x / resolution, std::nullopt, std::nullopt));} else {// Independent GOcommand.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));}}}}// Define additional strategy functions here
private: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);// 实例化一个对象ImageToGCode handle;// 设置相关参数// setInputImage 输入图像// setOutputTragetSize 输出物理尺寸大小 以 mm 为单位,这里输出 50x50 mm 大小// builder 开始执行图像转GCode操作// exportGCode 导出 gcode 文件handle.setInputImage(mat).setOutputTragetSize(50,50).builder().exportGCode(R"(ImageToGCode\output\001.nc)");
}

结论

通过结合OpenCV和GCode,我们成功地将灰度图转换为机器可执行的指令,实现了对图像的单向扫描激光雕刻。这种方法可应用于数控机床和3D打印机等领域,为数字制造提供了更灵活的图像处理和加工方式。

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

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

相关文章

安装newman显示required node version >=16解决办法

直接安装对应版本就行&#xff0c;我的Node.js是14.16.0的版本&#xff0c;newman安装5.2.2的就不会出错。 npm install -g newman5.2.2

linux centos安装neofetch

简介 neofetch是一个命令行工具&#xff0c;可以用来显示系统的基本信息和硬件配置。它支持多种操作系统&#xff0c;包括Linux、macOS和Windows等。 安装 增加yum源 curl -o /etc/yum.repos.d/konimex-neofetch-epel-7.repo https://copr.fedorainfracloud.org/coprs/konime…

WebSocket相关问题

1.WebSocket是什么&#xff1f;和HTTP的区别&#xff1f; WebSocket是一种基于TCP连接的全双工通信协议&#xff0c;客户端和服务器仅需要一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并且支持双向数据的传输。WebSocket和HTTP都是基于TCP的应用层协议&am…

【pikachu csrf】

cxrf 个人理解getPOST 个人理解 当被攻击用户登陆访问网站时&#xff0c;在保持登陆状态时点击小黑子&#xff08;黑客&#xff09;搭建的恶意链接而导致用户受到攻击。 举个例子 我去攻击网站&#xff0c;但是我找不到漏洞&#xff0c;这个时候我注册一个账号&#xff0c;发现…

数据分析基础之《pandas(5)—文件读取与存储》

一、概述 1、我们的数据大部分存在于文件当中&#xff0c;所以pandas会支持复杂的IO操作&#xff0c;pandas的API支持众多文件格式&#xff0c;如CSV、SQL、XLS、JSON、HDF5 二、CSV 1、读取csv文件 read_csv(filepath_or_buffer, sep,, delimiterNone) 说明&#xff1a; fi…

vmware网络配置,VMware的三种网络模式详解与配置

vmware为我们提供了三种网络工作模式 vmware为我们提供了三种网络工作模式, 它们分别是: Bridged&#xff08;桥接模式&#xff09;、NAT&#xff08;网络地址转换模式&#xff09;、Host-Only&#xff08;仅主机模式&#xff09;。 VMware虚拟机的三种网络类型的适用场景如下…

【防止重复提交】Redis + AOP + 注解的方式实现分布式锁

文章目录 工作原理需求实现1&#xff09;自定义防重复提交注解2&#xff09;定义防重复提交AOP切面3&#xff09;RedisLock 工具类4&#xff09;过滤器 请求工具类5&#xff09;测试Controller6&#xff09;测试结果 工作原理 分布式环境下&#xff0c;可能会遇到用户对某个接…

【资料分享】基于单片机大气压监测报警系统电路方案设计、基于飞思卡尔的无人坚守点滴监控自动控制系统设计(程序,原理图,pcb,文档)

基于单片机大气压监测报警系统电路方案设计 功能&#xff1a;实现的是大气压检测报警系统&#xff0c;可以通过传感器实时检测当前大气压值&#xff0c;可以设定大气压正常范围&#xff0c;当超过设定范围进行报警提示。 资料&#xff1a;protues仿真&#xff0c;程序&#x…

从头开始构建和训练 Transformer(下)

导 读 上一篇推文从头开始构建和训练 Transformer&#xff08;上&#xff09;https://blog.csdn.net/weixin_46287760/article/details/136048418介绍了构建和训练Transformer的过程和构建每个组件的代码示例。本文将使用数据对该架构进行代码演示&#xff0c;验证其模型性能。…

2-1 动手学深度学习v2-Softmax回归-笔记

回归 VS 分类 回归估计一个连续值分类预测一个离散类别 从回归到多类分类 回归 单连续数值输出输出的区间&#xff1a;自然区间 R \mathbb{R} R损失&#xff1a;跟真实值的区别 分类 通常多个输出&#xff08;这个输出的个数是等于类别的个数&#xff09;输出的第 i i i…

MATLAB知识点:矩阵的除法

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章 3.4.2 算术运算 下面我们再来介绍矩阵的除法。事…

企业数字化转型面临什么挑战?

数字化转型是一个复杂且持续的过程&#xff0c;涉及将数字技术集成到组织的各个方面&#xff0c;从根本上改变组织的运营方式和为客户提供价值的方式。虽然具体的挑战可能因企业的性质和规模而异&#xff0c;但一些常见的挑战包括&#xff1a; 1.抵制变革&#xff1a; 文化阻…

Java入门之JavaSe(韩顺平p1-p?)

学习背景&#xff1a; 本科搞过一段ACM、研究生搞了一篇B会后&#xff0c;本人在研二要学Java找工作啦~~&#xff08;宇宙尽头是Java&#xff1f;&#xff09;爪洼纯小白入门&#xff0c;C只会STL、python只会基础Pytorch、golang参与了一个Web后端项目&#xff0c;可以说项目小…

Flink-CDC实时读Postgresql数据

前言 CDC,Change Data Capture,变更数据获取的简称,使用CDC我们可以从数据库中获取已提交的更改并将这些更改发送到下游,供下游使用。这些变更可以包括INSERT,DELETE,UPDATE等。 用户可以在如下的场景使用cdc: 实时数据同步:比如将Postgresql库中的数据同步到我们的数仓中…

Python初学者学习记录——python基础综合案例:数据可视化——动态柱状图

一、案例效果 通过pyecharts可以实现数据的动态显示&#xff0c;直观的感受1960~2019年世界各国GDP的变化趋势 二、通过Bar构建基础柱状图 反转x轴和y轴 标签数值在右侧 from pyecharts.charts import Bar from pyecharts.options import LabelOpts# 构建柱状图对象 bar Bar()…

二进制安全虚拟机Protostar靶场(7)heap2 UAF(use-after-free)漏洞

前言 这是一个系列文章&#xff0c;之前已经介绍过一些二进制安全的基础知识&#xff0c;这里就不过多重复提及&#xff0c;不熟悉的同学可以去看看我之前写的文章 heap2 程序静态分析 https://exploit.education/protostar/heap-two/#include <stdlib.h> #include &…

环境配置:Ubuntu18.04 ROS Melodic安装

前言 不同版本的Ubuntu与ROS存在对应关系。 ROS作为目前最受欢迎的机器人操作系统&#xff0c;其核心代码采用C编写&#xff0c;并以BSD许可发布。ROS起源于2007年&#xff0c;是由斯坦福大学与机器人技术公司Willow Garage合作的Switchyard项目。2012年&#xff0c;ROS团队从…

力扣面试题 05.03. 翻转数位(前、后缀和)

Problem: 面试题 05.03. 翻转数位 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.将十进制数转换为二进制数&#xff08;每次按位与1求与&#xff0c;并且右移&#xff09;&#xff1b; 2.依次求取二进制数中每一位的前缀1的数量和&#xff0c;和后缀1的数量和…

计算机项目SpringBoot项目 办公小程序开发

从零构建后端项目、利用UNI-APP创建移动端项目 实现注册与登陆、人脸考勤签到、实现系统通知模块 实现会议管理功能、完成在线视频会议功能、 发布Emos在线办公系统 项目分享&#xff1a; SpringBoot项目 办公小程序开发https://pan.baidu.com/s/1sYPLOAMtaopJCFHAWDa2xQ?…

极狐GitLab 使用阿里云作为 OmniAuth 身份验证 provider

使用阿里云作为 OmniAuth 身份验证 provider 您可以启用阿里云 OAuth 2.0 OmniAuth provider并使用您的阿里云账户登录极狐GitLab。 创建阿里云应用 登录阿里云平台&#xff0c;在上面创建一个应用。阿里云会生成一个 client ID and secret key 供您使用。 登录到阿里云平台…