using System;
using OpenCvSharp;
namespace ConsoleApp1
{
class Program
{
public static void Main()
{
// 加载图像
Mat src = Cv2.ImRead(@"C:\Users\Tesseract\Desktop\666.png"); // 原图像,后面没怎么用到了
if (src.Empty())
{
Console.WriteLine("Could not open or find the image!");
return;
}
// 显示源图像
//Cv2.ImShow("Source Image", src);
// 将背景从白色更改为黑色,因为这将有助于以后在使用“距离变换”时提取更好的结果 -------------------[可以省略]
/*for (int i = 0; i < src.Rows; i++)
{
for (int j = 0; j < src.Cols; j++)
{
if (src.At<Vec3b>(i, j) == new Vec3b(255, 255, 255))
{
src.At<Vec3b>(i, j)[0] = 0;
src.At<Vec3b>(i, j)[1] = 0;
src.At<Vec3b>(i, j)[2] = 0;
}
}
}*/
// 显示输出图像
//Cv2.ImShow("Black Background Image", src);
// 创建一个内核,用于锐化图像
double[,] k = { {1.0, 1.0, 1.0 },
{ 1, -8, 1 },
{ 1, 1, 1} };
Mat kernel = Mat.FromArray(k);
// 二阶导数的近似,一个相当强的核做拉普拉斯滤波,因为它是好的
// ,我们需要把所有的东西转换成比CV_8U更深的东西,因为核有一
// 些负值,一般来说,我们可以期望拉普拉斯图像具有负值,但8位无
// 符号整数(我们正在处理的整数)可以包含0到255的值,因此可能
// 的负数将被截断
Mat imgLaplacian = new Mat(); // 拉普拉斯卷积,后面没再用到
Cv2.Filter2D(src, imgLaplacian, MatType.CV_32F, kernel); // ------------------[Filter2D卷积函数]
Mat sharp = new Mat(); // 应该是用于锐化处理的中间变量,后面没再用到
src.ConvertTo(sharp, MatType.CV_32F);
Mat imgResult = sharp - imgLaplacian; // ------------------[锐化处理后的图像(应该是)]
// 转换回8位彩图
imgResult.ConvertTo(imgResult, MatType.CV_8UC3);
imgLaplacian.ConvertTo(imgLaplacian, MatType.CV_8UC3);
//Cv2.ImShow( "Laplace Filtered Image", imgLaplacian );
//Cv2.ImShow("New Sharped Image", imgResult);
// 从源图像创建二进制图像
Mat bw = new Mat(); // 二值化后的图像,距离变换后就没用到了
Cv2.CvtColor(imgResult, bw, ColorConversionCodes.BGR2GRAY); // 锐化后的图像转灰度
Cv2.Threshold(bw, bw, 40, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); //二值化
//Cv2.ImShow("Binary Image", bw);
// ---------------------------- 所以可以我自己预处理成二值化图像(8UC1)后直接从这里开始 ------------------------------
// 执行距离变换算法
Mat dist = new Mat(); // ------------------[距离图像,后面持续用到]
Cv2.DistanceTransform(bw, dist, DistanceTypes.L2, DistanceTransformMasks.Mask3);
// 规格化范围为{0.0,1.0}的距离图像
// 这样我们就可以将其可视化并设置阈值
Cv2.Normalize(dist, dist, 0, 1.0, NormTypes.MinMax); // 距离图像归一化
Cv2.ImShow("Distance Transform Image", dist);
// 获得峰值的阈值
// 这将是前景对象的标记
Cv2.Threshold(dist, dist, 0.4, 1.0, ThresholdTypes.Binary);
// 稍微膨胀一下dist图像
Mat kernel1 = Mat.Ones(new Size(3, 3), MatType.CV_8U);
Cv2.Dilate(dist, dist, kernel1); // 膨胀
Cv2.ImShow("Peaks", dist); // 峰
// 创建距离图像的CV_8U版本
// findContours()需要它
Mat dist_8u = new Mat();
dist.ConvertTo(dist_8u, MatType.CV_8U); // 距离图像的8位版本
// 查找总标记
Point[][] contours;
HierarchyIndex[] hierarchyIndices;
Cv2.FindContours(dist_8u, out contours, out hierarchyIndices, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
// 为分水岭算法创建标记图像 ----------------------------------------------------------[后面大面积用到]
Mat markers = Mat.Zeros(dist.Size(), MatType.CV_32S); // 注水点据说是,但是没太看懂,opencv的是基于标记点的分水岭
// 绘制前景标记
for (int i = 0; i < contours.Length; i++)
{
Cv2.DrawContours(markers, contours, (int)i, new Scalar((int)i + 1), -1);
}
// 绘制背景标记
Cv2.Circle(markers, new Point(5, 5), 3, new Scalar(255), -1);// 仅用于显示哪里的部分是背景
Mat markers1 = markers * 10000; // 仅用于显示出标记点
markers1.ConvertTo(markers1, MatType.CV_16S);
Cv2.ImShow("Markers", markers1);
// 执行分水岭算法
Cv2.Watershed(imgResult, markers); // 分水岭处理仅需未灰度处理的原图像以及标记点
Mat mark = new Mat();
markers.ConvertTo(mark, MatType.CV_8U);
Cv2.BitwiseNot(mark, mark);
//Cv2.ImShow("Markers_v2", mark); // 如果您想查看标记图像在该点的外观,请取消注释
// 生成随机颜色
Vec3b[] colors = new Vec3b[contours.Length];
RNG rng = new RNG((ulong)DateTime.Now.Ticks);
for (int i = 0; i < contours.Length; i++)
{
colors[i] = new Vec3b((byte)rng.Uniform(0, 256), (byte)rng.Uniform(0, 256), (byte)rng.Uniform(0, 256));
}
// 创建结果图像
Mat dst = Mat.Zeros(markers.Size(), MatType.CV_8UC3);
// 用随机颜色填充标记的对象
for (int i = 0; i < markers.Rows; i++)
{
for (int j = 0; j < markers.Cols; j++)
{
int index = markers.At<int>(i, j);
if (index > 0 && index <= contours.Length)
{
dst.At<Vec3b>(i, j) = colors[index - 1];
}
}
}
// 可视化最终图像
Cv2.ImShow("Final Result", dst);
Cv2.WaitKey();
}
}
}