|
|
|
@ -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<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);
|
|
|
|
|
}
|
|
|
|
|
}
|