1117
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,33 @@
|
|||||||
using OpenCvSharp;
|
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;
|
if (!ImageUtils.TryRead(filepath, out Mat image))
|
||||||
bool isLeap1 = DateTime.IsLeapYear(year);
|
|
||||||
bool isLeap2 = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
|
||||||
bool isLeap3 = year switch
|
|
||||||
{
|
{
|
||||||
_ when year % 400 == 0 => true,
|
Console.WriteLine("Failed to read image: " + filepath);
|
||||||
_ when year % 100 == 0 => false,
|
return;
|
||||||
_ 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(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);
|
int thresholdValue = 128;
|
||||||
InputArray kernel = InputArray.Create<int>(new int[,] {
|
int thresholdDelta = 30;
|
||||||
{ 0, 1, 0 },
|
|
||||||
{ 1, 1, 1 },
|
|
||||||
{ 0, 1, 0 }
|
|
||||||
}, MatType.CV_8U);
|
|
||||||
Mat dst = new();
|
|
||||||
//Cv2.Filter2D(image, dst, image.Depth(), kernel);
|
|
||||||
|
|
||||||
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");
|
while(delta > thresholdValue)
|
||||||
|
{
|
||||||
|
//Cv2.Threshold(gray, img1, thresholdValue, 255, ThresholdTypes.Binary);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<int>(idx, 0);
|
||||||
|
byte b = (byte)Clamp(centers.At<float>(clusterIdx, 0));
|
||||||
|
byte g = (byte)Clamp(centers.At<float>(clusterIdx, 1));
|
||||||
|
byte rcol = (byte)Clamp(centers.At<float>(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<byte>(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<float>(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<float>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,34 +2,89 @@
|
|||||||
|
|
||||||
namespace DigitalImageProcessing
|
namespace DigitalImageProcessing
|
||||||
{
|
{
|
||||||
internal static class GLobalTools
|
internal static class GLobalTools
|
||||||
{
|
{
|
||||||
public static SKBitmap GetFull(string filepath) => SKBitmap.Decode(filepath);
|
public static SKBitmap GetFull(string filepath) => SKBitmap.Decode(filepath);
|
||||||
public static SKBitmap GetGray(string filepath)
|
public static SKBitmap GetGray(string filepath)
|
||||||
{
|
{
|
||||||
SKBitmap src = SKBitmap.Decode(filepath);
|
SKBitmap src = SKBitmap.Decode(filepath);
|
||||||
Parallel.For(0, src.Height - 1, (y) =>
|
Parallel.For(0, src.Height - 1, (y) =>
|
||||||
Parallel.For(0, src.Width - 1, (x) =>
|
Parallel.For(0, src.Width - 1, (x) =>
|
||||||
{
|
{
|
||||||
SKColor color = src.GetPixel(x, y);
|
SKColor color = src.GetPixel(x, y);
|
||||||
byte avg = (byte)((color.Red + color.Green + color.Blue) / 3);
|
byte avg = (byte)((color.Red + color.Green + color.Blue) / 3);
|
||||||
src.SetPixel(x, y, new(avg, avg, avg));
|
src.SetPixel(x, y, new(avg, avg, avg));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
public static SKBitmap GetBinary(string filepath)
|
public static SKBitmap GetBinary(string filepath)
|
||||||
{
|
{
|
||||||
SKBitmap src = SKBitmap.Decode(filepath);
|
SKBitmap src = SKBitmap.Decode(filepath);
|
||||||
Parallel.For(0, src.Height - 1, (y) =>
|
Parallel.For(0, src.Height - 1, (y) =>
|
||||||
Parallel.For(0, src.Width - 1, (x) =>
|
Parallel.For(0, src.Width - 1, (x) =>
|
||||||
{
|
{
|
||||||
SKColor color = src.GetPixel(x, y);
|
SKColor color = src.GetPixel(x, y);
|
||||||
byte bin = (byte)((color.Red + color.Green + color.Blue) / 3 > 128 ? 256 : 0);
|
byte bin = (byte)((color.Red + color.Green + color.Blue) / 3 > 128 ? 256 : 0);
|
||||||
src.SetPixel(x, y, new(bin, bin, bin));
|
src.SetPixel(x, y, new(bin, bin, bin));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return src;
|
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<T>(this T[,] array, Func<T, T> 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<T>(this T[,] array, Func<T, int, int, T> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using DigitalImageProcessing;
|
using DigitalImageProcessing;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
HSI.Run();
|
Segmentation.Run();
|
||||||
|
|
||||||
//string testImage = @"C:\Users\Surface\Downloads\" + @"1682575881230001.png";
|
//string testImage = @"C:\Users\Surface\Downloads\" + @"1682575881230001.png";
|
||||||
////Alograms.Steps(@"C:\Users\Surface\Downloads\", @"C:\Users\Surface\Downloads\" + @"1682575881230001.png");
|
////Alograms.Steps(@"C:\Users\Surface\Downloads\", @"C:\Users\Surface\Downloads\" + @"1682575881230001.png");
|
||||||
|
|||||||
@@ -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<int> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user