728x90
728x170
▶ SmoothingFilterType.cs
namespace TestProject
{
/// <summary>
/// 스무딩 필터 타입
/// </summary>
public enum SmoothingFilterType
{
/// <summary>
/// None
/// </summary>
None,
/// <summary>
/// Gaussian 3X3
/// </summary>
Gaussian3X3,
/// <summary>
/// Gaussian 5X5
/// </summary>
Gaussian5X5,
/// <summary>
/// Gaussian 7X7
/// </summary>
Gaussian7X7,
/// <summary>
/// Median 3X3
/// </summary>
Median3X3,
/// <summary>
/// Median 5X5
/// </summary>
Median5X5,
/// <summary>
/// Median 7X7
/// </summary>
Median7X7,
/// <summary>
/// Median 9X9
/// </summary>
Median9X9,
/// <summary>
/// Mean 3X3
/// </summary>
Mean3X3,
/// <summary>
/// Mean 5X5
/// </summary>
Mean5X5,
/// <summary>
/// Low Pass 3X3
/// </summary>
LowPass3X3,
/// <summary>
/// Low Pass 5X5
/// </summary>
LowPass5X5,
/// <summary>
/// Sharpen 3X3
/// </summary>
Sharpen3X3,
}
}
728x90
▶ Matrix.cs
namespace TestProject
{
/// <summary>
/// 매트릭스
/// </summary>
public static class Matrix
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region Gaussian 3X3 - Gaussian3X3
/// <summary>
/// Gaussian 3X3
/// </summary>
public static double[,] Gaussian3X3
{
get
{
return new double[,]
{
{ 1, 2, 1 },
{ 2, 4, 2 },
{ 1, 2, 1 }
};
}
}
#endregion
#region Gaussian 5X5 - Gaussian5X5
/// <summary>
/// Gaussian 5X5
/// </summary>
public static double[,] Gaussian5X5
{
get
{
return new double[,]
{
{ 2, 4, 5, 4, 2 },
{ 4, 9, 12, 9, 4 },
{ 5, 12, 15, 12, 5 },
{ 4, 9, 12, 9, 4 },
{ 2, 4, 5, 4, 2 }
};
}
}
#endregion
#region Gaussian 7X7 - Gaussian7X7
/// <summary>
/// Gaussian 7X7
/// </summary>
public static double[,] Gaussian7X7
{
get
{
return new double[,]
{
{ 1, 1, 2, 2, 2, 1, 1 },
{ 1, 2, 2, 4, 2, 2, 1 },
{ 2, 2, 4, 8, 4, 2, 2 },
{ 2, 4, 8, 16, 8, 4, 2 },
{ 2, 2, 4, 8, 4, 2, 2 },
{ 1, 2, 2, 4, 2, 2, 1 },
{ 1, 1, 2, 2, 2, 1, 1 }
};
}
}
#endregion
#region Mean 3X3 - Mean3X3
/// <summary>
/// Mean 3X3
/// </summary>
public static double[,] Mean3X3
{
get
{
return new double[,]
{
{ 1, 1, 1 },
{ 1, 1, 1 },
{ 1, 1, 1 }
};
}
}
#endregion
#region Mean 5X5 - Mean5X5
/// <summary>
/// Mean 5X5
/// </summary>
public static double[,] Mean5X5
{
get
{
return new double[,]
{
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 }
};
}
}
#endregion
#region Low Pass 3X3 - LowPass3X3
/// <summary>
/// Low Pass 3X3
/// </summary>
public static double [,] LowPass3X3
{
get
{
return new double [,]
{
{ 1, 2, 1 },
{ 2, 4, 2 },
{ 1, 2, 1 }
};
}
}
#endregion
#region Low Pass 5X5 - LowPass5X5
/// <summary>
/// Low Pass 5X5
/// </summary>
public static double[,] LowPass5X5
{
get
{
return new double[,]
{
{ 1, 1, 1, 1, 1 },
{ 1, 4, 4, 4, 1 },
{ 1, 4, 12, 4, 1 },
{ 1, 4, 4, 4, 1 },
{ 1, 1, 1, 1, 1 }
};
}
}
#endregion
#region Sharpen 3X3 - Sharpen3X3
/// <summary>
/// Sharpen 3X3
/// </summary>
public static double[,] Sharpen3X3
{
get
{
return new double[,]
{
{ -1, -2, -1 },
{ 2, 4, 2 },
{ 1, 2, 1 }
};
}
}
#endregion
}
}
300x250
▶ BitmapHelper.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace TestProject
{
/// <summary>
/// 비트맵 헬퍼
/// </summary>
public static class BitmapHelper
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 비트맵 로드하기 - LoadBitmap(filePath)
/// <summary>
/// 비트맵 로드하기
/// </summary>
/// <param name="filePath">파일 경로</param>
/// <returns>비트맵</returns>
public static Bitmap LoadBitmap(string filePath)
{
using(Bitmap bitmap = new Bitmap(filePath))
{
return new Bitmap(bitmap);
}
}
#endregion
#region 카툰 필터 적용하기 - ApplyCartoonEffectFilter(sourceBitmap, threshold, filterType)
/// <summary>
/// 카툰 필터 적용하기
/// </summary>
/// <param name="sourceBitmap">소스 비트맵</param>
/// <param name="threshold">임계치</param>
/// <param name="filterType">스무딩 필터 타입</param>
/// <returns>비트맵</returns>
public static Bitmap ApplyCartoonEffectFilter
(
Bitmap sourceBitmap,
byte threshold = 0,
SmoothingFilterType filterType = SmoothingFilterType.None
)
{
sourceBitmap = ApplySmoothingFilter(sourceBitmap, filterType);
BitmapData sourceBitmapData = sourceBitmap.LockBits
(
new Rectangle (0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
byte[] sourceByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
byte[] targetByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
Marshal.Copy(sourceBitmapData.Scan0, sourceByteArray, 0, sourceByteArray.Length);
sourceBitmap.UnlockBits(sourceBitmapData);
int byteOffset = 0;
int blueGradient = 0;
int greenGradient = 0;
int redGradient = 0;
double blue = 0;
double green = 0;
double red = 0;
bool exceedsThreshold = false;
for(int offsetY = 1; offsetY < sourceBitmap.Height - 1; offsetY++)
{
for(int offsetX = 1; offsetX < sourceBitmap.Width - 1; offsetX++)
{
byteOffset = offsetY * sourceBitmapData.Stride + offsetX * 4;
blueGradient = Math.Abs(sourceByteArray[byteOffset - 4] - sourceByteArray[byteOffset + 4]);
blueGradient += Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + sourceBitmapData.Stride]
);
byteOffset++;
greenGradient = Math.Abs(sourceByteArray[byteOffset - 4] - sourceByteArray[byteOffset + 4]);
greenGradient += Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + sourceBitmapData.Stride]
);
byteOffset++;
redGradient = Math.Abs(sourceByteArray[byteOffset - 4] - sourceByteArray[byteOffset + 4]);
redGradient += Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + sourceBitmapData.Stride]
);
if(blueGradient + greenGradient + redGradient > threshold)
{
exceedsThreshold = true ;
}
else
{
byteOffset -= 2;
blueGradient = Math.Abs(sourceByteArray[byteOffset - 4] - sourceByteArray[byteOffset + 4]);
byteOffset++;
greenGradient = Math.Abs(sourceByteArray[byteOffset - 4] - sourceByteArray[byteOffset + 4]);
byteOffset++;
redGradient = Math.Abs(sourceByteArray[byteOffset - 4] - sourceByteArray[byteOffset + 4]);
if(blueGradient + greenGradient + redGradient > threshold)
{
exceedsThreshold = true ;
}
else
{
byteOffset -= 2;
blueGradient = Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + sourceBitmapData.Stride]
);
byteOffset++;
greenGradient = Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + sourceBitmapData.Stride]
);
byteOffset++;
redGradient = Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + sourceBitmapData.Stride]
);
if(blueGradient + greenGradient + redGradient > threshold)
{
exceedsThreshold = true ;
}
else
{
byteOffset -= 2;
blueGradient = Math.Abs
(
sourceByteArray[byteOffset - 4 - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + 4 + sourceBitmapData.Stride]
);
blueGradient += Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride + 4] -
sourceByteArray[byteOffset + sourceBitmapData.Stride - 4]
);
byteOffset++;
greenGradient = Math.Abs
(
sourceByteArray[byteOffset - 4 - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + 4 + sourceBitmapData.Stride]
);
greenGradient += Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride + 4] -
sourceByteArray[byteOffset + sourceBitmapData.Stride - 4]
);
byteOffset++;
redGradient = Math.Abs
(
sourceByteArray[byteOffset - 4 - sourceBitmapData.Stride] -
sourceByteArray[byteOffset + 4 + sourceBitmapData.Stride]
);
redGradient += Math.Abs
(
sourceByteArray[byteOffset - sourceBitmapData.Stride + 4] -
sourceByteArray[byteOffset + sourceBitmapData.Stride - 4]
);
if(blueGradient + greenGradient + redGradient > threshold)
{
exceedsThreshold = true ;
}
else
{
exceedsThreshold = false ;
}
}
}
}
byteOffset -= 2;
if(exceedsThreshold)
{
blue = 0;
green = 0;
red = 0;
}
else
{
blue = sourceByteArray[byteOffset ];
green = sourceByteArray[byteOffset + 1];
red = sourceByteArray[byteOffset + 2];
}
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue ));
green = (green > 255 ? 255 : (green < 0 ? 0 : green));
red = (red > 255 ? 255 : (red < 0 ? 0 : red ));
targetByteArray[byteOffset ] = (byte)blue;
targetByteArray[byteOffset + 1] = (byte)green;
targetByteArray[byteOffset + 2] = (byte)red;
targetByteArray[byteOffset + 3] = 255;
}
}
Bitmap targetBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData targetBitmapData = targetBitmap.LockBits
(
new Rectangle(0, 0, targetBitmap.Width, targetBitmap.Height),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb
);
Marshal.Copy(targetByteArray, 0, targetBitmapData.Scan0, targetByteArray.Length);
targetBitmap.UnlockBits(targetBitmapData);
return targetBitmap;
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 중앙값 필터 적용하기 - ApplyMedianFilter(sourceBitmap, matrixSize)
/// <summary>
/// 중앙값 필터 적용하기
/// </summary>
/// <param name="sourceBitmap">소스 비트맵</param>
/// <param name="matrixSize">매트릭스 크기</param>
/// <returns>비트맵</returns>
private static Bitmap ApplyMedianFilter(Bitmap sourceBitmap, int matrixSize)
{
BitmapData sourceBitmapData = sourceBitmap.LockBits
(
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
byte[] sourceByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
byte[] targetByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
Marshal.Copy(sourceBitmapData.Scan0, sourceByteArray, 0, sourceByteArray.Length);
sourceBitmap.UnlockBits(sourceBitmapData);
int filterOffset = (matrixSize - 1) / 2;
int temporaryOffset = 0;
int byteOffset = 0;
List<int> neighbourPixelList = new List<int>();
byte[] middlePixel;
for(int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++)
{
for(int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++)
{
byteOffset = offsetY * sourceBitmapData.Stride + offsetX * 4;
neighbourPixelList.Clear();
for(int filterY = -filterOffset; filterY <= filterOffset; filterY++)
{
for(int filterX = -filterOffset; filterX <= filterOffset; filterX++)
{
temporaryOffset = byteOffset + (filterX * 4) + (filterY * sourceBitmapData.Stride);
neighbourPixelList.Add(BitConverter.ToInt32(sourceByteArray, temporaryOffset));
}
}
neighbourPixelList.Sort();
middlePixel = BitConverter.GetBytes(neighbourPixelList[filterOffset]);
targetByteArray[byteOffset ] = middlePixel[0];
targetByteArray[byteOffset + 1] = middlePixel[1];
targetByteArray[byteOffset + 2] = middlePixel[2];
targetByteArray[byteOffset + 3] = middlePixel[3];
}
}
Bitmap targetBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData targetBitmapData = targetBitmap.LockBits
(
new Rectangle(0, 0, targetBitmap.Width, targetBitmap.Height),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb
);
Marshal.Copy(targetByteArray, 0, targetBitmapData.Scan0, targetByteArray.Length);
targetBitmap.UnlockBits(targetBitmapData);
return targetBitmap;
}
#endregion
#region 회선 필터 적용하기 - ApplyConvolutionFilter(sourceBitmap, filterArray, factor, bias)
/// <summary>
/// 회선 필터 적용하기
/// </summary>
/// <param name="sourceBitmap">소스 비트맵</param>
/// <param name="filterArray">필터 배열</param>
/// <param name="factor">인자</param>
/// <param name="bias">바이어스</param>
/// <returns>비트맵</returns>
private static Bitmap ApplyConvolutionFilter(Bitmap sourceBitmap, double[,] filterArray, double factor = 1, int bias = 0)
{
BitmapData sourceBitmapData = sourceBitmap.LockBits
(
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
byte[] sourceByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
byte[] targetByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
Marshal.Copy(sourceBitmapData.Scan0, sourceByteArray, 0, sourceByteArray.Length);
sourceBitmap.UnlockBits(sourceBitmapData);
double blue = 0.0;
double green = 0.0;
double red = 0.0;
int filterWidth = filterArray.GetLength(1);
int filterHeight = filterArray.GetLength(0);
int filterOffset = (filterWidth - 1) / 2;
int sourceOffset = 0;
int targetOffset = 0;
for(int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++)
{
for(int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++)
{
blue = 0;
green = 0;
red = 0;
targetOffset = offsetY * sourceBitmapData.Stride + offsetX * 4;
for(int filterY = -filterOffset; filterY <= filterOffset; filterY++)
{
for(int filterX = -filterOffset; filterX <= filterOffset; filterX++)
{
sourceOffset = targetOffset + (filterX * 4) + (filterY * sourceBitmapData.Stride);
blue += (double)(sourceByteArray[sourceOffset]) *
filterArray[filterY + filterOffset, filterX + filterOffset];
green += (double)(sourceByteArray[sourceOffset + 1]) *
filterArray[filterY + filterOffset, filterX + filterOffset];
red += (double)(sourceByteArray[sourceOffset + 2]) *
filterArray[filterY + filterOffset, filterX + filterOffset];
}
}
blue = factor * blue + bias;
green = factor * green + bias;
red = factor * red + bias;
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue ));
green = (green > 255 ? 255 : (green < 0 ? 0 : green));
red = (red > 255 ? 255 : (red < 0 ? 0 : red ));
targetByteArray[targetOffset ] = (byte)(blue);
targetByteArray[targetOffset + 1] = (byte)(green);
targetByteArray[targetOffset + 2] = (byte)(red);
targetByteArray[targetOffset + 3] = 255;
}
}
Bitmap targetBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData targetBitmapData = targetBitmap.LockBits
(
new Rectangle(0, 0, targetBitmap.Width, targetBitmap.Height),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb
);
Marshal.Copy(targetByteArray, 0, targetBitmapData.Scan0, targetByteArray.Length);
targetBitmap.UnlockBits(targetBitmapData);
return targetBitmap;
}
#endregion
#region 스무딩 필터 적용하기 - ApplySmoothingFilter(sourceBitmap, filterType)
/// <summary>
/// 스무딩 필터 적용하기
/// </summary>
/// <param name="sourceBitmap">소스 비트맵</param>
/// <param name="filterType">스무딩 필터 타입</param>
/// <returns>비트맵</returns>
private static Bitmap ApplySmoothingFilter(Bitmap sourceBitmap, SmoothingFilterType filterType = SmoothingFilterType.None)
{
Bitmap targetBitmap = null;
switch(filterType)
{
case SmoothingFilterType.None :
targetBitmap = sourceBitmap;
break;
case SmoothingFilterType.Gaussian3X3 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.Gaussian3X3, 1.0 / 16.0, 0);
break;
case SmoothingFilterType.Gaussian5X5 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.Gaussian5X5, 1.0 / 159.0, 0);
break;
case SmoothingFilterType.Gaussian7X7 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.Gaussian7X7, 1.0 / 136.0, 0);
break;
case SmoothingFilterType.Median3X3 :
targetBitmap = ApplyMedianFilter(sourceBitmap, 3);
break;
case SmoothingFilterType.Median5X5 :
targetBitmap = ApplyMedianFilter(sourceBitmap, 5);
break;
case SmoothingFilterType.Median7X7 :
targetBitmap = ApplyMedianFilter(sourceBitmap, 7);
break;
case SmoothingFilterType.Median9X9 :
targetBitmap = ApplyMedianFilter(sourceBitmap, 9);
break;
case SmoothingFilterType.Mean3X3 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.Mean3X3, 1.0 / 9.0, 0);
break;
case SmoothingFilterType.Mean5X5 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.Mean5X5, 1.0 / 25.0, 0);
break;
case SmoothingFilterType.LowPass3X3 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.LowPass3X3, 1.0 / 16.0, 0);
break;
case SmoothingFilterType.LowPass5X5 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.LowPass5X5, 1.0 / 60.0, 0);
break;
case SmoothingFilterType.Sharpen3X3 :
targetBitmap = ApplyConvolutionFilter(sourceBitmap, Matrix.Sharpen3X3, 1.0 / 8.0, 0);
break;
}
return targetBitmap;
}
#endregion
}
}
▶ MainForm.cs
using System.Drawing;
using System.Windows.Forms;
namespace TestProject
{
/// <summary>
/// 메인 폼
/// </summary>
public partial class MainForm : Form
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainForm()
/// <summary>
/// 생성자
/// </summary>
public MainForm()
{
InitializeComponent();
Bitmap sourceBitmap = BitmapHelper.LoadBitmap("IMAGE\\sample.jpg");
Bitmap targetBitmap = BitmapHelper.ApplyCartoonEffectFilter(sourceBitmap, 128, SmoothingFilterType.Gaussian3X3);
this.pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
this.pictureBox.Image = targetBitmap;
}
#endregion
}
}
728x90
그리드형(광고전용)
'C# > WinForm' 카테고리의 다른 글
[C#/WINFORM] Bitmap 클래스 : 평균 색상 필터(Average Color Filter) 사용하기 (0) | 2021.01.10 |
---|---|
[C#/WINFORM] Bitmap 클래스 : 침식/팽창 필터(Erosion/Dilation Filter) 사용하기 (0) | 2021.01.10 |
[C#/WINFORM] Bitmap 클래스 : 부울 가장자리 탐지 필터(Boolean Edge Detection Filter) 사용하기 (0) | 2021.01.10 |
[C#/WINFORM] Bitmap 클래스 : 2개의 비트맵에서 차이점 찾기 (0) | 2021.01.10 |
[C#/WINFORM] Bitmap 클래스 : 그라디언트 기반 가장자리 탐지 필터(Gradient Based Edge Detection Filter) 사용하기 (0) | 2021.01.10 |
[C#/WINFORM] Bitmap 클래스 : 스무딩 필터(Smoothing Filter) 사용하기 (0) | 2021.01.09 |
[C#/WINFORM] Bitmap 클래스 : 선명 가장자리 탐지 필터(Sharpen Edge Detection Filter) 사용하기 (0) | 2021.01.09 |
[C#/WINFORM] 가우시안 커널(Gaussian Kernel) 계산하기 (0) | 2021.01.09 |
[C#/WINFORM] Bitmap 클래스 : 회색조 비트맵 구하기 (0) | 2021.01.08 |
[C#/WINFORM] Bitmap 클래스 : 투명 비트맵 구하기 (0) | 2021.01.08 |