C# 绘制内外边轮廓的方法

本文介绍了如何使用C#的GDI+库来识别PNG图片的内外轮廓。通过遍历像素点信息,作者实现了内轮廓的提取,并对比了使用Sobel算子计算轮廓的方法。主要关注的是提取图片的外边缘,代码中提供了按像素位置判断的外轮廓获取算法,同时展示了两种不同的轮廓提取效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

最近正在研究 GDI 绘图,需要识别 PNG 图片 取得相应内外轮廓和边框线;
用 winfrom 写了一个 Demo ,效果如下,有需要的朋友可以看看,原理比较简单,主要是判断像素点位置信息;
网上搜了一下有很多轮廓算法,其实我是想要提取图片的外边缘(左下角的效果),然后demo里也顺便对比了一下sobel算子方式识别轮廓的方法(PS: sobel并不是我想要的);

 C#实现代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Demo{
public partial class MyDemo : Form
    {
        public MyDemo()
        {
            InitializeComponent();
        }

        //原始图片
        protected Image SourceImage = Image.FromFile(@"your png path");

        protected override void OnLoad(EventArgs e)
        {
            foreach (var c in Controls)
            {
                if (c.GetType().Equals(typeof(PictureBox)))
                {
                    ((PictureBox)c).SizeMode = PictureBoxSizeMode.StretchImage; //使图片在控件内完全显示
                }
            }

            Task t = new Task(() =>
            {
                //显示原图
                this.pictureBox1.Image = SourceImage;
                GetImageinside();//显示内轮廓
                GetImageOutsideBySobel();//显示算子计算外轮廓
                GetImageOutside();//外轮廓
            });
            t.Start();

            base.OnLoad(e);
        }


        /// <summary>
        /// 获得图片内轮廓
        /// </summary>
        protected void GetImageinside()
        {

            //Graphics g = this.pictureBox2.CreateGraphics();//从picturebox2创建gdi对象
            int w = SourceImage.Width, h = SourceImage.Height;//获得原图宽高
            Bitmap bmp = new Bitmap(SourceImage);//将原图转为位图
            Bitmap output = new Bitmap(SourceImage.Width, SourceImage.Height);//用于输出内轮廓的图片

            //遍历图片像素轴信息
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    //获得当前坐标像素点颜色值
                    Color color = bmp.GetPixel(x, y);
                    if (color.A > 0)//如当前像素点是非 apach 通道的透明色则记录
                    {
                        //设置像素点位置的颜色,这里我为了能凸显出来,我用另一种颜色代替
                        output.SetPixel(x, y, Color.White);
                    }
                }
            }
            this.pictureBox2.Image = output;//输出显示图片
        }


        /// <summary>
        /// 算子外轮廓方式
        /// </summary>
        protected void GetImageOutsideBySobel()
        {
            Bitmap bmp = new Bitmap(SourceImage);//将原图转为位图
            Bitmap b = Sobel(bmp); //算子获取边缘
            this.pictureBox3.Image = b;
        }

        /// <summary>
        /// 获得图片外轮廓
        /// </summary>
        protected void GetImageOutside()
        {
           
            //实现原理: 
            //1.按x轴逐行扫描,判断x轴上首次出现非透明像素,及x轴上第二次及N次出现透明像素
            //2.按y轴逐行扫描,判断y轴上首次出现非透明像素,及y轴上第二次及N次出现透明像素;

            int w = SourceImage.Width, h = SourceImage.Height;//获得原图宽高
            Bitmap bmp = new Bitmap(SourceImage);//将原图转为位图
            Bitmap output = new Bitmap(SourceImage.Width, SourceImage.Height);//用于输出内轮廓的图片

            List<Point> ouput_data = new List<Point>();//计算后输出像素点信息

            //1.按x轴逐行扫描,判断x轴上首次出现非透明像素,及x轴上第二次及N次出现透明像素
            for (int y = 0; y < h; y++)
            {
                //记录沿X轴方向递增最后一次非透明色的位置
                int? start_x = null;
                for (int x = 0; x < w; x++)
                {
                    //如当前像素点是非 apach 通道的透明色则记录
                    if (bmp.GetPixel(x, y).A > 0)
                    {
                        //记录开始位置
                        if (start_x == null)
                        {
                            start_x = x;
                            ouput_data.Add(new Point(x, y));
                        }
                        //找下个开始位置
                        else if (x + 1 < w - 1 && start_x != null)
                        {
                            //开始位置向后递增一个像素如果像素为透明则记录
                            if (bmp.GetPixel(1 + x, y).A == 0)
                            {
                                ouput_data.Add(new Point(x, y));
                                start_x = null;//重置开始x位置
                            }
                        }
                        else if (x + 1 > w - 1)
                        {
                            ouput_data.Add(new Point(x, y));
                        }
                    }
                }
            }

            //2.按y轴逐行扫描,判断y轴上首次出现非透明像素,及y轴上第二次及N次出现透明像素;
            for (int x = 0; x < w; x++)
            {
                //记录沿y轴方向递增最后一次非透明色的位置
                //如当前像素点是非 apach 通道的透明色则记录
                int? start_y = null;
                for (int y = 0; y < h; y++)
                {
                    if (bmp.GetPixel(x, y).A > 0)
                    {
                        //记录开始位置
                        if (start_y == null)
                        {
                            start_y = y;
                            ouput_data.Add(new Point(x, y));
                        }
                        //找下个开始位置
                        else if (y + 1 < h - 1 && start_y != null)
                        {
                            //开始位置向后递增一个像素如果像素为透明则记录
                            if (bmp.GetPixel(x, y + 1).A == 0)
                            {
                                ouput_data.Add(new Point(x, y));
                                start_y = null;//重置开始y位置
                            }
                        }
                        else if (y + 1 > h - 1)
                        {
                            //如在最后边界上则也记录
                            ouput_data.Add(new Point(x, y));
                        }
                    }
                }
            }

            ouput_data.ForEach(x =>
                {
                    output.SetPixel(x.X, x.Y, Color.Red);//输出图片中写入像素点位置
                });

            this.pictureBox4.Image = output;//输出显示图片
        }


        /// <summary>
        /// Sobel 算子  提取图片轮廓 ,暂时没用上只是了解下,后续空余时间会注释上
        /// </summary>
        protected Bitmap Sobel(Bitmap a)
        {
            int w = a.Width;
            int h = a.Height;
            try
            {
                Bitmap dstBitmap = new Bitmap(w, h, PixelFormat.Format24bppRgb);
                BitmapData srcData = a.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
                BitmapData dstData = dstBitmap.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
                unsafe
                {
                    byte* pIn = (byte*)srcData.Scan0.ToPointer();
                    byte* pOut = (byte*)dstData.Scan0.ToPointer();
                    byte* p;
                    int stride = srcData.Stride;
                    for (int y = 0; y < h; y++)
                    {
                        for (int x = 0; x < w; x++)
                        {
                            //边缘八个点像素不变
                            if (x == 0 || x == w - 1 || y == 0 || y == h - 1)
                            {
                                pOut[0] = pIn[0];
                                pOut[1] = pIn[1];
                                pOut[2] = pIn[2];
                            }
                            else
                            {
                                int r0, r1, r2, r3, r4, r5, r6, r7, r8;
                                int g1, g2, g3, g4, g5, g6, g7, g8, g0;
                                int b1, b2, b3, b4, b5, b6, b7, b8, b0;
                                double vR, vG, vB;
                                //左上
                                p = pIn - stride - 3;
                                r1 = p[2];
                                g1 = p[1];
                                b1 = p[0];
                                //正上
                                p = pIn - stride;
                                r2 = p[2];
                                g2 = p[1];
                                b2 = p[0];
                                //右上
                                p = pIn - stride + 3;
                                r3 = p[2];
                                g3 = p[1];
                                b3 = p[0];
                                //左
                                p = pIn - 3;
                                r4 = p[2];
                                g4 = p[1];
                                b4 = p[0];
                                //右
                                p = pIn + 3;
                                r5 = p[2];
                                g5 = p[1];
                                b5 = p[0];
                                //左下
                                p = pIn + stride - 3;
                                r6 = p[2];
                                g6 = p[1];
                                b6 = p[0];
                                //正下
                                p = pIn + stride;
                                r7 = p[2];
                                g7 = p[1];
                                b7 = p[0];
                                // 右下 
                                p = pIn + stride + 3;
                                r8 = p[2];
                                g8 = p[1];
                                b8 = p[0];
                                //中心点
                                p = pIn;
                                r0 = p[2];
                                g0 = p[1];
                                b0 = p[0];
                                //使用模板
                                vR = (double)(Math.Abs(r1 + 2 * r4 + r6 - r3 - 2 * r5 - r8) + Math.Abs(r1 + 2 * r2 + r3 - r6 - 2 * r7 - r8));
                                vG = (double)(Math.Abs(g1 + 2 * g4 + g6 - g3 - 2 * g5 - g8) + Math.Abs(g1 + 2 * g2 + g3 - g6 - 2 * g7 - g8));
                                vB = (double)(Math.Abs(b1 + 2 * b4 + b6 - b3 - 2 * b5 - b8) + Math.Abs(b1 + 2 * b2 + b3 - b6 - 2 * b7 - b8));
                                if (vR > 0)
                                {
                                    //vR = Math.Min(255, vR);
                                    vR = Color.Red.R;

                                }
                                else
                                {
                                    vR = Math.Max(0, vR);
                                }
                                if (vG > 0)
                                {
                                    //vG = Math.Min(255, vG);
                                    vG = Color.Red.G;
                                }
                                else
                                {
                                    vG = Math.Max(0, vG);
                                }
                                if (vB > 0)
                                {
                                    //vB = Math.Min(255, vB);//自定义格式
                                    vB = Color.Red.B;
                                }
                                else
                                {
                                    vB = Math.Max(0, vB);
                                }
                                pOut[0] = (byte)vB;
                                pOut[1] = (byte)vG;
                                pOut[2] = (byte)vR;
                            }
                            pIn += 3;
                            pOut += 3;
                        }
                        pIn += srcData.Stride - w * 3;
                        pOut += srcData.Stride - w * 3;
                    }
                }
                a.UnlockBits(srcData);
                dstBitmap.UnlockBits(dstData);

                return dstBitmap;
            }
            catch
            {
                return null;
            }
        }


    }
}

随笔记录下,如有不对之处欢迎大咖斧正.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值