C#+ OpenCvSharp 实现精准玉米粒计数

501 阅读6分钟

前言

本项目利用 C# 编程语言结合 OpenCvSharp 库,实现一个智能玉米粒计数操作。

OpenCvSharp 是一个功能强大的开源计算机视觉库,提供了丰富的图像处理算法和工具,非常适合用于农业领域的物体识别任务。

目标是创建一个易于使用、高度准确且性能优越的解决方案,帮助农民和农业企业实现从收获到仓储全过程中的玉米粒数量统计自动化。

效果

核心技术与流程

图像预处理:首先将彩色图像转换为灰度图,并进行二值化操作,以突出目标物体(玉米粒)。接着使用形态学操作(如膨胀)来增强物体边界,确保后续步骤能更好地分离各个个体。

距离变换:通过计算每个非零像素点到最近背景像素的距离,生成距离变换图像。这一步骤有助于后续的轮廓提取,特别是当物体之间存在粘连情况时。

形态学处理:对距离变换后的图像再次进行形态学处理(如开运算),去除噪声干扰,进一步优化物体轮廓的质量。

轮廓提取与标注:利用 Cv2.FindContours 函数找到所有独立的物体轮廓,并根据其包围矩形确定中心点位置。然后,在原始图像上绘制圆形标记并添加编号文本,直观地展示每个物体的位置信息。

步骤

1、二值化操作

2、腐蚀

3、距离变换

4、形态学处理

5、找到种子的轮廓区域

OpenCV中,函数distanceTransform()用于计算图像中每一个非零点像素与其最近的零点像素之间的距离, 输出的是保存每一个非零点与最近零点的距离信息,图像上越亮的点,代表了离零点的距离越远。

用途:可以根据距离变换的这个性质,经过简单的运算,用于细化字符的轮廓和查找物体质心(中心)。

距离变换的处理图像通常都是二值图像,而二值图像其实就是把图像分为两部分,即背景和物体两部分,物体通常又称为前景目标。通常我们把前景目标的灰度值设为255(即白色),背景的灰度值设为0(即黑色)。

所以定义中的非零像素点即为前景目标,零像素点即为背景。

所以图像中前景目标中的像素点距离背景越远,那么距离就越大,如果我们用这个距离值替换像素值,那么新生成的图像中这个点越亮。

项目

623cc835360763e0989b1216497dc7e7_640_wx_fmt=png&from=appmsg.png

## 代码
using OpenCvSharp;  
using System;  
using System.Drawing;  
using System.Text;  
using System.Windows.Forms;  

namespace OpenCvSharp_Demo  
{  
    public partial class frmMain : Form  
    {  
        public frmMain()  
        {  
            InitializeComponent();  
        }  

        string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";  
        string image_path = "";  

        DateTime dt1 = DateTime.Now;  
        DateTime dt2 = DateTime.Now;  

        Mat image;  
        Mat result_image;  

        StringBuilder sb = new StringBuilder();  

        private void button1_Click(object sender, EventArgs e)  
        {  
            OpenFileDialog ofd = new OpenFileDialog();  
            ofd.Filter = fileFilter;  
            if (ofd.ShowDialog() != DialogResult.OK) return;  

            pictureBox1.Image = null;  
            pictureBox2.Image = null;  
            textBox1.Text = "";  

            image_path = ofd.FileName;  
            pictureBox1.Image = new Bitmap(image_path);  
            image = new Mat(image_path);  
        }  

        private void Form1_Load(object sender, EventArgs e)  
        {  
            //test  
            image_path = "test_img/1.jpg";  
            image = new Mat(image_path);  
            pictureBox1.Image = new Bitmap(image_path);  
        }  

        private void button2_Click(object sender, EventArgs e)  
        {  
            if (image_path == "")  
            {  
                return;  
            }  
            textBox1.Text = "检测中,请稍等……";  
            pictureBox2.Image = null;  
            Application.DoEvents();  

            result_image = image.Clone();  

            //二值化操作  
            Mat grayimg = new Mat();  
            Cv2.CvtColor(image, grayimg, ColorConversionCodes.BGR2GRAY);  
            Mat BinaryImg = new Mat();  
            Cv2.Threshold(grayimg, BinaryImg, 240255, ThresholdTypes.Binary);  
            //Cv2.ImShow("二值化", BinaryImg);  

            //腐蚀  
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(1515));  
            Mat morhImage = new Mat();  
            Cv2.Dilate(BinaryImg, morhImage, kernel, null2);  
            //Cv2.ImShow("morphology", morhImage);  

            //距离变换:用于二值化图像中的每一个非零点距自己最近的零点的距离,距离变换图像上越亮的点,代表了这一点距离零点的距离越远  
            Mat dist = new Mat();  
            Cv2.BitwiseNot(morhImage, morhImage);  
            /*  
            OpenCV中,函数distanceTransform()用于计算图像中每一个非零点像素与其最近的零点像素之间的距离,  
            输出的是保存每一个非零点与最近零点的距离信息,图像上越亮的点,代表了离零点的距离越远。  
            用途:  
            可以根据距离变换的这个性质,经过简单的运算,用于细化字符的轮廓和查找物体质心(中心)。  
            */  
            /*  
            距离变换的处理图像通常都是二值图像,而二值图像其实就是把图像分为两部分,即背景和物体两部分,物体通常又称为前景目标。  
            通常我们把前景目标的灰度值设为255(即白色),背景的灰度值设为0(即黑色)。  
            所以定义中的非零像素点即为前景目标,零像素点即为背景。  
            所以图像中前景目标中的像素点距离背景越远,那么距离就越大,如果我们用这个距离值替换像素值,那么新生成的图像中这个点越亮。  
            */  
            //User:用户自定义  
            //L1:  曼哈顿距离  
            //L2:  欧式距离  
            //C:   棋盘距离  
            Cv2.DistanceTransform(morhImage, dist, DistanceTypes.L1, DistanceTransformMasks.Mask3);  
            Cv2.Normalize(dist, dist, 01.0, NormTypes.MinMax);   //范围在0~1之间  
            //Cv2.ImShow("distance", dist);  

            //形态学处理  
            Mat MorphImg = new Mat();  
            dist.ConvertTo(MorphImg, MatType.CV_8U);  
            Cv2.Threshold(MorphImg, MorphImg, 0.99255, ThresholdTypes.Binary);  //上图像素值在0~1之间  
            kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(73), new OpenCvSharp.Point(-1-1));  
            Cv2.MorphologyEx(MorphImg, MorphImg, MorphTypes.Open, kernel);  //开操作  
            //Cv2.ImShow("t-distance", MorphImg);  

            //找到种子的轮廓区域  
            OpenCvSharp.Point[][] contours;  
            HierarchyIndex[] hierarchly;  
            Cv2.FindContours(MorphImg, out contours, out hierarchly, RetrievalModes.External, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(00));  
            Mat markers = Mat.Zeros(image.Size(), MatType.CV_8UC3);  
            int x, y, w, h;  
            Rect rect;  
            for (int i = 0; i < contours.Length; i++)  
            {  
                // Cv2.DrawContours(markers, contours, i, Scalar.RandomColor(), 2, LineTypes.Link8, hierarchly);  
                rect = Cv2.BoundingRect(contours[i]);  
                x = rect.X;  
                y = rect.Y;  
                w = rect.Width;  
                h = rect.Height;  
                Cv2.Circle(result_image, x + w / 2, y + h / 220new Scalar(00255), -1);  
                if (i >= 9)  
                {  
                    Cv2.PutText(result_image, (i + 1).ToString(), new OpenCvSharp.Point(x + w / 2 - 18, y + h / 2 + 8), HersheyFonts.HersheySimplex, 0.8new Scalar(02550), 2);  
                }  
                else  
                {  
                    Cv2.PutText(result_image, (i + 1).ToString(), new OpenCvSharp.Point(x + w / 2 - 8, y + h / 2 + 8), HersheyFonts.HersheySimplex, 0.8new Scalar(02550), 2);  
                }  
            }  

            textBox1.Text = "number of corns: " + contours.Length;  
            pictureBox2.Image = new Bitmap(result_image.ToMemoryStream());  

        }  

        private void pictureBox2_DoubleClick(object sender, EventArgs e)  
        {  
            //Common.ShowNormalImg(pictureBox2.Image);  
        }  

        private void pictureBox1_DoubleClick(object sender, EventArgs e)  
        {  
            //Common.ShowNormalImg(pictureBox1.Image);  
        }  
    }  
}  

总结

基于 C# 和 OpenCvSharp 的物体计数与标注系统成功实现了对玉米粒等小型物体的高效、精准计数和标注。相比传统的手动方式,该系统具有以下显著优势:

高精度与稳定性:借助先进的图像处理算法和技术手段,保证了在复杂背景下依然保持较高的计数准确性。

自动化与智能化:整个过程无需人工干预,从图像加载到最终结果输出一气呵成,大幅提升了工作效率。

用户友好界面:提供了清晰直观的结果可视化功能,使得非专业人员也能轻松理解和使用。

灵活性与扩展性:不仅可以应用于玉米粒计数,还可以针对其他类型的物体进行适当调整,适应更广泛的应用场景。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:꧁执笔小白꧂

出处:cnblogs.com/qq2806933146xiaobai/p/18295839

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!