最近正在研究 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;
}
}
}
}
随笔记录下,如有不对之处欢迎大咖斧正.