From 503b4a59f01f0331517413cf8d924236363d8f18 Mon Sep 17 00:00:00 2001 From: Redstone OLD <3069884909@qq.com> Date: Mon, 17 Nov 2025 10:32:51 +0800 Subject: [PATCH] 1117 --- .../ImageUtils.cs | 29 ++ DIgitalImageProcessingImageSharp/Program.cs | 53 +-- .../Segmentation.cs | 444 ++++++++++++++++++ DigitalImageProcessing/GLobalTools.cs | 115 +++-- DigitalImageProcessing/Program.cs | 2 +- DigitalImageProcessing/Segmentation.cs | 37 ++ 6 files changed, 619 insertions(+), 61 deletions(-) create mode 100644 DIgitalImageProcessingImageSharp/ImageUtils.cs create mode 100644 DIgitalImageProcessingImageSharp/Segmentation.cs create mode 100644 DigitalImageProcessing/Segmentation.cs diff --git a/DIgitalImageProcessingImageSharp/ImageUtils.cs b/DIgitalImageProcessingImageSharp/ImageUtils.cs new file mode 100644 index 0000000..2b6837f --- /dev/null +++ b/DIgitalImageProcessingImageSharp/ImageUtils.cs @@ -0,0 +1,29 @@ +using OpenCvSharp; + +public static class ImageUtils +{ + public static bool TryRead(string path, out Mat mat) + { + mat = Cv2.ImRead(path, ImreadModes.Color); + return !mat.Empty(); + } + + public static void Save(Mat mat, string path) + { + mat.ImWrite(path); + } + + // Apply mask to image: where mask==0 set to black + public static Mat ApplyMask(Mat src, Mat mask) + { + Mat dst = new Mat(); + if (mask.Type() != MatType.CV_8U) + { + Mat tmp = new(); + mask.ConvertTo(tmp, MatType.CV_8U); + mask = tmp; + } + Cv2.BitwiseAnd(src, src, dst, mask); + return dst; + } +} diff --git a/DIgitalImageProcessingImageSharp/Program.cs b/DIgitalImageProcessingImageSharp/Program.cs index 6d79857..8c61dd6 100644 --- a/DIgitalImageProcessingImageSharp/Program.cs +++ b/DIgitalImageProcessingImageSharp/Program.cs @@ -1,40 +1,33 @@ using OpenCvSharp; -using System.Xml.Serialization; -//string filepath = (@"C:\Users\Surface\Downloads\" + @"1682575881230001.png"); +string[] testFiles = [ + @"C:\Users\Surface\Downloads\1682575881230001.png", // 风景图 + @"C:\Users\Surface\Downloads\Sprite-0001.png", // 二值图 + @"C:\Users\Surface\Downloads\R-C.jfif", // 街道图 + @"C:\Users\Surface\Downloads\OIP.webp" // 指纹图 + ]; +string filepath = testFiles[3]; -int year = 2025; -bool isLeap1 = DateTime.IsLeapYear(year); -bool isLeap2 = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); -bool isLeap3 = year switch +if (!ImageUtils.TryRead(filepath, out Mat image)) { - _ when year % 400 == 0 => true, - _ when year % 100 == 0 => false, - _ when year % 4 == 0 => true, - _ => false, -}; -bool isLeap4 = year % 4 == 0 ? (year % 100 == 0 ? (year % 400 == 0 ? true : false) : true) : false; -bool isLeap5 = year % 4 != 0 ? false : year % 100 != 0 ? true : year % 400 == 0 ? true : false; + Console.WriteLine("Failed to read image: " + filepath); + return; +} -Console.WriteLine(isLeap1); -Console.WriteLine(isLeap2); -Console.WriteLine(isLeap3); -Console.WriteLine(isLeap4); -Console.WriteLine(isLeap5); -string filepath = (@"C:\Users\Surface\Downloads\" + @"sprite-0001.png"); -Mat image = Cv2.ImRead(filepath); -InputArray kernel = InputArray.Create(new int[,] { - { 0, 1, 0 }, - { 1, 1, 1 }, - { 0, 1, 0 } -}, MatType.CV_8U); -Mat dst = new(); -//Cv2.Filter2D(image, dst, image.Depth(), kernel); +int thresholdValue = 128; +int thresholdDelta = 30; -Cv2.Dilate(image, dst, kernel); +Mat gray = new(); +Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY); -dst = dst - image; +Mat img1 = new(), img2 = new(); +float delta = float.MaxValue; +int loopCount = 0; -dst.ImWrite(@"C:\Users\Surface\Downloads\" + @"filtered_opencv.png"); \ No newline at end of file +while(delta > thresholdValue) +{ + //Cv2.Threshold(gray, img1, thresholdValue, 255, ThresholdTypes.Binary); + +} \ No newline at end of file diff --git a/DIgitalImageProcessingImageSharp/Segmentation.cs b/DIgitalImageProcessingImageSharp/Segmentation.cs new file mode 100644 index 0000000..3b8636b --- /dev/null +++ b/DIgitalImageProcessingImageSharp/Segmentation.cs @@ -0,0 +1,444 @@ +using OpenCvSharp; + +public static class Segmentation +{ + // Otsu thresholding workflow: convert to gray, blur, Otsu threshold + public static Mat OtsuThreshold(Mat src) + { + Mat gray = new(); + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + Mat blur = new(); + Cv2.GaussianBlur(gray, blur, new Size(5, 5), 0); + Mat mask = new(); + Cv2.Threshold(blur, mask, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); + return mask; + } + + // K-means color segmentation + public static Mat KMeansSegmentation(Mat src, int k = 2, int attempts = 5) + { + // Reshape to a 2D samples matrix where each row is a pixel and columns are channels + Mat samples = src.Reshape(1, src.Rows * src.Cols); + Mat samples32f = new(); + samples.ConvertTo(samples32f, MatType.CV_32F); + + Mat labels = new(); + TermCriteria criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 10, 1.0); + Mat centers = new(); + + Cv2.Kmeans(samples32f, k, labels, criteria, attempts, KMeansFlags.PpCenters, centers); + + // Build segmented image by mapping each pixel to its cluster center color + Mat result = new Mat(src.Rows, src.Cols, src.Type()); + int cols = src.Cols; + for (int r = 0; r < src.Rows; r++) + { + for (int c = 0; c < cols; c++) + { + int idx = r * cols + c; + int clusterIdx = labels.At(idx, 0); + byte b = (byte)Clamp(centers.At(clusterIdx, 0)); + byte g = (byte)Clamp(centers.At(clusterIdx, 1)); + byte rcol = (byte)Clamp(centers.At(clusterIdx, 2)); + result.Set(r, c, new Vec3b(b, g, rcol)); + } + } + + return result; + } + + // Simple morphological cleaning (opening then closing) + public static Mat MorphologicalClean(Mat mask, int kernelSize = 3) + { + Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(kernelSize, kernelSize)); + Mat opened = new(); + Cv2.MorphologyEx(mask, opened, MorphTypes.Open, kernel); + Mat cleaned = new(); + Cv2.MorphologyEx(opened, cleaned, MorphTypes.Close, kernel); + return cleaned; + } + + // Create a binary mask by HSV range. lower and upper are HSV in OpenCV order: H:0-180, S:0-255, V:0-255 + public static Mat ColorRangeHSV(Mat src, Scalar lowerHsv, Scalar upperHsv) + { + Mat hsv = new(); + Cv2.CvtColor(src, hsv, ColorConversionCodes.BGR2HSV); + Mat mask = new(); + Cv2.InRange(hsv, lowerHsv, upperHsv, mask); + return mask; + } + + // Create a binary mask by Euclidean distance in color space to a target color. + // If useLab is true, convert BGR->Lab before distance to get perceptual distance. + // targetColor is Scalar(b, g, r). thresh is distance threshold. + public static Mat ColorDistanceMask(Mat src, Scalar targetColor, double thresh, bool useLab = false) + { + Mat working = new(); + if (useLab) + { + Cv2.CvtColor(src, working, ColorConversionCodes.BGR2Lab); + } + else + { + src.CopyTo(working); + } + + Mat f32 = new(); + working.ConvertTo(f32, MatType.CV_32F); + + // Prepare a Mat filled with the target color (in same color space) + Mat target = new Mat(f32.Size(), MatType.CV_32FC3, new Scalar(targetColor.Val0, targetColor.Val1, targetColor.Val2)); + + Mat diff = new(); + Cv2.Subtract(f32, target, diff); + + Mat sq = new(); + Cv2.Multiply(diff, diff, sq); + + // sum channels + Mat[] ch = Cv2.Split(sq); + Mat sum = new(); + Cv2.Add(ch[0], ch[1], sum); + Cv2.Add(sum, ch[2], sum); + + Mat dist = new(); + Cv2.Sqrt(sum, dist); + + Mat mask = new(); + // pixels with distance <= thresh -> 255 in mask + Cv2.Threshold(dist, mask, thresh, 255, ThresholdTypes.BinaryInv); + + // ensure mask is CV_8U + if (mask.Type() != MatType.CV_8U) + { + Mat tmp = new(); + mask.ConvertTo(tmp, MatType.CV_8U); + return tmp; + } + + return mask; + } + + // Canny edge based segmentation. + // Returns a binary edge mask (0/255). Parameters threshold1 and threshold2 are the Canny low/high thresholds. + // If makeRegions is true, the function will invert edges and apply closing to produce coarse region masks. + public static Mat CannySegmentation(Mat src, double threshold1 = 100.0, double threshold2 = 200.0, + bool blur = true, int blurKernel = 5, int apertureSize = 3, bool L2gradient = false, + bool makeRegions = false, int morphKernel = 3) + { + Mat gray = new(); + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + + if (blur) + { + Cv2.GaussianBlur(gray, gray, new Size(blurKernel, blurKernel), 0); + } + + Mat edges = new(); + Cv2.Canny(gray, edges, threshold1, threshold2, apertureSize, L2gradient); + + if (!makeRegions) + { + return edges; + } + + // Convert edge map to coarse regions by inverting edges and closing gaps + Mat inv = new(); + Cv2.BitwiseNot(edges, inv); + + Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(morphKernel, morphKernel)); + Mat closed = new(); + Cv2.MorphologyEx(inv, closed, MorphTypes.Close, kernel); + + // Optional: threshold to ensure binary + Mat mask = new(); + Cv2.Threshold(closed, mask, 128, 255, ThresholdTypes.Binary); + return mask; + } + + // Sobel gradient magnitude thresholding segmentation. + // thresh is the threshold applied to gradient magnitude (0-255 after normalization). + public static Mat SobelMagnitudeSegmentation(Mat src, double thresh = 50.0, bool blur = true, int blurKernel = 3) + { + Mat gray = new(); + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + + if (blur) + { + Cv2.GaussianBlur(gray, gray, new Size(blurKernel, blurKernel), 0); + } + + Mat gx = new(); + Mat gy = new(); + Cv2.Sobel(gray, gx, MatType.CV_32F, 1, 0, ksize: 3); + Cv2.Sobel(gray, gy, MatType.CV_32F, 0, 1, ksize: 3); + + Mat mag = new(); + Cv2.Magnitude(gx, gy, mag); + + // Normalize magnitude to 0-255 + Mat mag8u = new(); + Cv2.Normalize(mag, mag, 0, 255, NormTypes.MinMax); + mag.ConvertTo(mag8u, MatType.CV_8U); + + Mat mask = new(); + Cv2.Threshold(mag8u, mask, thresh, 255, ThresholdTypes.Binary); + return mask; + } + + // --- Second-derivative (Laplacian) based methods --- + + // Simple Laplacian magnitude thresholding. + // ksize: aperture size for the Laplacian operator (1,3,5...). thresh: threshold on normalized magnitude (0-255). + public static Mat LaplacianSegmentation(Mat src, int ksize = 3, double thresh = 30.0, bool blur = true, int blurKernel = 3) + { + Mat gray = new(); + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + + if (blur) + { + Cv2.GaussianBlur(gray, gray, new Size(blurKernel, blurKernel), 0); + } + + Mat lap = new(); + Cv2.Laplacian(gray, lap, MatType.CV_32F, ksize: ksize); + + Mat absLap = new(); + absLap = Cv2.Abs(lap); + + Mat norm = new(); + Cv2.Normalize(absLap, norm, 0, 255, NormTypes.MinMax); + + Mat mask = new(); + norm.ConvertTo(norm, MatType.CV_8U); + Cv2.Threshold(norm, mask, thresh, 255, ThresholdTypes.Binary); + return mask; + } + + // Laplacian of Gaussian: blur with Gaussian (sigma), then Laplacian, then detect zero-crossings. + // zeroCrossThreshold is applied to absolute Laplacian magnitude to avoid weak crossings caused by noise. + public static Mat LoGSegmentation(Mat src, double sigma = 1.4, double zeroCrossThreshold = 5.0, bool useKernelSize = false, int ksize = 0) + { + // choose kernel size from sigma if requested + if (!useKernelSize && ksize == 0) + { + ksize = (int)(sigma * 6) | 1; // ensure odd + } + + Mat gray = new(); + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + + Mat blurred = new(); + Cv2.GaussianBlur(gray, blurred, new Size(ksize, ksize), sigma); + + Mat lap = new(); + Cv2.Laplacian(blurred, lap, MatType.CV_32F, ksize: 3); + + Mat absLap = new(); + absLap = Cv2.Abs(lap); + + Mat mask = ZeroCrossingMask(absLap, zeroCrossThreshold); + return mask; + } + + // Difference of Gaussians as approximation of LoG. + // sigma1 < sigma2. thresh is applied to absolute DoG response after normalization. + public static Mat DoGSegmentation(Mat src, double sigma1 = 1.0, double sigma2 = 2.0, double thresh = 10.0, bool normalize = true) + { + Mat gray = new(); + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + + int k1 = (int)(sigma1 * 6) | 1; + int k2 = (int)(sigma2 * 6) | 1; + + Mat g1 = new(); + Mat g2 = new(); + Cv2.GaussianBlur(gray, g1, new Size(k1, k1), sigma1); + Cv2.GaussianBlur(gray, g2, new Size(k2, k2), sigma2); + + Mat dog = new(); + Cv2.Subtract(g1, g2, dog); + + Mat absDog = new(); + absDog = Cv2.Abs(dog); + + if (normalize) + { + Cv2.Normalize(absDog, absDog, 0, 255, NormTypes.MinMax); + } + + Mat mask = new(); + absDog.ConvertTo(absDog, MatType.CV_8U); + Cv2.Threshold(absDog, mask, thresh, 255, ThresholdTypes.Binary); + return mask; + } + + // Compute Otsu variance profiles for thresholds 0..254 (we evaluate t as threshold separating [0..t] and [t+1..255]). + // Returns best thresholds (max between-class variance and min within-class variance) and the per-threshold arrays. + public static (int bestByBetween, int bestByWithin, double[] betweenVars, double[] withinVars) ComputeOtsuVarianceProfiles(Mat src) + { + // ensure grayscale CV_8U + Mat gray = new(); + if (src.Type() == MatType.CV_8U && src.Channels() == 1) + { + src.CopyTo(gray); + } + else + { + Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); + } + + int rows = gray.Rows; + int cols = gray.Cols; + int total = rows * cols; + + // histogram + double[] hist = new double[256]; + for (int y = 0; y < rows; y++) + { + for (int x = 0; x < cols; x++) + { + byte v = gray.At(y, x); + hist[v] += 1.0; + } + } + + // convert to probabilities + for (int i = 0; i < 256; i++) hist[i] /= total; + + // cumulative arrays + double[] P = new double[256]; // cumulative probability + double[] S = new double[256]; // cumulative first moment (i * p(i)) + double[] M2 = new double[256]; // cumulative second moment (i^2 * p(i)) + + double pAcc = 0, sAcc = 0, m2Acc = 0; + for (int i = 0; i < 256; i++) + { + pAcc += hist[i]; + sAcc += i * hist[i]; + m2Acc += i * i * hist[i]; + P[i] = pAcc; + S[i] = sAcc; + M2[i] = m2Acc; + } + + double muT = S[255]; + double m2T = M2[255]; + + // total variance (scalar) + double totalVar = m2T - muT * muT; + + int maxIndexBetween = 0; + int minIndexWithin = 0; + double maxBetween = double.MinValue; + double minWithin = double.MaxValue; + + // arrays to hold per-threshold values (we'll fill 256 but only meaningful up to 254) + double[] betweenVars = new double[256]; + double[] withinVars = new double[256]; + + for (int t = 0; t < 255; t++) + { + double w0 = P[t]; + double w1 = 1.0 - w0; + + if (w0 <= 0.0 || w1 <= 0.0) + { + betweenVars[t] = 0.0; + withinVars[t] = double.PositiveInfinity; + continue; + } + + double s0 = S[t]; + double s1 = S[255] - s0; + double m20 = M2[t]; + double m21 = m2T - m20; + + double mu0 = s0 / w0; + double mu1 = s1 / w1; + + // between-class variance + double sigmaB = w0 * w1 * (mu0 - mu1) * (mu0 - mu1); + betweenVars[t] = sigmaB; + + // within-class variance via second moments: var0 = M2_0/w0 - mu0^2 + double var0 = m20 / w0 - mu0 * mu0; + double var1 = m21 / w1 - mu1 * mu1; + double sigmaW = w0 * var0 + w1 * var1; // weighted within-class variance + withinVars[t] = sigmaW; + + if (sigmaB > maxBetween) + { + maxBetween = sigmaB; + maxIndexBetween = t; + } + + if (sigmaW < minWithin) + { + minWithin = sigmaW; + minIndexWithin = t; + } + } + + return (maxIndexBetween, minIndexWithin, betweenVars, withinVars); + } + + // Helper: detect zero-crossings in a floating-point Laplacian image. + // A pixel is considered edge if any 4-neighbor has opposite sign and the absolute difference exceeds 'threshold'. + private static Mat ZeroCrossingMask(Mat lapFloat, double threshold) + { + // ensure lapFloat is CV_32F + Mat lap = new(); + if (lapFloat.Type() != MatType.CV_32F) + { + lapFloat.ConvertTo(lap, MatType.CV_32F); + } + else + { + lap = lapFloat.Clone(); + } + + int rows = lap.Rows; + int cols = lap.Cols; + Mat mask = new Mat(rows, cols, MatType.CV_8U, Scalar.All(0)); + + for (int y = 1; y < rows - 1; y++) + { + for (int x = 1; x < cols - 1; x++) + { + float v = lap.At(y, x); + // check 8 neighbors + bool isZeroCross = false; + float maxDiff = 0f; + for (int ny = -1; ny <= 1; ny++) + { + for (int nx = -1; nx <= 1; nx++) + { + if (ny == 0 && nx == 0) continue; + float vn = lap.At(y + ny, x + nx); + if ((v > 0 && vn < 0) || (v < 0 && vn > 0)) + { + float diff = MathF.Abs(v - vn); + if (diff > maxDiff) maxDiff = diff; + isZeroCross = true; + } + } + } + + if (isZeroCross && maxDiff >= threshold) + { + mask.Set(y, x, (byte)255); + } + } + } + + return mask; + } + + private static float Clamp(float v) + { + if (v < 0) return 0; + if (v > 255) return 255; + return v; + } +} diff --git a/DigitalImageProcessing/GLobalTools.cs b/DigitalImageProcessing/GLobalTools.cs index 9bdbfcc..2e37d3d 100644 --- a/DigitalImageProcessing/GLobalTools.cs +++ b/DigitalImageProcessing/GLobalTools.cs @@ -2,34 +2,89 @@ namespace DigitalImageProcessing { - internal static class GLobalTools - { - public static SKBitmap GetFull(string filepath) => SKBitmap.Decode(filepath); - public static SKBitmap GetGray(string filepath) - { - SKBitmap src = SKBitmap.Decode(filepath); - Parallel.For(0, src.Height - 1, (y) => - Parallel.For(0, src.Width - 1, (x) => - { - SKColor color = src.GetPixel(x, y); - byte avg = (byte)((color.Red + color.Green + color.Blue) / 3); - src.SetPixel(x, y, new(avg, avg, avg)); - }) - ); - return src; - } - public static SKBitmap GetBinary(string filepath) - { - SKBitmap src = SKBitmap.Decode(filepath); - Parallel.For(0, src.Height - 1, (y) => - Parallel.For(0, src.Width - 1, (x) => - { - SKColor color = src.GetPixel(x, y); - byte bin = (byte)((color.Red + color.Green + color.Blue) / 3 > 128 ? 256 : 0); - src.SetPixel(x, y, new(bin, bin, bin)); - }) - ); - return src; - } - } + internal static class GLobalTools + { + public static SKBitmap GetFull(string filepath) => SKBitmap.Decode(filepath); + public static SKBitmap GetGray(string filepath) + { + SKBitmap src = SKBitmap.Decode(filepath); + Parallel.For(0, src.Height - 1, (y) => + Parallel.For(0, src.Width - 1, (x) => + { + SKColor color = src.GetPixel(x, y); + byte avg = (byte)((color.Red + color.Green + color.Blue) / 3); + src.SetPixel(x, y, new(avg, avg, avg)); + }) + ); + return src; + } + public static SKBitmap GetBinary(string filepath) + { + SKBitmap src = SKBitmap.Decode(filepath); + Parallel.For(0, src.Height - 1, (y) => + Parallel.For(0, src.Width - 1, (x) => + { + SKColor color = src.GetPixel(x, y); + byte bin = (byte)((color.Red + color.Green + color.Blue) / 3 > 128 ? 256 : 0); + src.SetPixel(x, y, new(bin, bin, bin)); + }) + ); + return src; + } + public static int[,] GetGrayAsArray(string filepath) + { + SKBitmap src = SKBitmap.Decode(filepath); + int[,] result = new int[src.Width, src.Height]; + Parallel.For(0, src.Height - 1, (y) => + Parallel.For(0, src.Width - 1, (x) => + { + SKColor color = src.GetPixel(x, y); + byte avg = (byte)((color.Red + color.Green + color.Blue) / 3); + result[x, y] = avg; + }) + ); + return result; + } + public static T[,] Select(this T[,] array, Func func) + { + int width = array.GetLength(0); + int height = array.GetLength(1); + T[,] result = new T[width, height]; + Parallel.For(0, height - 1, (y) => + Parallel.For(0, width - 1, (x) => + { + result[x, y] = func(array[x, y]); + }) + ); + return result; + } + public static T[,] Select(this T[,] array, Func func) + { + int width = array.GetLength(0); + int height = array.GetLength(1); + T[,] result = new T[width, height]; + Parallel.For(0, height - 1, (y) => + Parallel.For(0, width - 1, (x) => + { + result[x, y] = func(array[x, y], x, y); + }) + ); + return result; + } + public static void Save(this int[,] array, string filepath) + { + int width = array.GetLength(0); + int height = array.GetLength(1); + using SKBitmap output = new(width, height); + Parallel.For(0, height - 1, (y) => + Parallel.For(0, width - 1, (x) => + { + byte val = (byte)array[x, y]; + output.SetPixel(x, y, new SKColor(val, val, val)); + }) + ); + using FileStream fs = new(filepath, FileMode.Create, FileAccess.Write); + output.Encode(fs, SKEncodedImageFormat.Png, 100); + } + } } diff --git a/DigitalImageProcessing/Program.cs b/DigitalImageProcessing/Program.cs index 5aa51f4..e5b776b 100644 --- a/DigitalImageProcessing/Program.cs +++ b/DigitalImageProcessing/Program.cs @@ -1,7 +1,7 @@ using DigitalImageProcessing; using SkiaSharp; -HSI.Run(); +Segmentation.Run(); //string testImage = @"C:\Users\Surface\Downloads\" + @"1682575881230001.png"; ////Alograms.Steps(@"C:\Users\Surface\Downloads\", @"C:\Users\Surface\Downloads\" + @"1682575881230001.png"); diff --git a/DigitalImageProcessing/Segmentation.cs b/DigitalImageProcessing/Segmentation.cs new file mode 100644 index 0000000..ae6d5c9 --- /dev/null +++ b/DigitalImageProcessing/Segmentation.cs @@ -0,0 +1,37 @@ +namespace DigitalImageProcessing +{ + internal static class Segmentation + { + public static void Run() + { + int thresholdValue = 128; + int thresholdDelta = 5; + + float delta = float.MaxValue; + int loopCount = 0; + + int[,] grayImage = GLobalTools.GetGrayAsArray(@"C:\Users\Surface\Downloads\OIP.webp"); + + while (delta > thresholdDelta) + { + loopCount++; + List forePix = [], backPix = []; + for (int i = 0; i < grayImage.GetLength(0); i++) + for (int j = 0; j < grayImage.GetLength(1); j++) + if (grayImage[i, j] >= thresholdValue) + forePix.Add(grayImage[i, j]); + else + backPix.Add(grayImage[i, j]); + int fore = (int)forePix.Average(); + int back = (int)backPix.Average(); + int thres = (fore + back) / 2; + delta = int.Abs(thres - thresholdValue); + thresholdValue = thres; + Console.WriteLine($"Current Threshold Value: {thres}, Delta: {delta}"); + } + Console.WriteLine($"Final Threshold Value: {thresholdValue} found in {loopCount} loops."); + int[,] foreground = grayImage.Select(i => i > thresholdValue ? 255 : 0); + foreground.Save(@"C:\Users\Surface\Downloads\OIP_foreground.webp"); + } + } +}