C# EMGU 3.4.1学习笔记(四)综合示例:漫水填充

本示例是《OpenCV3编程入门》中6.5.4的示例程序的C# + EMGU 3.4.1版,和C++程序相比,有以下几点不同:

1. 使用winform实现,主要控件有:MenuStrip(用于显示程序操作说明),ImageBox(用于显示图片),RichTextBox(用于记录并显示程序操作过程),TrackBar(用于变更负差最大值和正差最大值)。

2. 使用KeyDown事件,实现键盘按键的实时监控,该事件绑定到Form1窗体上。注意:Form1窗体的KeyPreview属性值需要设置为True,原因如下:

问题:当新建一个窗体时,添加KeyDown事件后,会正常处理,但是当添加有控件,比如ButtonTextBox时,则不会再触发窗体的KeyDown事件,也不会调用KeyDown事件的处理程序。

原因:由于窗体中添加了控件,于是焦点就在其中一个控件上,如果我们要处理的事件,窗体和获取焦点的控件都同时拥有,系统就会将键盘的操作键值直接传递给这个获取焦点的控件,就会出现上面的问题。

解决方案:此时需要将窗体的KeyPreview属性设置为true,将系统传入的键值先传递给窗体,再传递给控件。MSDN关于KeyPreview的说明:如果窗体将接收所有键事件,则为 true;如果窗体上当前选定控件接收键事件,则为 false,默认为 false

3. 使用MouseDown事件,实现鼠标点击的实时监控,该事件绑定到ImageBox控件上,当鼠标点击ImageBox中图片的某一点时,将以该点为seed point,进行漫水填充。

4. EMGU的FloodFill函数原型如下,与OpenCv相比,EMGU中FloodFill函数只有一种形式,而OpenCv中有两种,分别为带掩膜mask和不带掩膜mask。

public static int FloodFill(IInputOutputArray src, IInputOutputArray mask, Point seedPoint, MCvScalar newVal, out Rectangle rect, MCvScalar loDiff, MCvScalar upDiff, Connectivity connectivity = Connectivity.FourConnected, FloodFillType flags = FloodFillType.Default);

5. FloodFill函数中rect参数类型为Rectangle,可通过Rectangle ccomp = new Rectangle();语句定义其实参,调用函数时,该实参前需要使用out关键字进行修饰,表示参数通过引用来传递。

6. 通过Canny边缘检测算法,生成掩膜mask,实现真正意义上的mask功能(原书示例中虽然有此部分代码,但并未实现mask功能的可视化比较确认,只是函数调用形式上的差异而已)。注意:Canny算法生成的OutputArray,其上下左右需要各扩展一行/列,以匹配mask的大小(rows和cols需要比原图各多2)。

通过以下两条语句生成掩膜:

//边缘检测后的输出将作为FloodFill函数中的掩膜,黑色区域可以被填充,白色区域则保留原来的颜色
CvInvoke.Canny(g_grayImage, g_cannyImage,10,240);
//边缘检测后的输出上下左右各扩展一行/列(否则会抛异常),以匹配g_maskImage的大小
CvInvoke.CopyMakeBorder(g_cannyImage, g_cannyImage, 1, 1, 1, 1, BorderType.Constant);

并将生成的g_cannyImage拷贝给g_maskImage:

g_cannyImage.CopyTo(g_maskImage);

程序使用的原始图片如下:

程序代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;

namespace floodfill
{
    public partial class Form1 : Form
    {
        //定义原始图、目标图、灰度图、掩膜图
        Mat g_srcImage = new Mat();
        Mat g_dstImage = new Mat();
        Mat g_grayImage = new Mat();
        Mat g_maskImage = new Mat();
        Mat g_cannyImage = new Mat();
        int g_nFillMode = 1; //漫水填充的模式
        int g_nLowDifference = 20, g_nUpDifference = 20; //负差最大值、正差最大值
        Connectivity g_nConnectivity = Connectivity.FourConnected; //表示floodFill函数的连通值
        bool g_bIsColor = true; //是否为彩色的标识符布尔值
        bool g_bUseMask = false; //是否显示掩膜窗口的布尔值
        //int g_nNewMaskVal = 255; //新的重新绘制的像素值(OpenCv中该值用于设置flags的中间8位,而EMGU中无此设置方式)
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //载入源图
            g_srcImage = CvInvoke.Imread("home.jpg", ImreadModes.Color);

            if (g_srcImage.IsEmpty)
                MessageBox.Show("读取图片错误!", "Warning");

            g_srcImage.CopyTo(g_dstImage); //复制源图到目标图
            CvInvoke.CvtColor(g_srcImage, g_grayImage, ColorConversion.Bgr2Gray); //三通道bgr图转换到灰度图
            g_maskImage.Create(g_srcImage.Rows + 2, g_srcImage.Cols + 2, DepthType.Cv8U, 1); //利用源图的尺寸来初始化掩膜mask
            //边缘检测后的输出将作为FloodFill函数中的掩膜,黑色区域可以被填充,白色区域则保留原来的颜色
            CvInvoke.Canny(g_grayImage, g_cannyImage,10,240);
            //边缘检测后的输出上下左右各扩展一行/列(否则会抛异常),以匹配g_maskImage的大小
            CvInvoke.CopyMakeBorder(g_cannyImage, g_cannyImage, 1, 1, 1, 1, BorderType.Constant);


            //显示TrackBar的初始值
            lowDiffValueLable.Text = lowDiffTrackBar.Value.ToString();
            upDiffValueLable.Text = upDiffTrackBar.Value.ToString();

            //先显示源图
            imageBox1.Image = g_bIsColor ? g_dstImage : g_grayImage;
        }

        //鼠标单击事件
        private void imageBox1_MouseDown(object sender, MouseEventArgs e)
        {
            Point seed = new Point(e.X, e.Y); //获取当前坐标,作为种子点

            //当为空范围的漫水填充时,此值设为0,否则设为全局的g_nLowDifference
            int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;
            //当为空范围的漫水填充时,此值设为0,否则设为全局的g_nUpDifference
            int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;

            //标识符flags,其值为0时,对应设置为default
            FloodFillType flags = g_nFillMode == 1 ? FloodFillType.FixedRange : FloodFillType.Default;

            //随机生成bgr值,使用Guid类是为了得到“真”随机数
            int b = new Random(Guid.NewGuid().GetHashCode()).Next(0, 255); //随机返回一个0~255之间的值
            int g = new Random(Guid.NewGuid().GetHashCode()).Next(0, 255); //随机返回一个0~255之间的值
            int r = new Random(Guid.NewGuid().GetHashCode()).Next(0, 255); //随机返回一个0~255之间的值

            Rectangle ccomp = new Rectangle(); //定义重绘区域的最小边界矩形区域(conneted components缩写为ccomp)

            //在重绘区域的像素的新值,若是彩色模式,取MCvScalar(b, g, r)
            //若是灰度模式,取MCvScalar(r * 0.299 + g * 0.587 + b * 0.114)
            MCvScalar newVal = g_bIsColor ? new MCvScalar(b, g, r) : new MCvScalar(r * 0.299 + g * 0.587 + b * 0.114);

            Mat dst = g_bIsColor ? g_dstImage : g_grayImage; //dst赋值

            int area = 0; //统计被重绘像素的数量
            if (g_bUseMask)
            {
                //threshold函数作用为将g_maskImage二值化,其值大于1值,取值为128,反之取值为0
                //CvInvoke.Threshold(g_maskImage, g_maskImage, 1, 128, ThresholdType.Binary);
                //注意:Rectangle变量ccomp前需要使用out关键字,表示参数通过引用来传递
                g_cannyImage.CopyTo(g_maskImage); //边缘检测后的输出将作为FloodFill函数中的掩膜,黑色区域可以被填充,白色区域则保留原来的颜色
                area = CvInvoke.FloodFill(dst, g_maskImage, seed, newVal, out ccomp, new MCvScalar(LowDifference, LowDifference, LowDifference),
                    new MCvScalar(UpDifference, UpDifference, UpDifference), g_nConnectivity, flags);
                //CvInvoke.Imshow("mask", g_maskImage);
            }
            else
            {
                //将mask所有元素设置为0(此时相当于没有mask)
                g_maskImage = Mat.Zeros(g_maskImage.Rows, g_maskImage.Cols, g_maskImage.Depth, g_maskImage.NumberOfChannels);
                area = CvInvoke.FloodFill(dst, g_maskImage, seed, newVal, out ccomp, new MCvScalar(LowDifference, LowDifference, LowDifference),
                    new MCvScalar(UpDifference, UpDifference, UpDifference), g_nConnectivity, flags);
            }
            imageBox1.Image = dst;
            ShowMessage(area.ToString() + "个像素被重绘\n");
        }

        //获取键盘按键
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            //判断ESC是否按下,若按下则退出程序
            if (e.KeyCode == Keys.Escape)
            {
                MessageBox.Show("程序退出......");
                Application.Exit();
            }

            //根据按键的不同,执行各种操作
            switch (e.KeyCode)
            {
                //如果键盘按键"1"被按下,效果图在灰度图和彩色图之间切换
                case Keys.D1:
                    if (g_bIsColor) //若原来为彩图,则转为灰度图,并将掩膜mask所有元素设置为0
                    {
                        ShowMessage("按键“1”被按下,切换彩色/灰度模式,当前操作将【彩色模式】切换为【灰度模式】\n");
                        CvInvoke.CvtColor(g_srcImage, g_grayImage, ColorConversion.Bgr2Gray);
                        g_maskImage = Mat.Zeros(g_maskImage.Rows, g_maskImage.Cols, g_maskImage.Depth, g_maskImage.NumberOfChannels);
                        g_bIsColor = false;
                    }
                    else //若原来为灰度图,则将原来的彩图srcImage再次复制给dstImage,并将掩膜所有元素设置为0
                    {
                        ShowMessage("按键“1”被按下,切换彩色/灰度模式,当前操作将【灰度模式】切换为【彩色模式】\n");
                        g_srcImage.CopyTo(g_dstImage);
                        g_maskImage = Mat.Zeros(g_maskImage.Rows, g_maskImage.Cols, g_maskImage.Depth, g_maskImage.NumberOfChannels);
                        g_bIsColor = true;
                    }
                    break;
                //如果键盘按键"2"被按下,显示/隐藏掩膜窗口
                case Keys.D2:
                    if (g_bUseMask)
                    {
                        CvInvoke.DestroyWindow("mask");
                        g_bUseMask = false;
                    }
                    else
                    {
                        CvInvoke.NamedWindow("mask", 0);
                        g_cannyImage.CopyTo(g_maskImage); //边缘检测后的输出将作为FloodFill函数中的掩膜
                        CvInvoke.Imshow("mask", g_maskImage);
                        g_bUseMask = true;
                    }
                    break;
                //如果键盘按键"3"被按下,则恢复为未进行漫水填充的图像
                case Keys.D3:
                    ShowMessage("按键“3”被按下,恢复原始图像\n");
                    g_srcImage.CopyTo(g_dstImage);
                    CvInvoke.CvtColor(g_dstImage, g_grayImage, ColorConversion.Bgr2Gray);
                    g_maskImage = Mat.Zeros(g_maskImage.Rows, g_maskImage.Cols, g_maskImage.Depth, g_maskImage.NumberOfChannels);
                    imageBox1.Image = g_bIsColor ? g_dstImage : g_grayImage;
                    break;
                //如果键盘按键“4”被按下,使用空范围的漫水填充
                case Keys.D4:
                    ShowMessage("按键“4”被按下,使用空范围的漫水填充\n");
                    g_nFillMode = 0;
                    break;
                //如果键盘按键“5”被按下,使用渐变、固定范围的漫水填充
                case Keys.D5:
                    ShowMessage("按键“5”被按下,使用渐变、固定范围的漫水填充\n");
                    g_nFillMode = 1;
                    break;
                //如果键盘按键“6”被按下,使用渐变、浮动范围的漫水填充
                case Keys.D6:
                    ShowMessage("按键“6”被按下,使用渐变、浮动范围的漫水填充\n");
                    g_nFillMode = 2;
                    break;
                //如果键盘按键“7”被按下,使用4位的连接模式
                case Keys.D7:
                    ShowMessage("按键“7”被按下,使用4位的连接模式\n");
                    g_nConnectivity = Connectivity.FourConnected;
                    break;
                //如果键盘按键“8”被按下,使用8位的连接模式
                case Keys.D8:
                    ShowMessage("按键“8”被按下,使用8位的连接模式\n");
                    g_nConnectivity = Connectivity.EightConnected;
                    break;
            }
        }

        private void ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Form form2 = new Form(); //创建新的窗口form2
            form2.Size = new Size(400, 220); //设置新窗口的尺寸
            form2.Show(); //显示新窗口
            form2.Text = "按键操作说明"; //窗口标题栏
            RichTextBox rtb = new RichTextBox(); //新建RichTextBox
            rtb.Dock = DockStyle.Fill; //设置其Dock方式为Fill
            rtb.AppendText("鼠标点击图中区域 - 进行漫水填充操作\n");
            rtb.AppendText("键盘按键【ESC】 - 退出程序\n");
            rtb.AppendText("键盘按键【1】 - 切换彩色图/灰色图模式\n");
            rtb.AppendText("键盘按键【2】 - 显示/隐藏掩膜窗口\n");
            rtb.AppendText("键盘按键【3】 - 恢复原始图像\n");
            rtb.AppendText("键盘按键【4】 - 使用空范围的漫水填充\n");
            rtb.AppendText("键盘按键【5】 - 使用渐变、固定范围的漫水填充\n");
            rtb.AppendText("键盘按键【6】 - 使用渐变、浮动范围的漫水填充\n");
            rtb.AppendText("键盘按键【7】 - 操作标识符的低八位使用4位的连接模式\n");
            rtb.AppendText("键盘按键【8】 - 操作标识符的低八位使用8位的连接模式");
            form2.Controls.Add(rtb); //将rtb添加到窗口中
        }

        private void lowDiffTrackBar_Scroll(object sender, EventArgs e)
        {
            g_nLowDifference = lowDiffTrackBar.Value; //负差最大值随TrackBar的值而变
            lowDiffValueLable.Text = lowDiffTrackBar.Value.ToString();
        }

        private void upDiffTrackBar_Scroll(object sender, EventArgs e)
        {
            g_nUpDifference = upDiffTrackBar.Value; //正差最大值随TrackBar的值而变
            upDiffValueLable.Text = upDiffTrackBar.Value.ToString();
        }

        //自定义函数,用于记录操作过程
        void ShowMessage(string s)
        {
            OperationLogRichTxtBox.AppendText(s);
        }
    }
}

运行截图:

如果需要完整的程序资源,可到如下链接下载:

https://download.csdn.net/download/flymoon87/10672197

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值