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

news2024/11/18 3:45:40
  • 基于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的螺旋扫描方法。这为激光雕刻提供了一种更加灵活、精细的图案生成方式。通过理解和应用上述代码片段,你可以根据自己的需求进一步调整和优化,实现更复杂的图案生成。激光雕刻的应用不仅仅局限于艺术品制作,还可以在教育和创客领域发挥巨大的创造力。希望这篇博客能够为你在激光雕刻领域的探索提供一些有用的指导。

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

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

相关文章

智慧树答案怎么查找? #知识分享#学习方法#学习方法

大学开学&#xff0c;就意味着又回到了被线性代数、大学物理等测验题折磨的状态了……网站无法手动输入题干公式&#xff0c;初高中用过的搜题软件又都搜不到&#xff0c;想找个答案解析仿佛在大海捞针&#xff01;不过不用怕&#xff0c;今天小林就把从大学攒到毕业工作都在使…

ChatGPT高效提问—prompt基础

ChatGPT高效提问—prompt基础 ​ 设计一个好的prompt对于获取理想的生成结果至关重要。通过选择合适的关键词、提供明确的上下文、设置特定的约束条件&#xff0c;可以引导模型生成符合预期的回复。例如&#xff0c;在对话中&#xff0c;可以使用明确的问题或陈述引导模型生成…

Java笔记 --- 六、IO流

六、IO流 概述 分类 纯文本文件&#xff1a;Windows自带的记事本打开能读懂的 eg&#xff1a;txt文件&#xff0c;md文件&#xff0c;xml文件&#xff0c;lrc文件 IO流体系 字节流 FileOutputStream 操作本地文件的字节输出流&#xff0c;可以把程序中的数据写到本地文件中…

上海亚商投顾:沪指再现长下影线 多只高息股创历史新高

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 今日A股三大指数再次大幅波动&#xff0c;盘中再创阶段新低&#xff0c;收盘涨跌不一&#xff0c;黄白分时线分…

2024PMP考试新考纲-近年PMP真题练一练和很详细解析(3)

今天华研荟继续为您分享和解析PMP真题&#xff0c;一方面让大家感受实际的PMP考试和出题形式&#xff0c;另一方面是通过较详细的解题思路和知识讲解帮助大家最后一个多月有效备考&#xff0c;一次性3A通过2024年PMP考试。 2024年PMP考试新考纲-近年真题随机练一练 (注&#x…

【C/C++ 18】多态

目录 一、虚函数重写 二、虚函数重写的两个例外 三、C11 override和final 四、抽象类 五、虚函数表 六、单继承中的虚函数表 七、多继承中的虚函数表 一、虚函数重写 多态是在不同继承关系的类对象&#xff0c;去调用同一函数&#xff0c;产生了不同的行为。 构成多态…

『运维备忘录』之 Netstat 命令详解

运维人员不仅要熟悉操作系统、服务器、网络等只是&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

常用工具类-Collections

常用工具类-Collections 排序操作查找操作填充操作判断集合是否有交集不可变集合 java.util.Collections类是一个工具类&#xff0c;它包含了一些静态方法&#xff0c;用于操作集合&#xff08;如列表和映射&#xff09;。这个类主要用于创建不可修改的集合、填充集合、替换元素…

2024.3.28-29日ICVS-AI智能汽车产业峰会(杭州)

本次安策将携手泰雷兹一起&#xff0c;参展ICVS2024第四届AI智能汽车产业峰会(杭州)&#xff0c;2024年3月28日-29日&#xff0c;欢迎新老朋友参加和莅临27号展台交流。 随着自动驾驶汽车政策密集落地。从我国四部门联合发布《关于开展智能网联汽车准入和上路通行试点工作的通知…

sql非查询知识点(增删改-crud没有r)

1.建库 create database database_name 2.使用该数据库 use database_name 3.建表 3.1普通建表 create table if not exists actor(actor_id smallint(5) not null primary key comment "主键id",first_name varchar(45) not null comment "名字",last…

嵌入式软件的设计模式与方法

思想有多远&#xff0c;我们就能走多远 4、状态与工作流类设计模式 4.1 状态与事件 行为随条件变化而改变&#xff0c;这里状态切换的模式也称为状态机。有限状态机 (Finite State Machine&#xff0c;FSM) 是由3 个主要元素组成的有向图: 状态、转换和动作。 状态是系统或者…

2024年上海市安全员B证证模拟考试题库及上海市安全员B证理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年上海市安全员B证证模拟考试题库及上海市安全员B证理论考试试题是由安全生产模拟考试一点通提供&#xff0c;上海市安全员B证证模拟考试题库是根据上海市安全员B证最新版教材&#xff0c;上海市安全员B证大纲整理…

Kafka零拷贝技术与传统数据复制次数比较

读Kafka技术书遇到困惑: "对比传统的数据复制和“零拷贝技术”这两种方案。假设有10个消费者&#xff0c;传统复制方式的数据复制次数是41040次&#xff0c;而“零拷贝技术”只需110 11次&#xff08;一次表示从磁盘复制到页面缓存&#xff0c;另外10次表示10个消费者各自…

怎么用postman调用webservice(反推SoapUI)

<soapenv:Envelope xmlns:soapenv“http://schemas.xmlsoap.org/soap/envelope/” xmlns:lis“LisDataTrasen”> soapenv:Header/ soapenv:Body lis:Test lis:test111111111</lis:test> </lis:Test> </soapenv:Body> </soapenv:Envelope> Conten…

基于SSM的便民自行车管理系统的开发与实现(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的便民自行车管理系统的开发与实现&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0…

企业飞书应用机器人,使用python发送图文信息到群

企业飞书应用的自动化&#xff0c;需要创建企业应用&#xff0c;应用开通机器人能力&#xff0c;并获取机器人所需的app_id与app_secret&#xff08;这一部分大家可以在飞书的控制台获取&#xff1a;https://open.feishu.cn/api-explorer/&#xff09; 文章目录 步骤1&#xff…

C# OpenVINO 图片旋转角度检测

目录 效果 项目 代码 下载 效果 项目 代码 using OpenCvSharp; using Sdcb.OpenVINO; using System; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Te…

FCIS 2023:洞悉网络安全新态势,引领创新防护未来

随着网络技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;成为全球共同关注的焦点。在这样的背景下&#xff0c;FCIS 2023网络安全创新大会应运而生&#xff0c;旨在汇聚业界精英&#xff0c;共同探讨网络安全领域的最新动态、创新技术和解决方案。 本文将从大会的…

C语言贪吃蛇详解

个人简介&#xff1a;双非大二学生 个人博客&#xff1a;Monodye 今日鸡汤&#xff1a;人生就像一盒巧克力&#xff0c;你永远不知道下一块是什么味的 C语言基础刷题&#xff1a;牛客网在线编程_语法篇_基础语法 (nowcoder.com) 一.贪吃蛇游戏背景 贪吃蛇是久负盛名的游戏&…

内存对齐的规则

一、为什么要内存对齐 简单来说&#xff0c;就是方便计算机去读写数据。 对齐的地址一般都是 n&#xff08;n 2、4、8&#xff09;的倍数。 (1). 1 个字节的变量&#xff0c;例如 char 类型的变量&#xff0c;放在任意地址的位置上&#xff1b; (2). 2 个字节的变量&#xff0…