diff --git a/BlazorApp/BlazorApp.csproj b/BlazorApp/BlazorApp.csproj
index 73a18f0..2614717 100644
--- a/BlazorApp/BlazorApp.csproj
+++ b/BlazorApp/BlazorApp.csproj
@@ -19,8 +19,8 @@
-
-
+
+
diff --git a/BlazorApp/Pages/Index.razor b/BlazorApp/Pages/Index.razor
index 64f37f1..168093d 100644
--- a/BlazorApp/Pages/Index.razor
+++ b/BlazorApp/Pages/Index.razor
@@ -2,12 +2,20 @@
@using OpenCvSharp
-Index
+PINBlog
-
Hello, world!
+Hello, PINblog!
-Welcome to your new app.
+Welcome to PINBlog's Gabor Filter Test Page'.
+
+
+ ⬅️ 왼쪽의 GaborFilter Test 탭을 클릭하면 해당 페이지로 이동합니다. 🤗🤗
+
+
+
diff --git a/BlazorApp/Pages/OpenCvSharpSample.razor b/BlazorApp/Pages/OpenCvSharpSample.razor
index 40db74e..6dfa1c4 100644
--- a/BlazorApp/Pages/OpenCvSharpSample.razor
+++ b/BlazorApp/Pages/OpenCvSharpSample.razor
@@ -4,10 +4,85 @@
@inject HttpClient httpClient;
@implements IDisposable
-OpenCvSharp Sample
-
-OpenCvSharp on WebAssembly
+Gabor Filter Test
+
+
Gabor Filter란?
+
Gabor Filter는 영상처리에서 Bio-inspired라는 키워드가 있으면 빠지지않고 등장한다.
+
외곽선을 검출하는 기능을 하는 필터로, 사람의 시각체계가 반응하는 것과 비슷하다는 이유로 널리 사용되고 있다.
+
Gabor Fiter는 간단히 말해서 사인 함수로 모듈레이션 된 Gaussian Filter라고 생각할 수 있다.
+
파라미터를 조절함에 따라 Edge의 크기나 방향성을 바꿀 수 있으므로 Bio-inspired 영상처리 알고리즘에서 특징점 추출 알고리즘으로 핵심적인 역할을 하고 있다.
+
2D Gabor Filter의 수식은 아래와 같다.
+
+
+
+
+
+
함수 원형
+
+
+ cv::Mat cv::getGaborKernel(cv::Size ksize, double sigma, double theta, double lambd, double gamma, double psi = CV_PI*0.5, int ktype = CV_64F)
+
+
+
cv::getGaborKernel 함수는 OpenCV에서 가버필터(Gabor filter)를 생성하는 데 사용된다.
+
가버필터는 이미지 처리와 컴퓨터 비전에서 특정 방향성과 주파수의 특징을 강조하는 데 사용되는 선형 필터이다.
+
Parameters
+
+ -
+
ksize: 커널의 크기로, cv::Size 타입. 커널의 너비와 높이를 지정.
+
+ -
+
sigma: 가우시안 함수의 표준 편차. 값이 커질수록 커널의 크기가 커지고, 필터의 감도가 낮아진다.
+
+ -
+
theta: 필터의 방향을 라디안 단위로 지정한다. 0은 수평 방향, CV_PI/2는 수직 방향을 의미한다.
+
+ -
+
lambd: 가버필터의 파장. 이미지의 특정 패턴과 얼마나 잘 매치할지를 결정한다.
+
+ -
+
gamma: 공간종횡비(Spatial aspect ratio)로, 타원형 가버 함수의 타원성을 결정한다. (gamma=1은 원형, gamma<1은 타원형)
+
+ -
+
psi: 위상변위(Phase offset)로, 가버 필터의 위상을 조절한다. (일반적으로 CV_PI*0.5가 기본값으로 사용)
+
+ -
+
ktype: 커널 타입. (보통 CV_64F (64-비트 부동소수점)를 사용)
+
+
+
+
+
+
Gabor Filter 실행 (속도 느림 주의)
+
+
+
+
+
+
Threshold (0 ~ 255)
+
+
-
-
-
+
+
@code {
private Mat? srcMat;
+ private Mat? srcMat2;
private ElementReference srcCanvas;
private ElementReference dstCanvas;
+ private ElementReference srcCanvas2;
+ private ElementReference dstCanvas2;
private CanvasClient? srcCanvasClient;
private CanvasClient? dstCanvasClient;
+ private CanvasClient? srcCanvasClient2;
+ private CanvasClient? dstCanvasClient2;
+ private int valKernelSize = 3;
+ private double valSigma = 0.0;
+ private double valTheta = 0.0;
+ private double valLambd = 0.0;
+ private double valGamma = 0.0;
+ private double valPsi = 0.0;
+ private int valthreshold = 127;
public void Dispose()
{
srcMat?.Dispose();
+ srcMat2?.Dispose();
}
protected override async Task OnInitializedAsync()
@@ -43,19 +133,35 @@
protected override async Task OnAfterRenderAsync(bool firstRender)
{
- //if (!firstRender)
- // return;
+ if (!firstRender)
+ return;
await base.OnAfterRenderAsync(firstRender);
- var imageBytes = await httpClient.GetByteArrayAsync("images/Mandrill.bmp");
+ var imageBytes = await httpClient.GetByteArrayAsync("images/mandrill.bmp");
srcMat ??= Mat.FromImageData(imageBytes);
-
+ if(srcMat.Width < 256 || srcMat.Height < 256)
+ {
+ Cv2.PyrUp(srcMat, srcMat, new Size(256, 256));
+ }
srcCanvasClient ??= new CanvasClient(jsRuntime, srcCanvas);
dstCanvasClient ??= new CanvasClient(jsRuntime, dstCanvas);
await srcCanvasClient.DrawMatAsync(srcMat);
+
+
+ var imageBytes2 = await httpClient.GetByteArrayAsync("images/lenna.bmp");
+ srcMat2 ??= Mat.FromImageData(imageBytes2);
+ if(srcMat2.Width > 256 || srcMat2.Height > 256)
+ {
+ Cv2.PyrDown(srcMat2, srcMat2, new Size(256, 256));
+ }
+
+ srcCanvasClient2 ??= new CanvasClient(jsRuntime, srcCanvas2);
+ dstCanvasClient2 ??= new CanvasClient(jsRuntime, dstCanvas2);
+
+ await srcCanvasClient2.DrawMatAsync(srcMat2);
}
-
+
private async Task Grayscale()
{
if (srcMat is null)
@@ -68,7 +174,7 @@
await dstCanvasClient.DrawMatAsync(grayMat);
}
-
+
private async Task PseudoColor()
{
if (srcMat is null)
@@ -83,7 +189,7 @@
await dstCanvasClient.DrawMatAsync(dstMat);
}
-
+
private async Task Threshold()
{
if (srcMat is null)
@@ -92,45 +198,175 @@
throw new InvalidOperationException($"{nameof(dstCanvasClient)} is null");
using var grayMat = new Mat();
- using var dstMat = new Mat();
+ using var dstMat = new Mat(srcMat.Size(), MatType.CV_8UC1);
Cv2.CvtColor(srcMat, grayMat, ColorConversionCodes.BGR2GRAY);
- Cv2.Threshold(grayMat, dstMat, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
+ //Cv2.Threshold(grayMat, dstMat, 127, 255, ThresholdTypes.Binary);
+
+ for(int i=0; i(i, j);
+ byte newValue = (Convert.ToInt32(pixelValue) > valthreshold) ? (byte)255 : (byte)0;
+ dstMat.Set(i, j, newValue);
+ }
+ }
await dstCanvasClient.DrawMatAsync(dstMat);
}
- private async Task Canny()
+ private async Task GaborFilter()
{
- if (srcMat is null)
- throw new InvalidOperationException($"{nameof(srcMat)} is null");
- if (dstCanvasClient is null)
- throw new InvalidOperationException($"{nameof(dstCanvasClient)} is null");
+ if (srcMat2 is null)
+ throw new InvalidOperationException($"{nameof(srcMat2)} is null");
+ if (dstCanvasClient2 is null)
+ throw new InvalidOperationException($"{nameof(dstCanvasClient2)} is null");
- using var grayMat = new Mat();
- using var dstMat = new Mat();
- Cv2.CvtColor(srcMat, grayMat, ColorConversionCodes.BGR2GRAY);
- Cv2.Canny(grayMat, dstMat, 32, 128);
+ using var grayMat = new Mat(srcMat2.Size(), MatType.CV_32F);
+ Cv2.CvtColor(srcMat2, grayMat, ColorConversionCodes.BGR2GRAY);
- await dstCanvasClient.DrawMatAsync(dstMat);
+ int kernel_size = valKernelSize;
+ double sigma = valSigma;
+ double theta = valTheta * Cv2.PI / 180.0;
+ double lambd = valLambd;
+ double gamma = valGamma;
+ double psi = valPsi * Cv2.PI / 180.0;
+
+
+ Mat rst = new Mat();
+ try
+ {
+ //Mat gabor_filter = Cv2.GetGaborKernel(new Size(kernel_size, kernel_size), sigma, theta, lambd, gamma, psi, ktype);
+ //Mat kenel = new Mat(5, 5, MatType.CV_8UC1);
+ //Cv2.Filter2D(grayMat, rst, grayMat.Type(), kenel);
+
+ Mat gabor_filter = Gabor2DFilter.CreateGaborKernel(kernel_size, sigma, theta, lambd, gamma, psi);
+ rst = Gabor2DFilter.Filter2D(grayMat, gabor_filter);
+ }
+ catch(Exception e)
+ {
+ String log = e.Message;
+ }
+ await dstCanvasClient2.DrawMatAsync(rst);
}
- private async Task Akaze()
+ public class Gabor2DFilter
{
- if (srcMat is null)
- throw new InvalidOperationException($"{nameof(srcMat)} is null");
- if (dstCanvasClient is null)
- throw new InvalidOperationException($"{nameof(dstCanvasClient)} is null");
+ public static Mat CreateGaborKernel(int ksize, double sigma, double theta, double lambd, double gamma, double psi)
+ {
+ int center = ksize / 2;
+ Mat kernel = new Mat(ksize, ksize, MatType.CV_32F);
+ double sigmaX = sigma;
+ double sigmaY = sigma / gamma;
- using var grayMat = new Mat();
- Cv2.CvtColor(srcMat, grayMat, ColorConversionCodes.BGR2GRAY);
+ //for (int y = -center; y <= center; y++)
+ //{
+ // for (int x = -center; x <= center; x++)
+ // {
+ // double xPrime = x * Math.Cos(theta) + y * Math.Sin(theta);
+ // double yPrime = -x * Math.Sin(theta) + y * Math.Cos(theta);
+ // double value = Math.Exp(-0.5 * (xPrime * xPrime / (sigmaX * sigmaX) + yPrime * yPrime / (sigmaY * sigmaY)));
+ // value *= Math.Cos(2 * Math.PI * xPrime / lambd + psi);
+ // kernel.Set(center + y, center + x, Convert.ToSingle(value));
+ // }
+ //}
+ unsafe
+ {
+ for (int y = -center; y <= center; y++)
+ {
+ for (int x = -center; x <= center; x++)
+ {
+ double xPrime = x * Math.Cos(theta) + y * Math.Sin(theta);
+ double yPrime = -x * Math.Sin(theta) + y * Math.Cos(theta);
+ double value = Math.Exp(-0.5 * (xPrime * xPrime / (sigmaX * sigmaX) + yPrime * yPrime / (sigmaY * sigmaY)));
+ value *= Math.Cos(2 * Math.PI * xPrime / lambd + psi);
- using var akaze = AKAZE.Create();
- using var descriptors = new Mat();
- akaze.DetectAndCompute(grayMat, null, out var keypoints, descriptors);
+ float* kernelPtr = (float*)(kernel.DataPointer + (center + y) * kernel.Step() + (center + x) * sizeof(float));
+ *kernelPtr = Convert.ToSingle(value);
+ }
+ }
+ }
+ return kernel;
+ }
+
+ public static Mat Filter2D(Mat src, Mat kernel)
+ {
+ int kernelRows = kernel.Rows;
+ int kernelCols = kernel.Cols;
+ int kernelCenterX = kernelCols / 2;
+ int kernelCenterY = kernelRows / 2;
+ MatType type = src.Type();
+ Mat dst = new Mat(src.Size(), type);
+
+ try
+ {
+ //for (int y = 0; y < src.Rows; y++)
+ //{
+ // for (int x = 0; x < src.Cols; x++)
+ // {
+ // double sum = 0.0;
+ //
+ // for (int kRow = 0; kRow < kernelRows; kRow++)
+ // {
+ // int yy = y + kRow - kernelCenterY;
+ //
+ // for (int kCol = 0; kCol < kernelCols; kCol++)
+ // {
+ // int xx = x + kCol - kernelCenterX;
+ //
+ // if (xx >= 0 && xx < src.Cols && yy >= 0 && yy < src.Rows)
+ // {
+ // byte pixelValue = src.At(yy, xx);
+ // double kernelValue = kernel.At(kRow, kCol);
+ // sum += Convert.ToDouble(pixelValue) * kernelValue;
+ // }
+ // }
+ // }
+ //
+ // byte resultValue = dst.At(y, x);
+ // resultValue = (byte)Math.Clamp(sum, 0, 255);
+ // dst.Set(y, x, resultValue);
+ // }
+ //}
+ unsafe
+ {
+ for (int y = 0; y < src.Rows; y++)
+ {
+ for (int x = 0; x < src.Cols; x++)
+ {
+ double sum = 0.0;
+
+ for (int kRow = 0; kRow < kernelRows; kRow++)
+ {
+ int yy = y + kRow - kernelCenterY;
+ for (int kCol = 0; kCol < kernelCols; kCol++)
+ {
+ int xx = x + kCol - kernelCenterX;
+
+ if (xx >= 0 && xx < src.Cols && yy >= 0 && yy < src.Rows)
+ {
+ byte* pixelValue = src.DataPointer + yy * src.Step() + xx * src.ElemSize();
+ double kernelValue = kernel.At(kRow, kCol);
+ sum += Convert.ToDouble(*pixelValue) * kernelValue;
+ }
+ }
+ }
+
+ byte* resultValue = dst.DataPointer + y * dst.Step() + x * dst.ElemSize();
+ *resultValue = (byte)Math.Clamp(sum, 0, 255);
+ }
+ }
+ }
+ }
+ catch(Exception ex)
+ {
+ String log = ex.Message;
+ }
+
+ return dst;
+ }
+
- using var dstMat = srcMat.Clone();
- Cv2.DrawKeypoints(srcMat, keypoints, dstMat);
- await dstCanvasClient.DrawMatAsync(dstMat);
}
}
\ No newline at end of file
diff --git a/BlazorApp/Shared/NavMenu.razor b/BlazorApp/Shared/NavMenu.razor
index bc44b33..71cc6d7 100644
--- a/BlazorApp/Shared/NavMenu.razor
+++ b/BlazorApp/Shared/NavMenu.razor
@@ -1,6 +1,6 @@
-
BlazorApp
+
PINBlog - GaborFilter
@@ -9,21 +9,25 @@
diff --git a/BlazorApp/wwwroot/images/gaborfilter.png b/BlazorApp/wwwroot/images/gaborfilter.png
new file mode 100644
index 0000000..0ac84c1
Binary files /dev/null and b/BlazorApp/wwwroot/images/gaborfilter.png differ
diff --git a/BlazorApp/wwwroot/images/gaborfilter2.png b/BlazorApp/wwwroot/images/gaborfilter2.png
new file mode 100644
index 0000000..61b4aa9
Binary files /dev/null and b/BlazorApp/wwwroot/images/gaborfilter2.png differ
diff --git a/BlazorApp/wwwroot/images/gaborfilter3.png b/BlazorApp/wwwroot/images/gaborfilter3.png
new file mode 100644
index 0000000..e829e74
Binary files /dev/null and b/BlazorApp/wwwroot/images/gaborfilter3.png differ
diff --git a/BlazorApp/wwwroot/images/lenna.bmp b/BlazorApp/wwwroot/images/lenna.bmp
new file mode 100644
index 0000000..acddbc8
Binary files /dev/null and b/BlazorApp/wwwroot/images/lenna.bmp differ
diff --git a/BlazorApp/wwwroot/images/lenna.jpg b/BlazorApp/wwwroot/images/lenna.jpg
new file mode 100644
index 0000000..51860d3
Binary files /dev/null and b/BlazorApp/wwwroot/images/lenna.jpg differ
diff --git a/BlazorApp/wwwroot/images/lenna.png b/BlazorApp/wwwroot/images/lenna.png
new file mode 100644
index 0000000..59ef68a
Binary files /dev/null and b/BlazorApp/wwwroot/images/lenna.png differ
diff --git a/OpenCvSharpBlazorSamples.sln b/OpenCvSharpBlazorSamples.sln
index 7f5429d..51eff6d 100644
--- a/OpenCvSharpBlazorSamples.sln
+++ b/OpenCvSharpBlazorSamples.sln
@@ -5,6 +5,18 @@ VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp", "BlazorApp\BlazorApp.csproj", "{AB0B9C16-C0A8-403C-A30D-50A7074D5935}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "솔루션 항목", "솔루션 항목", "{6E6DA0F0-CB0F-4C39-AE22-83865CAC0595}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{ABBA1859-0B75-4598-AC2E-B9DF1AE7A1B4}"
+ ProjectSection(SolutionItems) = preProject
+ images\gaborfilter.png = images\gaborfilter.png
+ images\gaborfilter2.png = images\gaborfilter2.png
+ images\gaborfilter3.png = images\gaborfilter3.png
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +55,9 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {ABBA1859-0B75-4598-AC2E-B9DF1AE7A1B4} = {6E6DA0F0-CB0F-4C39-AE22-83865CAC0595}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1AC6F14A-E5DF-40A4-AACE-6E3BC5E00CFC}
EndGlobalSection
diff --git a/README.md b/README.md
index f28b21c..797a5b3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
---
layout: default
title: 02. Gabor Filter
-subtitle: Deep Learning
+subtitle: Image Processing
---
-----