Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,9 @@ $RECYCLE.BIN/

# Vim temporary swap files
*.swp


ImageConvolution/Agent_Processed_Output/
ImageConvolution/Processed_Output_Par/
ImageConvolution/Processed_Output_Seq/
ImageConvolution/Processed_Output_*/
82 changes: 82 additions & 0 deletions ImageConvolution.Tests/ConvolutionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.IO;

using ImageConvolution;

using Xunit;

namespace ImageConvolution.Tests;

public class ConvolutionTests
{
[Fact]
public void Test_ImageIO_SaveAndLoad()
{
string testPath = "test_image.png";
double[,] originalData = new double[2, 2] { { 0, 255 }, { 128, 64 } };

try
{
ImageIO.SaveImage(originalData, testPath);
Assert.True(File.Exists(testPath));

double[,] loadedData = ImageIO.LoadAsGrayscale(testPath);

Assert.Equal(2, loadedData.GetLength(0));
Assert.Equal(2, loadedData.GetLength(1));
}
finally
{
if (File.Exists(testPath)) File.Delete(testPath);
}
}

[Fact]
public void Test_Convolve_ExtendStrategy()
{
double[,] image = { { 10, 10, 10 }, { 10, 10, 10 }, { 10, 10, 10 } };
double[,] result = ConvolutionProcessor.Convolve(image, Kernels.BlurBox, EdgeStrategy.Extend);
Assert.Equal(10, Math.Round(result[1, 1]));
}

[Fact]
public void Test_Convolve_ZeroPaddingStrategy()
{
double[,] image = { { 10, 10, 10 }, { 10, 10, 10 }, { 10, 10, 10 } };
double[,] result = ConvolutionProcessor.Convolve(image, Kernels.BlurBox, EdgeStrategy.ZeroPadding);
Assert.True(result[0, 0] < 10);
}

[Fact]
public void Test_ConvolveParallel()
{
double[,] image = { { 100, 100, 100 }, { 100, 100, 100 }, { 100, 100, 100 } };
double[,] result = ParallelConvolutionProcessor.ConvolveParallel(image, Kernels.BlurBox, EdgeStrategy.Extend);
Assert.Equal(100, Math.Round(result[1, 1]));
}

[Fact]
public void Test_KernelsExist()
{
Assert.NotNull(Kernels.BlurBox);
Assert.NotNull(Kernels.Sharpen);
Assert.NotNull(Kernels.Laplacian);
}

[Fact]
public void Test_ConvolveParallel_ZeroPadding()
{
double[,] image = {
{ 10, 10, 10 },
{ 10, 10, 10 },
{ 10, 10, 10 }
};

double[,] result = ParallelConvolutionProcessor.ConvolveParallel(
image,
Kernels.BlurBox,
EdgeStrategy.ZeroPadding);

Assert.True(result[0, 0] < 10);
}
}
29 changes: 29 additions & 0 deletions ImageConvolution.Tests/ImageConvolution.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="coverlet.msbuild" Version="8.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ImageConvolution\ImageConvolution.csproj" />
</ItemGroup>

</Project>
14 changes: 14 additions & 0 deletions ImageConvolution/ImageConvolution.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
</ItemGroup>

</Project>
214 changes: 214 additions & 0 deletions ImageConvolution/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using System;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

namespace ImageConvolution
{
public enum EdgeStrategy
{
Extend,
ZeroPadding
}

[ExcludeFromCodeCoverage]
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Программа свёртки изображений ===");
Console.Write("Введите путь к изображению (перетащите файл в консоль): ");

string? inputPath = Console.ReadLine()?.Trim('\"', ' ', '\'');

if (string.IsNullOrEmpty(inputPath) || !File.Exists(inputPath))
{
Console.WriteLine("Ошибка: Файл не найден! Проверьте путь и попробуйте снова.");
return;
}

string directory = Path.GetDirectoryName(inputPath) ?? "";
string fileName = Path.GetFileNameWithoutExtension(inputPath);
string extension = Path.GetExtension(inputPath);
string outputPath = Path.Combine(directory, fileName + "_filtered" + extension);

try
{
Console.WriteLine("\n1. Загрузка изображения...");
double[,] image = ImageIO.LoadAsGrayscale(inputPath);

Console.WriteLine("2. Применение свёртки (Размытие / Blur)...");
Stopwatch sw = Stopwatch.StartNew();

double[,] result = ConvolutionProcessor.Convolve(
image,
Kernels.BlurBox,
EdgeStrategy.Extend);

sw.Stop();
Console.WriteLine("2.1. Применение параллельной свёртки (Размытие / Blur)...");
Stopwatch sw1 = Stopwatch.StartNew();

double[,] result2 = ParallelConvolutionProcessor.ConvolveParallel(
image,
Kernels.BlurBox,
EdgeStrategy.Extend);

sw1.Stop();

Console.WriteLine($"Свёртка завершена за {sw.ElapsedMilliseconds} мс");

Console.WriteLine($"Параллельная свёртка завершена за {sw1.ElapsedMilliseconds} мс");

Console.WriteLine("3. Сохранение результата...");
ImageIO.SaveImage(result, outputPath);

Console.WriteLine($"\nГотово! Изображение сохранено здесь:\n{outputPath}");
}
catch (Exception ex)
{
Console.WriteLine($"Произошла ошибка: {ex.Message}");
}
}
}

public class ImageIO
{
public static double[,] LoadAsGrayscale(string path)
{
using Image<Rgba32> image = Image.Load<Rgba32>(path);
int width = image.Width;
int height = image.Height;
double[,] result = new double[height, width];

for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Rgba32 pixel = image[x, y];
double gray = 0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B;
result[y, x] = gray;
}
}
return result;
}
public static void SaveImage(double[,] data, string path)
{
int height = data.GetLength(0);
int width = data.GetLength(1);

using Image<Rgba32> image = new Image<Rgba32>(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double val = data[y, x];
byte gray = (byte)Math.Clamp(val, 0, 255);
image[x, y] = new Rgba32(gray, gray, gray);
}
}
image.Save(path);
}
}

public class Kernels
{
public static double[,] Sharpen = new double[,]
{
{ 0, -1, 0},
{ -1, 5, -1},
{ 0, -1, 0}
};
public static double[,] Laplacian = new double[,]
{
{-1, -1, -1},
{-1, 8, -1},
{-1, -1, -1}
};
public static double[,] BlurBox = new double[,]
{
{1.0/9.0, 1.0/9.0, 1.0/9.0},
{1.0/9.0, 1.0/9.0, 1.0/9.0},
{1.0/9.0, 1.0/9.0, 1.0/9.0}
};
}

public class ConvolutionProcessor
{
internal static double CalculatePixelValue(double[,] image, double[,] kernel, int x, int y, EdgeStrategy strategy)
{
double sum = 0.0;
int imgheight = image.GetLength(0);
int imgwidth = image.GetLength(1);
int kheight = kernel.GetLength(0);
int kwidth = kernel.GetLength(1);
int offsetY = kheight / 2;
int offsetX = kwidth / 2;

for (int ky = 0; ky < kheight; ky++)
{
for (int kx = 0; kx < kwidth; kx++)
{
int pixelY = y + ky - offsetY;
int pixelX = x + kx - offsetX;
double pixelValue = 0.0;

if (strategy == EdgeStrategy.Extend)
{
pixelY = Math.Clamp(pixelY, 0, imgheight - 1);
pixelX = Math.Clamp(pixelX, 0, imgwidth - 1);
pixelValue = image[pixelY, pixelX];
}
else if (strategy == EdgeStrategy.ZeroPadding)
{
if (pixelY >= 0 && pixelY < imgheight && pixelX >= 0 && pixelX < imgwidth)
{
pixelValue = image[pixelY, pixelX];
}
}
sum += pixelValue * kernel[ky, kx];
}
}
return sum;
}

public static double[,] Convolve(double[,] image, double[,] kernel, EdgeStrategy strategy = EdgeStrategy.Extend)
{
int imgheight = image.GetLength(0);
int imgwidth = image.GetLength(1);
double[,] result = new double[imgheight, imgwidth];
for (int y = 0; y < imgheight; y++)
{
for (int x = 0; x < imgwidth; x++)
{
result[y, x] = CalculatePixelValue(image, kernel, x, y, strategy);
}
}
return result;
}
}
public class ParallelConvolutionProcessor
{

public static double[,] ConvolveParallel(double[,] image, double[,] kernal, EdgeStrategy strategy = EdgeStrategy.Extend)
{
int imgheight = image.GetLength(0);
int imgwidth = image.GetLength(1);
double[,] result = new double[imgheight, imgwidth];

Parallel.For(0, imgheight, y =>
{
for (int x = 0; x < imgwidth; x++)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно ли переиспользовать тело цикла?

{
result[y, x] = ConvolutionProcessor.CalculatePixelValue(image, kernal, x, y, strategy);
}
});
return result;
}
}
}