图像二值化
黑色(0)表示背景
白色(1-255)表示对象
Threshold二值化
//函数原型
double Threshold(InputArray src,
OutputArray dst,
double thresh,
double maxval,
ThresholdTypes type)
Threshold图像二值化 | |
函数返回值:返回二值化的thresh,type为Ostu与Triangle时,计算得到,其它时与参数thresh一致 | |
参数 | 说明 |
InputArray src | 输入图像:8位或32位浮点型多通道(使用OTSU或TRIANGLE时,只能8位单通道) |
OutputArray dst | 输出图像:与输入图像相同的大小、类型和通道数 |
double thresh | 阈值:type为Otsu或Triangle时,此参数忽略 |
double maxval | 最大值 |
ThresholdTypes type | 二值化类型:Ostu与Triangle可以其它组合使用 |
参数ThresholdTypes说明 | |
取值 | 说明 |
Binary | src(x,y)>thresh时为maxval;否则为0 |
BinaryInv | src(x,y)>thresh时为0;否则为maxval |
Trunc | src(x,y)>thresh时为thresh;否则为src(x,y) |
Tozero | src(x,y)>thresh时为0;否则为src(x,y) |
TozeroInv | src(x,y)>thresh时为src(x,y);否则为0 |
Otsu | 使用大津法计算thresh,再二值化 |
Triangle | 使用三角法计算thresh,再二值化 |
各种ThresholdType结果示例
AdaptiveThreshold自适应二值化
//函数原型
void AdaptiveThreshold(InputArray src,
OutputArray dst,
double maxValue,
AdaptiveThresholdTypes adaptiveMethod,
ThresholdTypes thresholdType,
int blockSize,
double c)
AdaptiveThreshold自适应阈值 | |
参数 | 说明 |
InputArray src | 输入图像:8位单通道 |
OutputArray dst | 输出图像:与输入图像相同的大小、类型和通道数 |
double maxValue | 最大值:二值化后的最大值 |
AdaptiveThresholdTypes adaptiveMethod | 适应方式 |
ThresholdTypes thresholdType | 阈值类型:THRESH_BINARY 或 THRESH_BINARY_INV |
int blockSize | 块大小:大于等于3的奇数(经验值25?) |
double c | C常量:用于平均值或加权平均值减去值,正负0均可(经验值10?) |
源码示例
public void Run()
{
using var src = Cv2.ImRead(ImagePath.LenaColor, ImreadModes.Grayscale);
if (src.Empty()) throw new Exception("图像读取有误");
using var dst = new Mat();
double thresh = 127;
double maxVal = 255;
Cv2.Threshold(src, dst, thresh, maxVal, ThresholdTypes.Binary);
Cv2.ImShow($"Threshold-Binary :{thresh}", dst);
//计算图像的均值
thresh = Cv2.Mean(src).ToDouble();
Cv2.Threshold(src, dst, thresh, maxVal, ThresholdTypes.Binary);
Cv2.ImShow($"Threshold-Binary:{thresh}", dst);
//三角法:对直方图有一个峰的处理结果较优;第三个参数,不影响结果
thresh = Cv2.Threshold(src, dst, 0, maxVal, ThresholdTypes.Triangle | ThresholdTypes.Binary);
Cv2.ImShow($"Threshold-Triangle|Binary: {thresh}", dst);
//大津法:对直方图有两个峰的处理结果较优;第三个参数,不影响结果
thresh = Cv2.Threshold(src, dst, 0, maxVal, ThresholdTypes.Otsu | ThresholdTypes.Binary);
Cv2.ImShow($"Threshold-Ostu|Binary: {thresh}", dst);
Cv2.Threshold(src, dst, thresh, maxVal, ThresholdTypes.BinaryInv);
Cv2.ImShow($"Threshold BinaryInv: {thresh}", dst);
Cv2.Threshold(src, dst, thresh, maxVal, ThresholdTypes.Trunc);
Cv2.ImShow($"Threshold Trunc: {thresh}", dst);
Cv2.Threshold(src, dst, thresh, maxVal, ThresholdTypes.Tozero);
Cv2.ImShow($"Threshold Tozero: {thresh}", dst);
Cv2.Threshold(src, dst, thresh, maxVal, ThresholdTypes.TozeroInv);
Cv2.ImShow($"Threshold TozeroInv: {thresh}", dst);
var wName = "AdaptiveThreshold Demo";
Cv2.NamedWindow(wName, WindowFlags.AutoSize);
var tbBlockSize = "blockSize";
var tbC = "C-255";
var tbMaxValue = "maxValue";
int blockSize = 25;
int adaptiveC = 10 + 255;
int maxvalue = 255;
Cv2.CreateTrackbar(tbBlockSize, wName, ref blockSize, 255, tbOnChanged);
Cv2.CreateTrackbar(tbC, wName, ref adaptiveC, 255 * 2, tbOnChanged);
Cv2.CreateTrackbar(tbMaxValue, wName, ref maxvalue, 255, tbOnChanged);
while (true)
{
if (Cv2.WaitKey(1) == (int)Keys.Escape) break;
if (tbChanged)
{
if (blockSize % 2 == 0)//必须为正奇数
{
Cv2.SetTrackbarPos(tbBlockSize, wName, blockSize + 1);
continue;
}
if (blockSize == 1) continue;//不能为1
//GaussianC,
Cv2.AdaptiveThreshold(src, dst, maxvalue, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, blockSize, adaptiveC - 255);
dst.PutText("GaussianC", new Point(30, 50), HersheyFonts.HersheySimplex, 0.8, Scalar.Black);
Cv2.Line(dst, new Point(dst.Width - 1, 0), new Point(dst.Width - 1, dst.Height), Scalar.Black);
//MeanC
using var dst1 = new Mat();
Cv2.AdaptiveThreshold(src, dst1, maxvalue, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, blockSize, adaptiveC - 255);
dst1.PutText("MeanC", new Point(30, 50), HersheyFonts.HersheySimplex, 0.8, Scalar.Black);
Cv2.HConcat(new[] { dst, dst1 }, dst);
Cv2.ImShow(wName, dst);
tbChanged = false;
}
}
DrawThresholdTypesDemo();
Cv2.WaitKey();
Cv2.DestroyAllWindows();
}
private bool tbChanged = false;
private void tbOnChanged(int pos,IntPtr userData)
{
tbChanged = true;
}
//二值化示例窗口名
private string winName = "ThresholdTypes Demo";
//当前阈值
private int thresh = 200;
//最大值
private int maxVal = 255;
/// <summary>
/// 生成各种二值化图例
/// </summary>
private void DrawThresholdTypesDemo()
{
using (var srcMat = new Mat(1, 255, MatType.CV_8U, new Scalar(0)))
{
//初始待二值化图像
for (int i = 0; i < 50; i++)
{
srcMat.At<byte>(0, i) = (byte)(i * 2 + 150);
}
for (int i = 50; i < 170; i++)
{
srcMat.At<byte>(0, i) = (byte)(350 - i * 2);
}
for (int i = 170; i < 256; i++)
{
srcMat.At<byte>(0, i) = (byte)(i * 2.5 - 412);
}
Cv2.NamedWindow(winName, WindowFlags.AutoSize);
Cv2.CreateTrackbar("thresh", winName, ref thresh, 255, OnChanged);
Cv2.CreateTrackbar("maxVal", winName, ref maxVal, 255, OnChanged);
while (true)
{
if (Cv2.WaitKey(50) == (int)Keys.Escape) break;
if(posChanged) DrawDemo(srcMat);
}
Cv2.DestroyAllWindows();
}
}
/// <summary>
/// 示例各种二值化结果
/// </summary>
/// <param name="srcMat"></param>
private void DrawDemo(Mat srcMat)
{
//不重新获取的话,滚动一会后会失效?
thresh = Cv2.GetTrackbarPos("thresh", winName);
maxVal = Cv2.GetTrackbarPos("maxVal", winName);
using var thresholdMat = new Mat();
int graphIndex = 0;//行高偏移系数
var resultImg = new Mat(900, 1530, MatType.CV_8UC3, new Scalar(255, 255, 255));
//待二值化数据
DrawLine(resultImg, srcMat, graphIndex, thresh, maxVal, $"Origin,thresh={thresh}");
graphIndex++;
Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.Binary);
DrawLine(resultImg, thresholdMat, graphIndex, thresh, maxVal, $"Binary,thresh={thresh}");
graphIndex++;
Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.BinaryInv);
DrawLine(resultImg, thresholdMat, graphIndex, thresh, maxVal, $"BinaryInv,thresh={thresh}");
graphIndex++;
Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.Trunc);
DrawLine(resultImg, thresholdMat, graphIndex, thresh, maxVal, $"Trunc,thresh={thresh}");
graphIndex++;
Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.TozeroInv);
DrawLine(resultImg, thresholdMat, graphIndex, thresh, maxVal, $"TozeroInv,thresh={thresh}");
graphIndex++;
Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.Tozero);
DrawLine(resultImg, thresholdMat, graphIndex, thresh, maxVal, $"Tozero,thresh={thresh}");
graphIndex++;
var calcThresh = Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.Otsu | ThresholdTypes.Binary);
DrawLine(resultImg, thresholdMat, graphIndex, calcThresh, maxVal, $"Otsu,Binary,thresh={calcThresh}");
graphIndex++;
calcThresh = Cv2.Threshold(srcMat, thresholdMat, thresh, maxVal, ThresholdTypes.Triangle | ThresholdTypes.BinaryInv);
DrawLine(resultImg, thresholdMat, graphIndex, calcThresh, maxVal, $"Triangle,BinaryInv,thresh={calcThresh}");
Cv2.ImShow(winName, resultImg);
posChanged = false;
}
/// <summary>
/// 绘制最大值、阈值及二值化结果
/// </summary>
/// <param name="resultImg">绘制结果</param>
/// <param name="thresholdMat">二值化图像</param>
/// <param name="graphIndex">最几个图例,用于控制绘图位置</param>
/// <param name="thresh">阈值</param>
/// <param name="maxVal">最大值</param>
/// <param name="text">提示文字</param>
private void DrawLine(Mat resultImg, Mat thresholdMat, int graphIndex, double thresh, double maxVal, string text)
{
var graphWidth = resultImg.Width / 2;//分左右两列
var graphHeight = 220;
var binW = Math.Round((double)1D * (graphWidth - 100) / thresholdMat.Width, 2);
var offsetX = (graphWidth - thresholdMat.Width * binW) / 2 + graphWidth * (graphIndex % 2);
var binH = 0.5;
var offsetY = graphIndex / 2 * graphHeight + 50;
var thickness = 2;
for (int i = 1; i < thresholdMat.Width; i++)
{
var pt1 = new Point2d(binW * (i - 1) + offsetX, binH * (255 - thresholdMat.At<byte>(0, i - 1)) + offsetY);
var pt2 = new Point2d(binW * i + offsetX, binH * (255 - thresholdMat.At<byte>(0, i)) + offsetY);
//二值线,黑色
Cv2.Line(resultImg, (Point)pt1, (Point)pt2, Scalar.Black, thickness, LineTypes.AntiAlias);
//分隔线
if ((i - 1) % 25 == 0)
{
Cv2.Line(resultImg, new Point(binW * i + offsetX, offsetY), new Point(binW * i + offsetX, binH * 255 + offsetY), Scalar.LightGray);
}
}
var x1 = offsetX;
var x2 = offsetX + thresholdMat.Width * binW;
//thresh阈值线,绿色
Cv2.Line(resultImg, new Point(x1, binH * (255 - thresh) + offsetY),
new Point(x2, binH * (255 - thresh) + offsetY), Scalar.Green, 1);
//maxVal最大阈值线,红色
Cv2.Line(resultImg, new Point(x1, binH * (255 - maxVal) + offsetY),
new Point(x2, binH * (255 - maxVal) + offsetY), Scalar.Red, 1);
//0值,蓝色
Cv2.Line(resultImg, new Point(x1, binH * (255 - 0) + offsetY),
new Point(x2, binH * (255 - 0) + offsetY), Scalar.Blue, 1);
Cv2.PutText(resultImg, text+ ",Red:maxval,Green:thresh,Blue:0,Black:result", new Point(offsetX, binH * 280 + offsetY), HersheyFonts.HersheySimplex, 0.5, Scalar.Black);
}
//滚动条是否变过
private bool posChanged = false;
private void OnChanged(int pos,IntPtr userData)
{
posChanged = true;
}
OpenCvSharp函数示例目录