main
pinb 1 year ago
parent e065a401b8
commit da3d6d433c

@ -19,8 +19,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.1" PrivateAssets="all" />
<PackageReference Include="OpenCvSharp4" Version="4.5.5.20211231" />
<PackageReference Include="OpenCvSharp4.runtime.wasm" Version="4.5.5.20211231" />
<PackageReference Include="OpenCvSharp4" Version="4.5.3.20211207" />
<PackageReference Include="OpenCvSharp4.runtime.wasm" Version="4.5.3.20211207" />
</ItemGroup>
</Project>

@ -2,12 +2,20 @@
@using OpenCvSharp
<PageTitle>Index</PageTitle>
<PageTitle>PINBlog</PageTitle>
<h1>Hello, world!</h1>
<h1>Hello, PINblog!</h1>
Welcome to your new app.
Welcome to PINBlog's Gabor Filter Test Page'.
<pre>
⬅️ 왼쪽의 GaborFilter Test 탭을 클릭하면 해당 페이지로 이동합니다. 🤗🤗
</pre>
<!--
<pre>@OpenCvSharp.Cv2.GetBuildInformation()</pre>
<SurveyPrompt Title="How is Blazor working for you?" />
<SurveyPrompt Title="CBNU - PINBlog - Gabor Filter Test" />
-->

@ -4,10 +4,85 @@
@inject HttpClient httpClient;
@implements IDisposable
<PageTitle>OpenCvSharp Sample</PageTitle>
<h1>OpenCvSharp on WebAssembly</h1>
<PageTitle>Gabor Filter Test</PageTitle>
<div>
<h1 id="-gabor-filter-"><strong>Gabor Filter란?</strong></h1><br />
<p>Gabor Filter는 영상처리에서 Bio-inspired라는 키워드가 있으면 빠지지않고 등장한다.</p>
<p>외곽선을 검출하는 기능을 하는 필터로, 사람의 시각체계가 반응하는 것과 비슷하다는 이유로 널리 사용되고 있다.</p>
<p>Gabor Fiter는 간단히 말해서 사인 함수로 모듈레이션 된 Gaussian Filter라고 생각할 수 있다.</p>
<p>파라미터를 조절함에 따라 Edge의 크기나 방향성을 바꿀 수 있으므로 Bio-inspired 영상처리 알고리즘에서 특징점 추출 알고리즘으로 핵심적인 역할을 하고 있다.</p>
<p>2D Gabor Filter의 수식은 아래와 같다.</p>
<p>
<img src="images/gaborfilter.png" alt="gaborfilter"><br />
<img src="images/gaborfilter2.png" alt="gaborfilter"><br />
<img src="images/gaborfilter3.png" alt="gaborfilter"><br />
</p><br /><br />
<h3 id="-parameters-"><strong>함수 원형</strong></h3>
<pre style ="background-color: #f8f8f8;">
<code class="lang-cpp">
cv::Mat cv::getGaborKernel(cv::Size ksize, <span class="hljs-keyword">double</span> sigma, <span class="hljs-keyword">double</span> theta, <span class="hljs-keyword">double</span> lambd, <span class="hljs-keyword">double</span> gamma, <span class="hljs-keyword">double</span> psi = CV_PI*<span class="hljs-number">0.5</span>, <span class="hljs-keyword">int</span> ktype = CV_64F)
</code>
</pre><br />
<p>cv::getGaborKernel 함수는 OpenCV에서 가버필터(Gabor filter)를 생성하는 데 사용된다.</p>
<p>가버필터는 이미지 처리와 컴퓨터 비전에서 특정 방향성과 주파수의 특징을 강조하는 데 사용되는 선형 필터이다. </p><br /><br />
<h3 id="-parameters-"><strong>Parameters</strong></h3><br />
<ul>
<li>
<p>ksize: 커널의 크기로, cv::Size 타입. 커널의 너비와 높이를 지정.</p>
</li>
<li>
<p>sigma: 가우시안 함수의 표준 편차. 값이 커질수록 커널의 크기가 커지고, 필터의 감도가 낮아진다.</p>
</li>
<li>
<p>theta: 필터의 방향을 라디안 단위로 지정한다. 0은 수평 방향, CV_PI/2는 수직 방향을 의미한다.</p>
</li>
<li>
<p>lambd: 가버필터의 파장. 이미지의 특정 패턴과 얼마나 잘 매치할지를 결정한다.</p>
</li>
<li>
<p>gamma: 공간종횡비(Spatial aspect ratio)로, 타원형 가버 함수의 타원성을 결정한다. (gamma=1은 원형, gamma&lt;1은 타원형)</p>
</li>
<li>
<p>psi: 위상변위(Phase offset)로, 가버 필터의 위상을 조절한다. (일반적으로 CV_PI*0.5가 기본값으로 사용)</p>
</li>
<li>
<p>ktype: 커널 타입. (보통 CV_64F (64-비트 부동소수점)를 사용)</p><br />
</li>
</ul>
</div>
<br />
<div>
<h3 id="-parameters-"><strong>Gabor Filter 실행 (속도 느림 주의)</strong></h3>
<canvas @ref="srcCanvas2" width="256" height="256" style="border:1px solid gray;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<canvas @ref="dstCanvas2" width="256" height="256" style="border:1px solid gray;">
Your browser does not support the HTML5 canvas tag.
</canvas>
</div>
<div>
<p>Kernel Size(0 이상 홀수)</p>
<input type="range" min="3" max="127" step="2" @bind="@valKernelSize" @bind:event="oninput" />
<input type="text" @bind="@valKernelSize" /><br />
<p>Sigma (0.0 이상)</p>
<input type="range" min="0" max="1000" step="0.01" @bind="@valSigma" @bind:event="oninput" />
<input type="text" @bind="@valSigma" /><br />
<p>Theta (0도 ~ 180도)</p>
<input type="range" min="0" max="180" step="0.01" @bind="@valTheta" @bind:event="oninput" />
<input type="text" @bind="@valTheta" /><br />
<p>Lambda (0.0 이상 이미지 사이즈 미만(256.0))</p>
<input type="range" min="0" max="255" step="0.01" @bind="@valLambd" @bind:event="oninput" />
<input type="text" @bind="@valLambd" /><br />
<p>Gamma (0.0 ~ 1.0)</p>
<input type="range" min="0.0" max="1.0" step="0.01" @bind="@valGamma" @bind:event="oninput" />
<input type="text" @bind="@valGamma" /><br />
<p>Psi (0도 ~ 360도)</p>
<input type="range" min="0.0" max="360" step="0.01" @bind="@valPsi" @bind:event="oninput" />
<input type="text" @bind="@valPsi" /><br />
<button @onclick="@(async () => await GaborFilter())">GaborFilter</button>
</div>
<br />
<div>
<canvas @ref="srcCanvas" width="256" height="256" style="border:1px solid gray;">
Your browser does not support the HTML5 canvas tag.
@ -17,23 +92,38 @@
</canvas>
</div>
<div>
<p>Threshold (0 ~ 255)</p>
<input type="range" min="0" max="255" step="1" @bind="@valthreshold" @bind:event="oninput" />
<input type="text" @bind="@valthreshold" /><br />
<button @onclick="@(async () => await Grayscale())">Grayscale</button>
<button @onclick="@(async () => await PseudoColor())">PseudoColor</button>
<button @onclick="@(async () => await Threshold())" style="color: darkgray;">Threshold</button>
<button @onclick="@(async () => await Canny())" style="color: darkgray;">Canny</button>
<button @onclick="@(async () => await Akaze())" style="color: darkgray;">AKAZE</button>
<button @onclick="@(async () => await Threshold())">Threshold</button>
</div>
@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<grayMat.Rows; i++)
{
for(int j=0; j<grayMat.Cols; j++)
{
byte pixelValue = grayMat.At<byte>(i, j);
byte newValue = (Convert.ToInt32(pixelValue) > valthreshold) ? (byte)255 : (byte)0;
dstMat.Set<byte>(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<float>(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<byte>(yy, xx);
// double kernelValue = kernel.At<double>(kRow, kCol);
// sum += Convert.ToDouble(pixelValue) * kernelValue;
// }
// }
// }
//
// byte resultValue = dst.At<byte>(y, x);
// resultValue = (byte)Math.Clamp(sum, 0, 255);
// dst.Set<byte>(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<double>(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);
}
}

@ -1,6 +1,6 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">BlazorApp</a>
<a class="navbar-brand" href="">PINBlog - GaborFilter</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
@ -9,21 +9,25 @@
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<!--
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
-->
<div class="nav-item px-3">
<NavLink class="nav-link" href="opencvsharp_sample">
<span class="oi oi-map" aria-hidden="true"></span> OpenCvSharp Sample
<span class="oi oi-map" aria-hidden="true"></span> GaborFilter Test
</NavLink>
</div>
<!--
<div class="nav-item px-3">
<NavLink class="nav-link" href="canvas">
<span class="oi oi-map" aria-hidden="true"></span> Canvas Sample
</NavLink>
</div>
-->
</nav>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

@ -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

@ -1,7 +1,7 @@
---
layout: default
title: 02. Gabor Filter
subtitle: Deep Learning
subtitle: Image Processing
---
-----

Loading…
Cancel
Save