1+ using System ;
2+ using System . Data ;
3+ using System . Diagnostics ;
4+ using System . Diagnostics . CodeAnalysis ;
5+ using System . IO ;
6+ using System . Threading . Tasks ;
7+
8+ using SixLabors . ImageSharp ;
9+ using SixLabors . ImageSharp . PixelFormats ;
10+
11+ namespace ImageConvolution
12+ {
13+ [ ExcludeFromCodeCoverage ]
14+ class Program
15+ {
16+ static void Main ( string [ ] args )
17+ {
18+ Console . WriteLine ( "=== Программа свёртки изображений ===" ) ;
19+ Console . Write ( "Введите путь к изображению (перетащите файл в консоль): " ) ;
20+
21+ string ? inputPath = Console . ReadLine ( ) ? . Trim ( '\" ' , ' ' , '\' ' ) ;
22+
23+ if ( string . IsNullOrEmpty ( inputPath ) || ! File . Exists ( inputPath ) )
24+ {
25+ Console . WriteLine ( "Ошибка: Файл не найден! Проверьте путь и попробуйте снова." ) ;
26+ return ;
27+ }
28+
29+ string directory = Path . GetDirectoryName ( inputPath ) ?? "" ;
30+ string fileName = Path . GetFileNameWithoutExtension ( inputPath ) ;
31+ string extension = Path . GetExtension ( inputPath ) ;
32+ string outputPath = Path . Combine ( directory , fileName + "_filtered" + extension ) ;
33+
34+ try
35+ {
36+ Console . WriteLine ( "\n 1. Загрузка изображения..." ) ;
37+ double [ , ] image = ImageIO . LoadAsGrayscale ( inputPath ) ;
38+
39+ Console . WriteLine ( "2. Применение свёртки (Размытие / Blur)..." ) ;
40+ Stopwatch sw = Stopwatch . StartNew ( ) ;
41+
42+ double [ , ] result = ConvolutionProcessor . Convolve (
43+ image ,
44+ Kernels . BlurBox ,
45+ ConvolutionProcessor . EdgeStrategy . Extend ) ;
46+
47+ sw . Stop ( ) ;
48+ Console . WriteLine ( "2.1. Применение параллельной свёртки (Размытие / Blur)..." ) ;
49+ Stopwatch sw1 = Stopwatch . StartNew ( ) ;
50+
51+ double [ , ] result2 = ParallelConvolutionProcessor . ConvolveParallel (
52+ image ,
53+ Kernels . BlurBox ,
54+ ParallelConvolutionProcessor . EdgeStrategy . Extend ) ;
55+
56+ sw1 . Stop ( ) ;
57+
58+ Console . WriteLine ( $ "Свёртка завершена за { sw . ElapsedMilliseconds } мс") ;
59+
60+ Console . WriteLine ( $ "Параллельная свёртка завершена за { sw1 . ElapsedMilliseconds } мс") ;
61+
62+ Console . WriteLine ( "3. Сохранение результата..." ) ;
63+ ImageIO . SaveImage ( result , outputPath ) ;
64+
65+ Console . WriteLine ( $ "\n Готово! Изображение сохранено здесь:\n { outputPath } ") ;
66+ }
67+ catch ( Exception ex )
68+ {
69+ Console . WriteLine ( $ "Произошла ошибка: { ex . Message } ") ;
70+ }
71+ }
72+ }
73+
74+ public class ImageIO
75+ {
76+ public static double [ , ] LoadAsGrayscale ( string path )
77+ {
78+ using Image < Rgba32 > image = Image . Load < Rgba32 > ( path ) ;
79+ int width = image . Width ;
80+ int height = image . Height ;
81+ double [ , ] result = new double [ height , width ] ;
82+
83+ for ( int y = 0 ; y < height ; y ++ )
84+ {
85+ for ( int x = 0 ; x < width ; x ++ )
86+ {
87+ Rgba32 pixel = image [ x , y ] ;
88+ double gray = 0.299 * pixel . R + 0.587 * pixel . G + 0.114 * pixel . B ;
89+ result [ y , x ] = gray ;
90+ }
91+ }
92+ return result ;
93+ }
94+ public static void SaveImage ( double [ , ] data , string path )
95+ {
96+ int height = data . GetLength ( 0 ) ;
97+ int width = data . GetLength ( 1 ) ;
98+
99+ using Image < Rgba32 > image = new Image < Rgba32 > ( width , height ) ;
100+ for ( int y = 0 ; y < height ; y ++ )
101+ {
102+ for ( int x = 0 ; x < width ; x ++ )
103+ {
104+ double val = data [ y , x ] ;
105+ byte gray = ( byte ) Math . Clamp ( val , 0 , 255 ) ;
106+ image [ x , y ] = new Rgba32 ( gray , gray , gray ) ;
107+ }
108+ }
109+ image . Save ( path ) ;
110+ }
111+ }
112+
113+ public class Kernels
114+ {
115+ public static double [ , ] Sharpen = new double [ , ]
116+ {
117+ { 0 , - 1 , 0 } ,
118+ { - 1 , 5 , - 1 } ,
119+ { 0 , - 1 , 0 }
120+ } ;
121+ public static double [ , ] Laplacian = new double [ , ]
122+ {
123+ { - 1 , - 1 , - 1 } ,
124+ { - 1 , 8 , - 1 } ,
125+ { - 1 , - 1 , - 1 }
126+ } ;
127+ public static double [ , ] BlurBox = new double [ , ]
128+ {
129+ { 1.0 / 9.0 , 1.0 / 9.0 , 1.0 / 9.0 } ,
130+ { 1.0 / 9.0 , 1.0 / 9.0 , 1.0 / 9.0 } ,
131+ { 1.0 / 9.0 , 1.0 / 9.0 , 1.0 / 9.0 }
132+ } ;
133+ }
134+
135+ public class ConvolutionProcessor
136+ {
137+ public enum EdgeStrategy
138+ {
139+ Extend ,
140+ ZeroPadding
141+ }
142+
143+ public static double [ , ] Convolve ( double [ , ] image , double [ , ] kernel , EdgeStrategy strategy = EdgeStrategy . Extend )
144+ {
145+ int imgheight = image . GetLength ( 0 ) ;
146+ int imgwidth = image . GetLength ( 1 ) ;
147+ int kheight = kernel . GetLength ( 0 ) ;
148+ int kwidth = kernel . GetLength ( 1 ) ;
149+
150+ int offsetY = kheight / 2 ;
151+ int offsetX = kwidth / 2 ;
152+
153+ double [ , ] result = new double [ imgheight , imgwidth ] ;
154+
155+ for ( int y = 0 ; y < imgheight ; y ++ )
156+ {
157+ for ( int x = 0 ; x < imgwidth ; x ++ )
158+ {
159+ double sum = 0.0 ;
160+
161+ for ( int ky = 0 ; ky < kheight ; ky ++ )
162+ {
163+ for ( int kx = 0 ; kx < kwidth ; kx ++ )
164+ {
165+ int pixelY = y + ky - offsetY ;
166+ int pixelX = x + kx - offsetX ;
167+ double pixelValue = 0.0 ;
168+
169+ if ( strategy == EdgeStrategy . Extend )
170+ {
171+ if ( pixelY < 0 ) pixelY = 0 ;
172+ if ( pixelY >= imgheight ) pixelY = imgheight - 1 ;
173+ if ( pixelX < 0 ) pixelX = 0 ;
174+ if ( pixelX >= imgwidth ) pixelX = imgwidth - 1 ;
175+
176+ pixelValue = image [ pixelY , pixelX ] ;
177+ }
178+ else if ( strategy == EdgeStrategy . ZeroPadding )
179+ {
180+ if ( pixelY >= 0 && pixelY < imgheight && pixelX >= 0 && pixelX < imgwidth )
181+ {
182+ pixelValue = image [ pixelY , pixelX ] ;
183+ }
184+ }
185+ sum += pixelValue * kernel [ ky , kx ] ;
186+ }
187+ }
188+
189+ result [ y , x ] = sum ;
190+ }
191+ }
192+ return result ;
193+ }
194+ }
195+ public class ParallelConvolutionProcessor
196+ {
197+ public enum EdgeStrategy
198+ {
199+ Extend ,
200+ ZeroPadding
201+ }
202+
203+ public static double [ , ] ConvolveParallel ( double [ , ] image , double [ , ] kernal , EdgeStrategy strategy = EdgeStrategy . Extend )
204+ {
205+ int imgwidth = image . GetLength ( 1 ) ;
206+ int imgheight = image . GetLength ( 0 ) ;
207+ int kwidth = kernal . GetLength ( 0 ) ;
208+ int kheight = kernal . GetLength ( 1 ) ;
209+
210+ int offsetX = kwidth / 2 ;
211+ int offsetY = kheight / 2 ;
212+
213+ double [ , ] result2 = new double [ imgheight , imgwidth ] ;
214+ Parallel . For ( 0 , imgheight , y =>
215+ {
216+ for ( int x = 0 ; x < imgwidth ; x ++ )
217+ {
218+ double sum = 0.0 ;
219+
220+ for ( int ky = 0 ; ky < kheight ; ky ++ )
221+ {
222+ for ( int kx = 0 ; kx < kwidth ; kx ++ )
223+ {
224+ int pixelY = y + ky - offsetY ;
225+ int pixelX = x + kx - offsetX ;
226+ double pixelValue = 0.0 ;
227+
228+ if ( strategy == EdgeStrategy . Extend )
229+ {
230+ if ( pixelY < 0 ) pixelY = 0 ;
231+ if ( pixelY >= imgheight ) pixelY = imgheight - 1 ;
232+ if ( pixelX < 0 ) pixelX = 0 ;
233+ if ( pixelX >= imgwidth ) pixelX = imgwidth - 1 ;
234+
235+ pixelValue = image [ pixelY , pixelX ] ;
236+ }
237+ else if ( strategy == EdgeStrategy . ZeroPadding )
238+ {
239+ if ( pixelY >= 0 && pixelY < imgheight && pixelX >= 0 && pixelX < imgwidth )
240+ {
241+ pixelValue = image [ pixelY , pixelX ] ;
242+ }
243+ }
244+ sum += pixelValue * kernal [ ky , kx ] ;
245+ }
246+ }
247+ result2 [ y , x ] = sum ;
248+ }
249+ } ) ;
250+ return result2 ;
251+ }
252+ }
253+ }
0 commit comments