前言

这周三晚上我们上了一节关于叶面积的测定方法的实验课,在课上我想出了一种测定思路,我的想法是通过固定比例尺拍照转换为灰度图来遍历图像像素判断叶的像素,然后再通过比例尺算法计算叶的面积,从程序的角度来看是可行的,只要给定输入固定比例尺的图像,就可以批量输出结果。

本来我是没打算写这个程序的,不过昨晚学习学累了,就突然想到这个事情,我就想这弄一下吧,然后就弄出来了,程序基于Win平台,使用的是C#,Winform开发技术(主要是快)。至于WPF的美化版我懒得弄了。

使用效果

我早上吃完饭随手摘的银杏的叶子,然后放在一张A4纸上拍照,单击【选择图片】导入程序即可。

image-20220422085545367

本方法精度取决于比例尺,所以如果想要大规模应用,需要固定相机和拍摄底的距离即可。

实现逻辑

  1. 先判断是否导入了选定图片,导入成功则显示预览图,失败则提醒,二次验证是否规定了比例尺,两者具备则可以正常计算面积
  2. 计算面积前需要将图像转换为灰度图,然后根据算法统计叶子所占的像素,然后根据比例尺算出每个像素的面积,然后加和返回结果。

程序源码

namespace LeafArea
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //预览图片
        private void button2_Click(object sender, EventArgs e)
        {
            string filePath = OpenFile();
            if(filePath == "无")
            {
                retrunMes.ForeColor = Color.Red;
                retrunMes.Text = "错误,未指定图片";
            }
            else
            {
                Image img = Image.FromFile(filePath);
                pictureBox1.Image = img;
                retrunMes.ForeColor = Color.Green;
                retrunMes.Text = "加载图片成功";
            }
        }

        //计算叶面积
        private void button1_Click(object sender, EventArgs e)
        {
            if(pictureBox1.Image == null)
            {
                MessageBox.Show("请选择图片");
            }
            else
            {
                if(pixel.Text == "" || cm.Text == "")
                {
                    MessageBox.Show("请填写比例设定");
                }
                else
                {
                    Image bwImg = BlackWhiteChange(pictureBox1);     //转化为灰度图
                    pictureBox1.Image = bwImg;
                    retrunMes.Text = "计算叶面积中...";
                    retrunMes.Text = "叶面积为" + CalculateArea(LeafPixel(bwImg)) + "平方厘米";
                }
            }
        }

        //选择图片路径
        public string OpenFile()
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Multiselect = false;//禁止选择多个文件
            dialog.Title = "请选择文件夹";
            dialog.Filter = "所有文件(*.*)|*.*";
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                return dialog.FileName;
            }
            else
            {
                return "无";
            }
        }

        //转化灰度图
        public Image BlackWhiteChange(PictureBox pic)
        {
            int varH = pic.Image.Height;    //获取图像高度
            int varW = pic.Image.Width;     //获取图像宽度
            Bitmap bitmap = new Bitmap(varW,varH);  //更具宽高创建灰度图对象
            Bitmap rgbmap = (Bitmap)pic.Image;  //RGB图像
            for(int i = 0; i < varW; i++)
            {
                for (int j = 0; j < varH; j++)
                {
                    Color nowColor = rgbmap.GetPixel(i, j);     //获取当前颜色像素
                    int r, g, b, value = 0;
                    r = nowColor.R;
                    g = nowColor.G;
                    b = nowColor.B;
                    value = (r + g + b) / 3;      //加权平均生成灰度图
                    bitmap.SetPixel(i, j, Color.FromArgb(value, value, value));     //生成灰度图

                }
            }

            return bitmap;      //返回生成的灰度图
        }

        //计算叶面积像素
        public int LeafPixel(Image img)
        {
            int num =0;
            Bitmap bitmap = (Bitmap)img;
            Console.WriteLine("像素宽度{0},长度{0}", img.Width,img.Height);
            for (int i = 0; i < img.Width; i++)
            {
                for (int j = 0; j < img.Height; j++)
                {
                    Color nowColor = bitmap.GetPixel(i, j);     //获取当前颜色像素
                    int r;
                    r = nowColor.R;
                    if (r < 100)
                    {
                        num++;      //计算叶面积占的像素
                    }
                }
            }
            Console.WriteLine("所占像素为{0}", num);
            return num;     //返回所占像素值
            
        }

        //根据像素和比例算面积
        public double CalculateArea(int num)
        {
            double result = 0;
            double real = double.Parse(cm.Text)/double.Parse(pixel.Text);  //计算一个像素是多少厘米
            Console.WriteLine("单像素面积为{0}", real);
            double realone = real * real;    //一个像素的面积
            result = realone * num;     //总像素面积
            Console.WriteLine("总面积为{0}", result);
            return result;
        }
    }
}

End

程序不难,如果后续改进的话可以将遍历图片部分的算法更换其他灵活的算法,计算的速度应该可以翻几倍,因为.NET默认的遍历像素的算法比较耗时,如果采用锁定内存或者非安全代码来使用引用指针话速度会更快。

关于叶面积测定仪,我个人也想到了这种方法,不过这种方法是从单片机的角度来考虑的,相比较程序来说便携和快速的结果是最大的优势,但是对于大规模的计算来说,程序会相对较好,给定输入,返回输出。

关于纸质量测定法,我个人认为如果通过复印机的方式直接复印叶子的样子出来,然后剪切叶子的形状称量重量会相对精确一些,比较人手画的相对误差大。前提是排除了复印机墨的质量。

Q.E.D.


赤脚踩在明媚的沙滩上,我看见了你闪耀的双眼,柔软的头发,我便心有所属