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

  • 基于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)));
for(int k /*diagonal*/ = 0; k < image.rows + image.cols - 1 /*cond = height + width - 1*/; ++k) {
    if((k & 1) == 0) {
        // even
        for(int i = std::min(k, image.rows - 1); i >= 0; --i) {
            int j = k - i;
            if(i < image.rows && j < image.cols) {
                internal(image, j, i);
            }
        }
    } else {
        // odd
        for(int j = std::min(k, image.cols - 1); j >= 0; --j) {
            int i = k - j;
            if(i < image.rows && j < image.cols) {
                internal(image, j, i);
            }
        }
    }
}

这个函数将生成一个包含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
        diagonalStrategy();
    }

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

    // 双向斜向扫描
    // Bidirectional oblique scanning
    void diagonalStrategy() {
        cv::Mat image;
        cv::resize(mat, image, cv::Size(static_cast<int>(width * resolution), static_cast<int>(height * resolution)));
        for(int k /*diagonal*/ = 0; k < image.rows + image.cols - 1 /*cond = height + width - 1*/; ++k) {
            if((k & 1) == 0) {
                // even
                for(int i = std::min(k, image.rows - 1); i >= 0; --i) {
                    int j = k - i;
                    if(i < image.rows && j < image.cols) {
                        internal(image, j, i);
                    }
                }
            } else {
                // odd
                for(int j = std::min(k, image.cols - 1); j >= 0; --j) {
                    int i = k - j;
                    if(i < image.rows && j < image.cols) {
                        internal(image, j, i);
                    }
                }
            }
        }
    }

    // // 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/373583.html

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

相关文章

003集—三调数据库添加三大类字段——arcgis

在国土管理日常统计工作中经常需要用到三大类数据&#xff08;农用地、建设用地、未利用地&#xff09;&#xff0c;而三调数据库中无三大类字段&#xff0c;因此需要手工录入三大类字段&#xff0c;并根据二级地类代码录入相关三大类名称。本代码可一键录入海量三大类名称统计…

Springboot+vue的企业财务管理系统(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的企业财务管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的企业财务管理系统&#xff0c;采用M&#xff08;model&…

想提升测试效率?Ant来帮你

Ant的安装和配置 Apache Ant,是一款将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发 设置ant环境变量: a) ant包名称为:apache-ant-1.9.6-bin.zip b) 解压到一个目录下面:unzip apache-ant-1.9.6-bin.zip c) 比如放到:/op…

Netty核心原理与基础实战(二)——详解Bootstrap

接上篇&#xff1a;Netty核心原理与基础实战&#xff08;一&#xff09; 1 Bootstrap基础概念 Bootstrap类是Netty提供的一个便利的工厂类&#xff0c;可以通过它来完成Netty的客户端或服务端的Netty组件的组装&#xff0c;以及Netty程序的初始化和启动执行。Netty的官方解释是…

Google MobileDiffusion: 移动端设备上的快速文字到图片生成技术

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

腌腊食品污废水处理需要哪些工艺设备

腌腊食品行业在加工过程中产生的污废水处理是一个关键的环境保护问题。为了确保生产过程中的环境友好和可持续性发展&#xff0c;腌腊食品污废水处理需要采用一系列的工艺设备。下面将介绍一些常用的工艺设备&#xff0c;以供参考。 首先&#xff0c;腌腊食品污废水处理中常用的…

缩略图保持加密(TPE)论文

文献: R.Zhao,Y.Zhang,Y.Nan,W.Wen,X.Chai,andR. Lan, “Primitively visually meaningful image encryption: A new paradigm,” Inf. Sci. (Ny), Vol. 613, pp. 628–48, 2022. DOI: 10.1016/j.ins.2022.08.027. (1) 第1行:原始图像 第2行:加密图像 加密的目标: 原始…

如何使用 Bard 中的画图功能

Bard 是 Google AI 推出的大型语言模型&#xff0c;它不仅可以生成文本、翻译语言&#xff0c;还可以根据您的描述生成图像。这篇文章将介绍如何使用 Bard 中的画图功能。 步骤 1&#xff1a;打开 Bard 首先&#xff0c;您需要打开 Bard。您可以访问 bard.google.com: https:…

【保姆级教程|YOLOv8改进】【5】精度与速度双提升,使用FasterNet替换主干网络

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

jquery生成多个滑块,并对每个滑块做处理

基础滑块可以参考上一篇 eval(newThree).map((item, index) > { <div id"${uniqueId}" data-value"${item.text}" class"slider2"></div>$(document).ready(function () {for (let i 0; i < sliders.length; i)…

服务器和CDN推荐

简介 陆云Roovps是一家成立于2021年的主机服务商&#xff0c;主要业务是销售美国服务器、香港服务器及国外湖北十堰高防服务器&#xff0c;还有相关CDN产品。&#xff08; 地址&#xff1a;roovps&#xff09; 一、相关产品

C++多线程:this_thread 命名空间

std::this_thread 是 C 标准库中提供的一个命名空间&#xff0c;它包含了与当前线程相关的功能。这个命名空间提供了许多与线程操作相关的工具&#xff0c;使得在多线程环境中更容易进行编程。 源码类似于如下&#xff1a; namespace std{namespace this_thread{//...........…

详解Redis哨兵模式下,主节点掉线而重新选取主节点的流程

⭐最核心的结论&#xff1a;所谓选举的过程不是直接选出新的主节点&#xff0c;而是先在哨兵节点中选出 leader &#xff0c;再由 leader 负责后续主节点的指定。 假定当前环境&#xff1a; 三个哨兵(sentenal1, sentenal2, sentenal3)一个主节点(redis-master)两个从节点(red…

GPT-1, GPT-2, GPT-3, GPT-3.5, GPT-4论文内容解读

目录 1 ChatGPT概述1.1 what is chatGPT1.2 How does ChatGPT work1.3 The applications of ChatGPT1.3 The limitations of ChatGPT 2 算法原理2.1 GPT-12.1.1 Unsupervised pre-training2.1.2 Supervised fine-tuning2.1.3 语料2.1.4 分析 2.2 GPT-22.3 GPT-32.4 InstructGPT…

【Kotlin】Kotlin环境搭建

1 前言 Kotlin 是一种现代但已经成熟的编程语言&#xff0c;由 JetBrains 公司于 2011 年设计和开发&#xff0c;并在 2012 年开源&#xff0c;在 2016 年发布 v1.0 版本。在 2017 年&#xff0c;Google 宣布 Kotlin 正式成为 Android 开发语言&#xff0c;这进一步推动了 Kotl…

Google Chrome Close AutoUpdate

DOMException: play() failed because the user didn‘t interact with the document first.-CSDN博客 html5 audio video-CSDN博客 Google Chrome Close AutoUpdate 关闭google浏览器自动更新 1&#xff1a;检查是否已安装google浏览器&#xff0c;并卸载&#xff1a; 2&…

二叉搜索树题目:二叉搜索树的最近公共祖先

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉搜索树的最近公共祖先 出处&#xff1a;235. 二叉搜索树的最近公共祖先 难度 3 级 题目描述 要求 给定一个…

openstack(T版)公有云--Dashboard服务

公有云上OpenStack Train最小化安装_openstack最小化部署-CSDN博客 我的opensatck(T)是参考上面链接去部署完成的&#xff0c;在部署完Dashboard服务后&#xff0c;将要用浏览器访问的时候出现了404 500 Internal Server Error 等各种各样的问题&#xff0c;以下是我排查问题…

【Linux驱动】块设备驱动(二)—— 块设备读写(使用请求队列)

块设备的操作函数并没有类似于字符驱动中的read 和write函数&#xff0c;要实现读写操作&#xff0c;只能在请求处理函数中实现。这就分为两种&#xff0c;是否要使用请求队列&#xff0c;请求队列的主要作用是管理和调度IO请求。在以下情况中&#xff0c;一般需要用到请求队队…

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

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