PCB电路板Bmp图像输出及处理

0 项目需求:

解析PCB电路板的gerber文件,将PCB电路板图像显示并绘制出PCB电路板BMP图像用于喷墨打印,针对打印需求,需要输出1bit图像2bit图像1bit&2bit反色图8bit墨量直观图预览图xml文件,同时,为控制喷头喷印的墨量,需要对图像进行处理,比如2bit灰度变化抽点削减线宽,为防止喷墨后有墨流出,需要对色块的边缘进行一圈筑坝。解析和显示部分由同事完成,本人主要完成PCB电路板BMP图像处理部分,即上述黑体字所描述的功能。

1 绘制8bit图像

业务上并没有输出8bit图像的需求,但由于直接对1bit图像进行绘制看上去可行,但是1bit图像只有黑白两种颜色,0表示黑色,1表示白色,由于2bit图像是由1bit图像转换而来,1bit图像无法进行灰度变化,所以我们无法对2bit进行灰度变化,所以这种方法不可取。8bit图像用1字节表示一个像素,具有256种灰度值,如果2bit需要进行灰度变化,那就先对8bit图像进行灰度变化,然后转化后的2bit也就进行了相应的灰度变化,因此这里先对8bit图像进行绘制,将8bit转化为2bit和1bit,同时也可以用于后面的2bit图像灰度变化。

绘制直线、圆弧、闪绘、自定义区域:

内外削:

核心思想:通过腐蚀和膨胀达到内外削的效果

  • 主要使用的halcon算子:
    erosion_rectangle1
    dilation_rectangle1
    

抽点:

抽点也是一种控制墨量的方式,通过对白油块的部分白色素转化为黑色素,从而减少喷墨量,由于需要避免喷墨不均匀的问题,因此需要均匀减少各个区域的喷墨量,即均匀地将白色素转化为黑色素。算法步骤如下:

  • 通过Halcon读取图像,并进行阈值分割,将白色前景和黑色背景分割开来。
  • 利用Halcon查找连通区域,并按面积筛选连通区域(太小的白油块不进行抽点),然后统计符合需求的,需要进行抽点的连通区域数量。
  • 多线程处理连通区域,每个线程处理一部分连通区域
  • 接下来就是针对每个连通区域进行处理,由于我们需要均匀地将白色素转化为黑色素,所以需要获取该连通区域的最小外接矩形,然后从左上角到右下角以对角线方向改变像素颜色,改变像素颜色之前要注意判断该像素是否在联通区域内。
  • 由于需要实现25%、50%、75%不同程度的抽点,因此需要设置步长,对于25%程度的抽点,每4条对角线,沿对角线方向进行一次抽点,对于50%程度的抽点,每2条对角线,沿对角线方向进行一次抽点,对于75%程度的抽点,4条对角线,对3条对角线进行抽点。

筑坝: 本质上是一种边缘提取,用较浅色的灰度值在区域周围画一圈轮廓,从而减少周围区域墨量,防止周围墨量溢出。算法主要步骤:

  • 使用halcon算子读取图像,并进行阈值分割,提取白色区域
  • 对于阈值分割后的区域进行连通区域分析
  • 对分析得到的连通区域以绘制边缘的方式进行绘制,同时指定较浅色的灰度值,实现边缘提取,即筑坝功能。

2 获取8位深图像底层2进制数据

逐行读取有效数据。首先计算8比特图像初始偏移量,即文件头+信息头+调色板的大小,即14+50+256*4 = 1078字节,定位到初始偏移量处,读取一行的有效字节数,存入事先申请的空间中,并将指向该行数据的指针存入vector容器中,然后初始偏移量+第一行实际字节数(有效字节数+填充字节),得到新的偏移量,并定位到新的偏移量处,然后读取第二行的有效字节数,存入事先申请的空间中,并将指向行数据的指针存入vector容器中,如此循环,不断读取每一行的有效字节数,并按顺序将指向行数据的指针存入vector容器中,直到每一行均读取并存储完毕。

3 图像位深度转换

3.1 8bit图像转化为2bit/1bit

8bit图像转化为2bit和1bit思路一模一样,接下来以8bit转化为2bit为例,主要算法步骤如下:

  • 创建并设置2位深图像文件头和信息头的基本信息,并定义调色板
//创建并设置BMP文件头和信息头
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;

fileHeader.bfType = 0x4D42;
fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (pixelWidth * 2 + 31) / 32 * 4 * pixelHeight;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(colors2);

infoHeader.biSize = sizeof(BITMAPINFOHEADER);
infoHeader.biWidth = pixelWidth;
infoHeader.biHeight = pixelHeight;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 2;
infoHeader.biCompression = BI_RGB;
infoHeader.biSizeImage = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;

//定义2位深图像调色板
RGBQUAD colors2[4];
colors2[0].rgbBlue = 0;      // 黑色
colors2[0].rgbGreen = 0;
colors2[0].rgbRed = 0;
colors2[0].rgbReserved = 0;

colors2[1].rgbBlue = 96;     // 暗灰色
colors2[1].rgbGreen = 96;
colors2[1].rgbRed = 96;
colors2[1].rgbReserved = 0;

colors2[2].rgbBlue = 48;    // 淡灰色
colors2[2].rgbGreen = 48;
colors2[2].rgbRed = 48;
colors2[2].rgbReserved = 0;

colors2[3].rgbBlue = 255;    // 白色
colors2[3].rgbGreen = 255;
colors2[3].rgbRed = 255;
colors2[3].rgbReserved = 0;
  • 将文件头、信息头、调色板写入文件
ofstream image2bit = ofstream(finalOutputPath2.toLocal8Bit(), ios::binary);

if (!image2bit.is_open())
{
	qDebug() << "generate2bit: file of 2bit open failed";
	return false;
}
image2bit.write(reinterpret_cast<const char*>(&fileHeader), sizeof(BITMAPFILEHEADER));
image2bit.write(reinterpret_cast<const char*>(&infoHeader), sizeof(BITMAPINFOHEADER));
image2bit.write(reinterpret_cast<const char*>(&colors2), sizeof(colors2));
  • 创建线程,并行处理多行数据,将8位深图像数据转化为2位深图像数据
  • 具体转化思路为,将8位深图像的有效字节数(去除内存对齐的部分)转化为2bit,比如00000000转化为00,11111111转化为11。如果2位深图像存满一字节,则算出其所对应的实际值写入一片内存中。8位深图像一行的有效数据部分转化完后,开始对2位深图像进行内存对齐处理,先对未填满字节进行比特填充,最后再填充字节。
  • 每个线程处理一部分行数据,每个线程对应一个vector容器,用于存储该线程所处理的所有行数据,每处理好一行数据,就将指向该行数据的指针存入vector中,即vetocr中每一个元素对应2位深图像完整一行的数据,该线程处理完毕后,将该线程的索引号和vector容器存到map集合中。
  • 所有线程处理完毕后,遍历map,依次获取每一个线程的vector容器,依次将每个vector中的每一行数据写入2位深图像文件中。
  • bmp图像文件格式超详解

3.2 2bit图像转化为8bit

  • 前面几步和8转2一样,都是创建并设置文件头、信息头基本信息,定义调色板,将文件头、信息头和调色板写入图像文件中。
  • 多线程转化,每个线程分别处理相应的行。
  • 具体转化思路为,将2位深图像的有效字节数(去除内存对齐的部分)和最后一字节的有效比特数转化为8bit,比如00转化为00000000,11转化为11111111。通常情况下每一字节实际要转化的比特数就是8位,我们依次计算每两位的像素值,根据值的大小转化为8bit图像的像素值,但是当达到一行最后一个有效字节的时候需要注意,2bit图像最后一个有效字节可能只有部分比特是表示像素,其余比特可能是填充的部分,所以对于最后一个有效字节要注意判断有效比特数,不要将填充的部分进行转化了。当最后一个有效字节的有效比特部分全部转化完,这个时候就要开始考虑内存对齐,对于8比特图像而言,不存在需要填充字节的情况,只需要将这一行的总字节数减去有效字节数,就是我们要填充的字节。内存对齐完成后,将该行数据所在空间的指针存储在该线程所对应的vector容器中。当该线程处理完毕其所对应的所有行后,将该线程索引号和相应的vector容器存入map集合。
  • 所有线程处理完毕后,遍历map,依次获取每一个线程的vector容器,依次将每个vector中的每一行数据写入8位深图像文件中。

4 输出8bit墨量直观图

将2bit图像通过位深度转化算法转化为8位深度彩色图,具体算法步骤如下:

  • 创建并设置文件头、信息头基本信息,定义调色板 ,并将文件头、信息头、调色板写入图像文件
  • 创建线程,并行处理多行数据,将2位深图像数据转化为8位深图像数据。
  • 具体转化步骤为:将2位深图像的每一行有效字节数每两位转化为相应的1字节,当处理完最后一个有效字节后,需要进行内存对齐处理,8位深图像直接填充字节即可。
  • 每个线程处理一部分行数据,每个线程对应一个vector容器,vetocr中每一个元素对应8位深图像完整一行的数据,将处理好的8位深行数据存入到该vector容器中,该线程处理完毕后,将该线程的索引号和vector容器存到map集合中。
  • 所有线程处理完毕后,依次将每个vector中的每一行数据写入8位深图像文件中。

5 输出预览图

核心思想: 对1bit图像进行缩放

  • 主要使用的halcon算子:

    GetImageSize
    ZoomImageSize
    

6 输出xml文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子非鱼Swx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值