摘要
本章节将把读取的影像进行处理如灰阶、分别撷取R, G, B通道、针对影像通道减少颜色操作,最后把显示的影像做保存,本篇依照上一篇[VS] C# - [01] EmguCV 影像读取的程式继续操作。
实作
介面设置
介面的部份把各转换的方式分成不同的按钮进行使用。
图一 主程式介面
Enum
在转换影像的时候,很多按钮处理的方式大同小异,因此我们会把重複性高的程式做一个函数,使用该函数只要丢进影像与转换类型即可获得预期输出,使用字串的方式传递会容易使错误,所以採用 enum
(列举)的方式建立我们所需要的类型。
public enum EColorType{ Blue = 0, Green, Red, Gray}
灰阶与影像通道处理
我们接取的影像型态为Image<Bgr, Byte>
,经过转换灰阶或者是单独撷取通道的时候皆为一个通道,因此在输出的影像时候则为Image<Gray, Byte>
的型态。
虽说接下来在按钮按下的时候会先检测影像是否存在,但为了安全起见,还是会从函式里面做一个检测影像是否为 null ,若为 null 透过 throw new
的方式建立我们所需要掷出的例外(Exception)资讯。
我们输入的影像型态为 Bgr
的格式,因此我们使用 a_bitmap[index] 当中的 index 位置做为通道如 [0] = B, [1] = G, [2] = R,因此我们只要将通道撷取出来即可完成我们要提取的通道。
把 Bgr
的格式转换成 Gray
的方式也只需要透过Convert<TColor, TDepth>
方式进行转换。
参考资料:Image<TColor, TDepth>.Convert Method
private Image<Gray, Byte> ChanneConvert1(Image<Bgr, Byte> a_bitmap, EColorType a_type){ if (a_bitmap == null) { throw new NullReferenceException("影像不得为空。"); } Image<Gray, Byte> convertImage; // 颜色转换 switch (a_type) { case EColorType.Blue: convertImage = a_bitmap[0].Clone(); break; case EColorType.Green: convertImage = a_bitmap[1].Clone(); break; case EColorType.Red: convertImage = a_bitmap[2].Clone(); break; case EColorType.Gray: convertImage = a_bitmap.Convert<Gray, Byte>().Clone(); break; default: throw new NullReferenceException("无其他类型。"); } return convertImage;}
若不想要影像进来时因为掷出例外使程式中断,可以採用回传空影像(预设影像)的方式并带着错误的资讯显示给使用者,下面的方法则是将影像直接做一个 new Image<Gray, Byte>
并在每个像素预设为 Gray(255) ,也就是预设白影像。
convertImage = new Image<Gray, Byte>(m_image.Width, m_image.Height, new Gray(255));MessageBox.Show("影像显示错误。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
保留颜色
这边保留颜色指的是使用原图pixelA=(233, 50, 30)
与遮罩(0, 255, 255)
进行相减保留蓝色的像素,最后会变成pixelA=(233, 0, 0)
,当然你也可以用图片作为参考遮罩,也许该称为滤镜比较适当。
Image<TColor, TDepth>.Sub Method
private Image<Bgr, Byte> ChannelReduce1(Image<Bgr, Byte> a_bitmap, EColorType a_type){ if (a_bitmap == null) { throw new NullReferenceException("影像不得为空。"); } Image<Bgr, Byte> convertImage; // 颜色转换 switch (a_type) { case EColorType.Blue: convertImage = a_bitmap.Sub(new Bgr(0, 255, 255)).Clone(); break; case EColorType.Green: convertImage = a_bitmap.Sub(new Bgr(255, 0, 255)).Clone(); break; case EColorType.Red: convertImage = a_bitmap.Sub(new Bgr(255, 255, 0)).Clone(); break; default: throw new NullReferenceException("无其他类型。"); } return convertImage;}
保存影像
Image<TColor, TDepth>
格式众多,为了方便这边直接使用 Bitmap
的方式进行影像保存,只要使用 Save()
的函式并带上路径与档案名称就可以完成保存,为了让开发者了解影像是否保存成功,使用 bool
的方式来进行响应,因路径或者是档名可能会有错误,因此採用 try catch
的方式掌控。
private bool SaveImage(Bitmap a_image, string a_path, string a_fileName){ try { a_image.Save($"{a_path}/{a_fileName}"); return true; } catch (Exception e) { MessageBox.Show(e.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; }}
元件
因大部分影像处理的功能皆为重複操作,因此只用按钮第一个(蓝)色作为显示,只需要改变函式中的 EColorType.{名称}
就可以对应各功能了。
灰阶影像:
private void button2GrayImage_Click(object sender, EventArgs e){ if (m_image == null) return; pictureBoxDisplayImage.Image = (Bitmap)ChanneConvert1(m_image, EColorType.Gray).ToBitmap().Clone(); pictureBoxDisplayImage.Invalidate();}蓝:private void buttonChannelBlue_Click(object sender, EventArgs e){ if (m_image == null) return; pictureBoxDisplayImage.Image = (Bitmap)ChanneConvert1(m_image, EColorType.Blue).ToBitmap().Clone(); pictureBoxDisplayImage.Invalidate();}保留蓝:private void buttonKeepBlue_Click(object sender, EventArgs e){ if (m_image == null) return; pictureBoxDisplayImage.Image = (Bitmap)ChannelReduce1(m_image, EColorType.Blue).ToBitmap().Clone(); pictureBoxDisplayImage.Invalidate();}影像保存:private void buttonSaveImage_Click(object sender, EventArgs e){ // "./" 代表当前路径 // 后面名称需附上副档名 if (SaveImage((Bitmap)pictureBoxDisplayImage.Image, "./", "save_image.bmp") == true) { MessageBox.Show("保存影像成功。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("保存影像失败。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); }}
结果
图二 灰阶影像
图三 蓝通道
图四 透过遮罩显示的保留蓝
图五 保存影像结果
结论
EmguCV在影像的操作是非常的完整,在这边你已经学会了如何把读取影像进来的影像做简易的处理,如整张影像灰阶、提取通道、透过Sub遮罩减掉通道这些操作,下一篇我们将会学到 Panel 与 Dock 进行元件编排,以及使用 EmguCV 所提供的 ImageBox 元件取代 PictureBox。
Github: Link.