C#+OpenCV实战(三)_玉米粒计数

/// <summary>
/// 标注物体 -物体计数标注
/// 比如玉米粒计数并标注每个玉米
/// </summary>
/// <param name="imgFile1"></param>
/// <returns>物体位置;数量=contours.Length</returns>
public static Point[][] ImageDetector_CountAndLabel(Mat srcMat, out Mat dstMat)
{
    dstMat = new Mat();
    srcMat.CopyTo(dstMat);

    // 二值化操作
    Mat grayimg = new Mat();
    Cv2.CvtColor(srcMat, grayimg, ColorConversionCodes.BGR2GRAY);
    Mat binaryImg = new Mat();
    Cv2.Threshold(grayimg, binaryImg, 240, 255, ThresholdTypes.Binary);

    // 腐蚀
    Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(15, 15));
    Mat morhImage = new Mat();
    Cv2.Dilate(binaryImg, morhImage, kernel, null, 2);

    // 距离变换:用于二值化图像中的每一个非零点距自己最近的零点的距离,距离变换图像上越亮的点,代表了这一点距离零点的距离越远
    Mat dist = new Mat();
    Cv2.BitwiseNot(morhImage, morhImage);

    // 计算物体之间的距离
    Cv2.DistanceTransform(morhImage, dist, DistanceTypes.L1, DistanceTransformMasks.Mask3);  // 曼哈顿距离,
    Cv2.Normalize(dist, dist, 0, 1.0, NormTypes.MinMax);   //范围在0~1之间

    // 形态学处理
    Mat morphImg = new Mat();
    dist.ConvertTo(morphImg, MatType.CV_8U);
    Cv2.Threshold(morphImg, morphImg, 0.99, 255, ThresholdTypes.Binary);  //上图像素值在0~1之间
    kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(7, 3), new Point(-1, -1));
    Cv2.MorphologyEx(morphImg, morphImg, MorphTypes.Open, kernel);        //开操作

    // 找到种子的轮廓区域
    /*
     * 找轮廓(输入图像,out 轮廓集合,out 级别,轮廓检索模式,近似法,偏移量)
     * 输入图像:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
     * 轮廓集合:contours 
     * 历史轮廓:hierarchy:0:后一个轮廓,1:前一个轮廓,2:父轮廓,3:内嵌轮廓
     * 轮廓检索模式:轮廓的检索模式
     *         取值一:CV_RETR_EXTERNAL 只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
     *         取值二:CV_RETR_LIST     检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到
     *         取值三:CV_RETR_CCOMP    检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
     *         取值四:CV_RETR_TREE     检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
     * 近似方法:轮廓的近似方法
     *         取值一:CV_CHAIN_APPROX_NONE   保存物体边界上所有连续的轮廓点到contours向量内
     *         取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
     *         取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
     * 偏移量:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,且Point可以是负值。不填为默认不偏移Point()
    */
    Cv2.FindContours(morphImg, out Point[][] contours, out HierarchyIndex[] hierarchly, RetrievalModes.External, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(0, 0));

    // 标识位置
    for (int i = 0; i < contours.Length; i++)
    {
        Rect rect = Cv2.BoundingRect(contours[i]);
        int x = rect.X;
        int y = rect.Y;
        int w = rect.Width;
        int h = rect.Height;
        Cv2.Circle(dstMat, x + w / 2, y + h / 2, 20, new Scalar(0, 0, 255), -1);
        if (i >= 9)
        {
            Cv2.PutText(dstMat, (i + 1).ToString(), new Point(x + w / 2 - 18, y + h / 2 + 8), HersheyFonts.HersheySimplex, 0.8, new Scalar(0, 255, 0), 2);
        }
        else
        {
            Cv2.PutText(dstMat, (i + 1).ToString(), new Point(x + w / 2 - 8, y + h / 2 + 8), HersheyFonts.HersheySimplex, 0.8, new Scalar(0, 255, 0), 2);
        }
    }

    return contours;
}
posted @ 2024-07-11 11:49  ꧁执笔小白꧂  阅读(91)  评论(0编辑  收藏  举报