将BMP图像转换为绘图机的指令集?

3
我有一台类似于这个的绘图仪:PloterXY device. 我需要实现将24位BMP转换为此绘图仪的一组指令。在该绘图仪中,我可以更改16种常见颜色。我面临的第一个问题是颜色减少。我面临的第二个问题是如何将像素转换为一组绘图指令。
作为绘画工具,会使用油漆刷。这意味着绘图仪绘制的线条不会太细,它们会相对较短。
请建议用于解决此图像数据转换问题的算法?
一些初步结果: Flower 1 - colors reduction. Flower 2 - colors reduction. Flower 3 - colors reduction.

2
谷歌肯定会帮助你。对于位图转换到绘图仪,这将高度取决于您拥有的图像。您将以不同的方式将字符矢量化与花卉矢量化。 - Piglet
2
你不需要使用抖动来降低颜色...矢量化取决于你想要什么...只是轮廓还是完整填充...同时它也极大地取决于图像内容,真实照片与卡通素描不同...如果你不受位图的限制,那么你也可以直接使用2D矢量格式,如svg、wmf、emf、dwg等...添加图像示例。 - Spektre
我将使用真实的照片图像。我的想法是比标准算法更好地减少颜色。例如,GIMP有非常好的颜色减少模块,但它是通用的颜色减少。我需要进行图像矢量化,并且我想知道是否有方法可以实现与我的任务相关的更好的减少颜色。 - Todor Balabanov
作为一个初学者,你应该解释一下有哪些可用的绘图指令... - fjardon
1
我猜更改颜色所需的时间/成本相对于移动绘图机头的时间也是确定如何绘制的决定因素,因此您需要识别绘图机及其用户手册。 - Mark Setchell
显示剩余2条评论
1个回答

6

抖动

今天我有时间来做这件事,所以这里是结果。你没有提供你的绘图机颜色板,所以我从你的结果图像中提取了它,但你可以使用任何颜色板。抖动背后的思想很简单:我们的感知将区域上的颜色集成起来,而不是看单个像素,因此必须使用一些颜色差异积累器,该积累器记录已渲染和应该被渲染的内容之间的颜色差异,并将其添加到下一个像素…

这样,区域内大致具有相同的颜色,但实际使用的只有离散数量的颜色。如何更新此信息的形式会影响结果,导致抖动分支出许多方法。最简单直接的方法是:

  1. 将颜色积累器重置为零
  2. 处理所有像素
    1. 对于每个像素,将其颜色添加到积累器中
    2. 找到颜色板中最接近匹配的结果
    3. 渲染选择的颜色
    4. 从积累器中减去选择的颜色

以下是你的输入图像(我把它们放在一起):

input

这里是你的源图像的结果:

result

左上角的颜色方块只是我使用的调色板(从你的图像中提取出来的)。

以下是我用C++编写的代码:

picture pic0,pic1,pic2;
    // pic0 - source img
    // pic1 - source pal
    // pic2 - output img
int x,y,i,j,d,d0,e;
int r,g,b,r0,g0,b0;
color c;
List<color> pal;
// resize output to source image size clear with black
pic2=pic0; pic2.clear(0);
// create distinct colors pal[] list from palette image
for (y=0;y<pic1.ys;y++)
 for (x=0;x<pic1.xs;x++)
    {
    c=pic1.p[y][x];
    for (i=0;i<pal.num;i++) if (pal[i].dd==c.dd) { i=-1; break; }
    if (i>=0) pal.add(c);
    }
// dithering
r0=0; g0=0; b0=0;   // no leftovers
for (y=0;y<pic0.ys;y++)
 for (x=0;x<pic0.xs;x++)
    {
    // get source pixel color
    c=pic0.p[y][x];
    // add to leftovers
    r0+=WORD(c.db[picture::_r]);
    g0+=WORD(c.db[picture::_g]);
    b0+=WORD(c.db[picture::_b]);
    // find closest color from pal[]
    for (i=0,j=-1;i<pal.num;i++)
        {
        c=pal[i];
        r=WORD(c.db[picture::_r]);
        g=WORD(c.db[picture::_g]);
        b=WORD(c.db[picture::_b]);
        e=(r-r0); e*=e; d =e;
        e=(g-g0); e*=e; d+=e;
        e=(b-b0); e*=e; d+=e;
        if ((j<0)||(d0>d)) { d0=d; j=i; }
        }
    // get selected palette color
    c=pal[j];
    // sub from leftovers
    r0-=WORD(c.db[picture::_r]);
    g0-=WORD(c.db[picture::_g]);
    b0-=WORD(c.db[picture::_b]);
    // copy to destination image
    pic2.p[y][x]=c;
    }
// render found palette pal[] (visual check/debug)
x=0; y=0; r=16; g=pic2.xs/r; if (g>pal.num) g=pal.num;
for (y=0;y<r;y++)
 for (i=0;i<g;i++)
  for (c=pal[i],x=0;x<r;x++)
   pic2.p[y][x+(i*r)]=c;

这里的picture是我的图像类,以下是它的一些成员:

  • xs,ys 分辨率
  • color p[ys][xs] 直接像素访问(32位像素格式,每个通道8位)
  • clear(DWORD c) 用颜色c填充图像

color只是DWORD ddBYTE db [4]union,用于简单的通道访问。

List<>是我的模板(动态数组/列表)

  • List<int> aint a[]相同。
  • add(b)将b添加到列表末尾
  • num是列表中项目的数量

现在为了避免过多的点(为了您的绘图仪的使用寿命),您可以使用不同的线条样式等,但这需要大量的试验/错误...例如,您可以计算某个区域中使用特定颜色的次数,并从该比率使用不同的填充模式(基于线条)。您需要在图像质量、渲染速度/耐久性之间进行选择...

如果没有更多关于您的绘图仪能力(速度、工具更换方法、颜色组合行为)的信息,很难决定最佳形成控制流的方法。我打赌您手动更改颜色,因此您将一次渲染每种颜色。因此,提取所有使用第一个工具颜色的像素,将相邻像素合并为线条/曲线并进行渲染...然后移动到下一个工具颜色...


@MarkSetchell 如果您喜欢这篇文章,那么也可以看看可扩展抖动。我在那里上传了新的动画GIF。 - Spektre
Spektre,非常感谢,给出了令人惊叹的好答案。我故意没有提供太多初始信息,是希望实现类似头脑风暴的会议,以收集新思路。这里可以看到我解决问题的更多细节:https://github.com/TodorBalabanov/EllipsesImageApproximator。 - Todor Balabanov
1
不错的算法。我喜欢它。它有一些缺陷(比如产生波浪),但它真的很简单,允许相当快的执行。 - Raffzahn
1
@Raffzahn :) ... 我还记得在Z80的时代,编码和翻译都要在纸上完成,然后poke(将信息写入内存地址)到RAM中并随机usr(跳转到指定地址执行程序):) ... 以及数小时寻找其中的错误。 - Spektre
1
@TodorBalabanov 看一下类似的 QA 机器人臂画笔生成算法 - Spektre
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接