基于OpenCV灰度图像转GCode的螺旋扫描实现

  • 基于OpenCV灰度图像转GCode的螺旋扫描实现
    • 引言
    • 激光雕刻简介
    • OpenCV简介
    • 实现步骤
      • 1.导入必要的库
      • 2. 读取灰度图像
      • 3. 图像预处理
      • 4. 生成GCode
      • 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打印机等设备。以下是简化版的螺旋扫描生成GCode的代码片段:

cv::Mat image;
cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));
int top = 0, bottom = image.rows - 1, left = 0, right = image.cols - 1;
while(top <= bottom && left <= right) {
    for(int i = left; i <= right; ++i) {
        internal(image, i, top);
    }
    ++top;
    for(int i = top; i <= bottom; ++i) {
        internal(image, right, i);
    }
    --right;
    if(top <= bottom) {
        for(int i = right; i >= left; --i) {
            internal(image, i, bottom);
        }
        --bottom;
    }
    if(left <= right) {
        for(int i = bottom; i >= top; --i) {
            internal(image, left, i);
        }
        ++left;
    }
}

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

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 Power
        Engraving,  // 雕刻 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 here
        spiralStrategy();
    }

    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));
        }
    }

    // 螺旋扫描 从外到里的方向
    // Spiral scan from outside to inside direction
    void spiralStrategy() {
        cv::Mat image;
        cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));

        int top = 0, bottom = image.rows - 1, left = 0, right = image.cols - 1;
        while(top <= bottom && left <= right) {
            for(int i = left; i <= right; ++i) {
                internal(image, i, top);
            }
            ++top;

            for(int i = top; i <= bottom; ++i) {
                internal(image, right, i);
            }
            --right;

            if(top <= bottom) {
                for(int i = right; i >= left; --i) {
                    internal(image, i, bottom);
                }
                --bottom;
            }

            if(left <= right) {
                for(int i = bottom; i >= top; --i) {
                    internal(image, left, i);
                }
                ++left;
            }
        }
    }

    // Define additional strategy functions here

private:
    cv::Mat mat;                                 // 灰度图像
    double width {0};                            // 工作范围 x 轴
    double height {0};                           // 工作范围 y 轴
    double resolution {0};                       // 精度 lin/mm
    ScanMode scanMode {ScanMode::Bidirection};   // 默认双向
    LaserMode laserMode {LaserMode::Engraving};  // 默认雕刻模式
    std::optional<int> airPump;                  // 自定义指令 气泵 用于吹走加工产生的灰尘 范围 [0,1000]
    // add more custom cmd
    std::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/mm
    handle.setInputImage(mat).setOutputTragetSize(50,50,2).builder().exportGCode(R"(~\ImageToGCode\output\001.nc)");
}

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5-3、S曲线生成器【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍步进电机S曲线生成器的计算以及使用 一.计算原理 根据上一节内容&#xff0c;已经计算了一条任意S曲线的函数。在步进电机S曲线加减速的控制中&#xff0c;需要的S曲线如图1所示&#xff0c;横…

React 实现表单组件

表单是html的基础元素&#xff0c;接下来我会用React实现一个表单组件。支持包括输入状态管理&#xff0c;表单验证&#xff0c;错误信息展示&#xff0c;表单提交&#xff0c;动态表单元素等功能。 数据状态 表单元素的输入状态管理&#xff0c;可以基于react state 实现。 …

09_树莓派_树莓派外设板_GPIO_按键的中断与消抖

目录 1.树莓派外设集成板总体介绍 2.第一部分 按键矩阵 GPIO_按键与中断 3.实现效果 1.树莓派外设集成板总体介绍 1&#xff09;前言&#xff1a;这是一块为了验证树莓派【兼容树莓派多个型号】的40pins的外设接口的外接板&#xff0c;告别复杂的面包板外设搭建。【欢迎各位…

【Iceberg学习四】Evolution和Maintenance在Iceberg的实现

Evolution Iceberg 支持就底表演化。您可以像 SQL 一样演化表结构——即使是嵌套结构——或者当数据量变化时改变分区布局。Iceberg 不需要像重写表数据或迁移到新表这样耗费资源的操作。 例如&#xff0c;Hive 表的分区布局无法更改&#xff0c;因此从每日分区布局变更到每小…

Node.js+Express+Mysql服务添加环境变量

1、使用dotenv插件 1&#xff09;安装插件&#xff1a;npm install dotenv-cli --save-dev 2&#xff09;在项目根目录下添加对应的 .env 配置文件&#xff1b; // .env配置文件内容 MODEdevelopment, BASE_URLhttp://127.0.0.1:80813) 在启动命令中设置对应的加载文件&#…

RabbitMQ-1.介绍与安装

介绍与安装 1.RabbitMQ1.0.技术选型1.1.安装1.2.收发消息1.2.1.交换机1.2.2.队列1.2.3.绑定关系1.2.4.发送消息 1.2.数据隔离1.2.1.用户管理1.2.3.virtual host 1.RabbitMQ 1.0.技术选型 消息Broker&#xff0c;目前常见的实现方案就是消息队列&#xff08;MessageQueue&…

新零售的升维体验,摸索华为云GaussDB如何实现数据赋能

新零售商业模式 商业模式通常是由客户价值、企业资源和能力、盈利方式三个方面构成。其最主要的用途是为实现客户价值最大化。 商业模式通过把能使企业运行的内外各要素整合起来&#xff0c;从而形成一个完整的、高效率的、具有独特核心竞争力的运行系统&#xff0c;并通过最…

springboot与Elasticsearch版本兼容对比

首先 大家在下载 Elasticsearch 时 最好先弄清楚版本 因为 如果 Spring Boot 版本 不兼容 Elasticsearch 那就是到头一场空了 Elasticsearch 版本 6.x 可以兼容 Spring Boot 2.x Elasticsearch 版本 7.x 可以兼容 Spring Boot 2.x 3.x 4x Elasticsearch 版本 7.x 以及 8.x 可以…

Golang-Map有序输出——使用orderedmap库实现

前言 工作中遇到一个问题&#xff1a;需要导出一个MySQL表格&#xff0c;表格内容由sql查询得来。但现在发现&#xff0c;所导出的表格中&#xff0c;各列的顺序不确定。多次导出&#xff0c; 每一次的序列顺序也是不定的。 因此确定是后端&#xff0c;Map使用相关导致的问题。…

分布式文件存储系统minio

参考Linux搭建免费开源对象存储 wget https://dl.minio.io/server/minio/release/linux-amd64/minio yum install -y wget yum install -y wget wget https://dl.minio.io/server/minio/release/linux-amd64/minio chmod x minio sudo mv minio /usr/local/bin/ minio --vers…

黑马头条 Kafka

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

玩家笔记:幻兽帕鲁搭建服务器开服教程

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

1.0 Zookeeper 分布式配置服务教程

ZooKeeper 是 Apache 软件基金会的一个软件项目&#xff0c;它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper 的架构通过冗余服务实现高可用性。 Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高…

实践:微服务版本升级步骤以及maven仓库相关概念

进行微服务开发的时候&#xff0c;上层服务依赖于下层的服务的api&#xff0c;比如适配属于上层服务&#xff0c;用户属于下层服务。 例子: 上层服务 <!--订单管理微服务api依赖--> <dependency><groupId>com.jn.server</groupId><artifactId>…

docker部署docker运维工具

简介 主要功能:管理容器,管理镜像,管理容器网络 安装 拉取镜像 docker pull joinsunsoft/docker.ui:1.0.1 启动容器 docker run -d --name docker.ui --restart always -v /var/run/docker.sock:/var/run/docker.sock -p 10039:8999 joinsunsoft/docker.ui:1.0.1 使用 打…

【ArcGIS微课1000例】0101:删除冗余节点或折点

文章目录 一、实验描述二、实验数据三、实验过程1. 手动删除2. 简化线工具四、注意事项一、实验描述 矢量数据获取通常来源于手动或者ArcScan自动采集,其基本存储方式就是记录每个要素的点坐标,如点要素就是一个坐标、线要素由多个点要素连接形成。当某段线要素被过多的节点…

多模态对比语言图像预训练CLIP:打破语言与视觉的界限,具备零样本能力

多模态对比语言图像预训练CLIP:打破语言与视觉的界限,具备零样本能力。 一种基于多模态(图像、文本)对比训练的神经网络。它可以在给定图像的情况下,使用自然语言来预测最相关的文本片段,而无需为特定任务进行优化。CLIP的设计类似于GPT-2和GPT-3,具备出色的零射击能力…

C++中的构造函数

一、类中的6个默认成员函数 如果一个类中什么都没有&#xff0c;那么这个类就是一个空类。但空类中并不是真的什么都没有&#xff0c;编译器会自动生成6个默认成员函数 分别是构造函数、析构函数、拷贝构造函数、赋值重载函数、普通对象取地址函数、const对象取地址函数 本篇…

全链游戏的未来趋势与Bridge Champ的创新之路

为了充分探索全链游戏的特点和趋势&#xff0c;以及Bridge Champ如何作为一个创新案例融入这一发展脉络&#xff0c;我们需要深入了解这两者之间的互动和相互影响。全链游戏&#xff0c;或完全基于区块链的游戏&#xff0c;代表了游戏行业的一个重要转型&#xff0c;它们利用区…

Android meminfo 查看方法及解析

目录 Android 上查看memory 信息的方法 内存限制的信息 手动释放缓存 例 adb shell dumpsys meminfo pid 解析 adb shell dumpsys meminfo 汇总信息说明 Total RAM Free RAM ION Used RAM Lost RAM ZRAM /proc/meminfo 参考文档 Android 上查看memory 信息的方法 …