opencv实现机器视觉检测和计数的方法

引言

在机器视觉中,有时需要对产品进行检测和计数。其难点无非是对于产品的图像分割。

由于之前网购的维生素片,有时候忘了今天有没有吃过,就想对瓶子里的药片计数...在学习opencv以后,希望实现对于维生素片分割计数算法。本次实战在基于形态学的基础上又衍生出基于距离变换的分水岭算法,使其实现的效果更具普遍性。

基于形态学的维生素片检测和计数

整体思路:

  • 读取图片
  • 形态学处理(在二值化前进行适度形态学处理,效果俱佳)
  • 二值化
  • 提取轮廓(进行药片分割)
  • 获取轮廓索引,并筛选所需要的轮廓
  • 画出轮廓,显示计数

opencv实现:

int main(int argc, char** argv)
{
    Mat src, src_binary,dst,src_distance;
    src = imread("D:/opencv练习图片/维生素片机器视觉检测和计数.png");
    imshow("原图片", src);
    Mat kernel = getStructuringElement(MORPH_RECT, Size(16, 16), Point(-1, -1));
    morphologyEx(src, dst, MORPH_OPEN, kernel);
    imshow("形态学",dst);
    cvtColor(dst, dst, COLOR_RGB2GRAY);
    threshold(dst, src_binary, 100, 255, THRESH_OTSU);
    imshow("二值化", src_binary);
    vector<vector<Point>> contours;
    findContours(src_binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));
    RNG rng(12345);
    double area;
    Point2i PL;
    for (size_t i = 0; i < contours.size(); i++)
    {
        area = contourArea(contours[i]);
        if (area < 500)continue;
        PL = contours[i].front();
        Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        drawContours(src, contours, i, color, 2, 8);
        putText(src, to_string(i), PL, FONT_HERSHEY_COMPLEX, 1, color, 2);
    }
    imshow("计数结果", src);
    waitKey(0);
    return 0;
}

效果展示:

由上图可以看的,原图在经过形态学处理后,可以去除很多细节,简化后续的药片分割操作。

但是在计数结果图上发现,索引17号药片并没有完全分割(实际上修改形态学的结构元素尺寸(改为20*20)也可以完全分离这两个药片)。

🤨这不由得让我们思考,如果简单的形态学处理分割不了药片呢?

对于复杂的产品图片,我们可以使用基于距离变换的分水岭算法对其分割。

基于距离变换的分水岭算法检测和计数

OpenCV 采用了基于标记点的分水岭算法,在这种算法中我们要设置哪些山谷点会汇合,哪些不会。这是一种交互式的图像分割。我们要做的就是给我们已知的对象打上不同的标签(即添加注水点)。然后实施分水岭算法。每一次灌水,我们的标签就会被更新,当两个不同颜色的标签相遇时就构建堤坝,直到将所有山峰淹没,最后我们得到的边界对象(堤坝)的值为 -1。

🙁对于如何打上标签(即添加注水点)有两种办法:

opencv中,对于一张二值化的图像,后续处理方式有两种。第一种方式就是利用findContours、drawContours等函数进行轮廓分析(opencv以对轮廓的处理为主)。第二种方式就是计算连通域进行区域分析。

第一种(基于轮廓):在二值化后,对图像寻找轮廓findContours,筛选出注水区域轮廓,然后通过drawContours对轮廓标记。

第二种(基于区域):在二值化后,先对寻找图像中的前景图(即注水点),再寻找到背景图(进行膨胀),最后找到未知区域(背景减去前景,得到边缘图),通过connectedComponents()获取标记点。

相关API:

分水岭函数watershed函数原型

void watershed( InputArray image, InputOutputArray markers );

第一个输入参数 image,必须是CV_8UC3类型图像。

第二个输入/输出参数markers必须是32位单通道图像。和image尺寸一样。包含不同区域的轮廓,每个轮廓有一个自己唯一的编号。

😛在执行watershed函数后,算法会根据markers传入的轮廓作为种子,对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。

距离变换函数distanceTransform函数原型

距离变换运算用于计算二值化图像中的每一个非零点距自己最近的零点的距离,距离变换图像上越亮的点,代表了这一点距离零点的距离越远。

距离变换通常用于求解图像的骨骼和查找物体的质心(即获取距离变换的极大值)和计算非零像素到最近零像素点的最短距离。

distanceTransform( InputArray src, OutputArray dst, int distanceType, int maskSize,int dstType = CV_32F);

第一个输入参数src,必须是CV_8UC1类型的二值图像(只有0或1)

第二个输出参数dst,表示的是计算距离的输出图像,输出类型是CV_32F/CV_8U的单通道图像,大小与输入图片相同。

第三个参数distanceType,表示的是选取距离的类型,可以设置为DIST_L1,DIST_L2,DIST_C

第四个参数maskSize,表示的是距离变换的掩膜模板,可以设置为3,5(常用3)

第四个参数dstType,表示输出类型,可选择CV_32F/CV_8U

注:若输出类型为CV_32F,想要显示距离变换后的骨架图像,需要对其归一化。(normalize)

先来看看第一种标记mark(基于轮廓)的方法:

(一)读入图像,形态学,二值化(消除噪声)

    Mat src, src_binary, dst, src_distance;
    src = imread("D:/opencv练习图片/维生素片机器视觉检测和计数.png");
    imshow("原图片", src);
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    morphologyEx(src, dst, MORPH_OPEN, kernel);
    imshow("形态学", dst);
    cvtColor(dst, dst, COLOR_RGB2GRAY);
    threshold(dst, src_binary, 100, 255, THRESH_OTSU);
    imshow("二值化", src_binary);

(二)距离变换(归一化显示),再二值化

   distanceTransform(src_binary, src_distance, DIST_L2, 3, 5);
    normalize(src_distance, src_distance, 0, 1, NORM_MINMAX);
    imshow("距离变换", src_distance);
    threshold(src_distance, src_distance, 0.4,1, THRESH_BINARY);
    imshow("再二值化", src_distance);

经过距离变换后的二值化,可以清晰看到,药片以及完全分割开来。

(三)打上标签(添加注水点),基于轮廓

    //寻找标记点marsk的轮廓信息 也就是分水岭的水坝
    src_distance.convertTo(src_distance, CV_8UC1);
    vector<vector<Point>> contours;
    findContours(src_distance, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
    //创建maker
    Mat markers = Mat::zeros(src.size(), CV_32S);//  //因为分水岭后的边缘存储是-1,所以必须使用有符号的CV_32S
    for (size_t t = 0; t < contours.size(); t++)
    {
        drawContours(markers, contours, static_cast<int>(t), Scalar(static_cast<int>(t) + 1), -1);//轮廓数字编号
    }
    circle(markers, Point(5, 5), 30, Scalar(255), -1);//关键代码(mark做一个小标记)
    int index1 = 0;
    //打印轮廓数据 有值的均为轮廓线
    for (int row = 0; row < markers.rows; row++)
        for (int col = 0; col < markers.cols; col++)
        {
            index1 = markers.at<int>(row, col);
            cout << index1 << ",";
        }

部分标签markers轮廓数据截图,可以看到0代表背景,轮廓线用正数索引标识。

(四)进行分水岭操作,并给分水岭后的区域随机上色,并打印出检测的药片个数。

 // 生成随机颜色
    vector<Vec3b> colors;
    for (size_t i = 0; i < contours.size(); i++) {
        int r = theRNG().uniform(0, 255);
        int g = theRNG().uniform(0, 255);
        int b = theRNG().uniform(0, 255);
        colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }

    // 颜色填充与最终显示
    Mat dst1 = Mat::zeros(markers.size(), CV_8UC3);
    int index = 0;
    for (int row = 0; row < markers.rows; row++) {
        for (int col = 0; col < markers.cols; col++) {
            index = markers.at<int>(row, col);

            if (index > 0 && index <= contours.size()) {
                dst1.at<Vec3b>(row, col) = colors[index - 1];

            }
            else {
                dst1.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
            }

        }
    }
    imshow("结果显示", dst1);
    printf("药片检测个数: %d\n", contours.size());

再来看看第二种标记mark(基于区域)的方法:

(一)读入图像,形态学,二值化(消除噪声)

 Mat foreground, background, unkonwn;//创建前景,背景,未知区域
    Mat src, src_binary, dst, src_distance;
    src = imread("D:/opencv练习图片/维生素片机器视觉检测和计数.png");
    imshow("原图片", src);
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    morphologyEx(src, dst, MORPH_OPEN, kernel);
    imshow("形态学", dst);
    cvtColor(dst, dst, COLOR_RGB2GRAY);
    threshold(dst, src_binary, 100, 255, THRESH_OTSU);
    imshow("二值化", src_binary);

(二)对二值化图像进行膨胀操作,得到大部分是背景的图片

//得到背景图片
    dilate(src_binary, background, kernel, Point(-1, -1), 3);
    imshow("背景图片", background);

(三)通过对二值图像距离变换得到前景图片(即注水点)

//距离变换
    distanceTransform(src_binary, src_distance, DIST_L2, 3, 5);
    imshow("距离变换", src_distance);
    normalize(src_distance, src_distance, 0, 255, NORM_MINMAX);
    double my_minv = 0.0, my_maxv = 0.0;
    minMaxIdx(src_binary, &my_minv, &my_maxv);
    threshold(src_distance, foreground, 0.4 * my_maxv, 255, THRESH_BINARY);
    foreground.convertTo(foreground, CV_8U);
    imshow("前景图片", foreground);

(四)通过背景与前景的差值,得到未知区域(即边缘所在区域)

//得到未知区域
    unkonwn = background - foreground;
    imshow("未知区域", unkonwn);

(五)得到这些区域以后,我们可以获取注水点的标签,通过connectedComponents实现(即获取markers标签)

//创建标记点markers
    Mat markers = Mat(src.size(), CV_32S);
    int num = connectedComponents(foreground, markers, 8);
    cout << num << endl;
    markers = markers + 1;
    for (int i = 0; i < unkonwn.rows; i++)
    {
        for (int j = 0; j < unkonwn.cols; j++)
        {
            if (((int)unkonwn.at<uchar>(i, j)) == 255)
            {
                markers.at<signed int>(i, j) = 0;
            }
        }
    }

详细理解该步骤:

现在我们已经知道哪些是背景,哪些是药片(前景区域)。

因此我们可以创建一个标签(和原图大小,类型为CV_32S),通过connectedComponents函数对前景区域进行标记

该函数会对前景区域连通域分析,并将背景设定为0,其他区域从1开始正整数标记(这就是我们的种子,水漫时会从这里漫出),结果返回给markers。

但是对于分水岭算法,会将为0的区域认为是未知区域,因此要markers整体加一。

(六)进行分水岭操作,并显示边缘

watershed(src, markers);
    for (int row = 0; row < markers.rows; row++)
    {
        for (int col = 0; col < markers.cols; col++)
        {

            if (markers.at< int>(row, col) == -1)
            {
                src.at<Vec3b>(row, col) = Vec3b(0, 0, 255);
            }
        }
    }

    imshow("结果", src);

由于分水岭算法会将找到的边缘在markers置为-1,因此我们对原图操作,将索引为-1的位置的像素值改为红色(即显示边缘)。

参考链接:

OpenCV---分水岭算法 - 山上有风景 - 博客园 (cnblogs.com)

(8条消息) c++和opencv小知识:基于距离变换的分水岭算法(固定流程)_梦游城市的博客-CSDN博客

(8条消息) OpenCV分水岭算法图像分割_冰冰bing的博客-CSDN博客

到此这篇关于opencv实现机器视觉检测和计数的方法的文章就介绍到这了,更多相关opencv 机器视觉检测和计数 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用OpenCV实现道路车辆计数的使用方法

    今天,我们将一起探讨如何基于计算机视觉实现道路交通计数. 在本教程中,我们将仅使用Python和OpenCV,并借助背景减除算法非常简单地进行运动检测. 我们将从以下四个方面进行介绍: 1. 用于物体检测的背景减法算法主要思想. 2. OpenCV图像过滤器. 3. 利用轮廓检测物体. 4. 建立进一步数据处理的结构. 背景扣除算法 有许多不同的背景扣除算法,但是它们的主要思想都很简单. 假设有一个房间的视频,在某些帧上没有人和宠物,那么此时的视频基本为静态的,我们将其称为背景(backgrou

  • opencv实现机器视觉检测和计数的方法

    引言 在机器视觉中,有时需要对产品进行检测和计数.其难点无非是对于产品的图像分割. 由于之前网购的维生素片,有时候忘了今天有没有吃过,就想对瓶子里的药片计数...在学习opencv以后,希望实现对于维生素片分割计数算法.本次实战在基于形态学的基础上又衍生出基于距离变换的分水岭算法,使其实现的效果更具普遍性. 基于形态学的维生素片检测和计数 整体思路: 读取图片 形态学处理(在二值化前进行适度形态学处理,效果俱佳) 二值化 提取轮廓(进行药片分割) 获取轮廓索引,并筛选所需要的轮廓 画出轮廓,显示

  • Python机器视觉之基于OpenCV的手势检测

    目录 1 简介 2 传统机器视觉的手势检测 2.1 轮廓检测法 2.2 算法结果 2.3 整体代码实现 3 深度学习方法做手势识别 3.1 经典的卷积神经网络 3.2 YOLO系列 3.3 SSD 3.4 实现步骤 3.5 关键代码 4 实现手势交互 1 简介 今天学长向大家介绍一个机器视觉项目 基于机器视觉opencv的手势检测 手势识别 算法 2 传统机器视觉的手势检测 普通机器视觉手势检测的基本流程如下: 其中轮廓的提取,多边形拟合曲线的求法,凸包集和凹陷集的求法都是采用opencv中自带

  • python opencv人脸检测提取及保存方法

    注意这里提取到的人脸图片的保存地址要改成自己要保存的地址 opencv人脸的检测模型的路径也要更改为自己安装的opencv的人脸检测模型的路径 import cv2 save_path = 'F:\\face_photo_save\\chenym\\' cascade = cv2.CascadeClassifier("D:\\opencv249\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt_tree.xml&q

  • OpenCV实现人脸检测

    前段日子,写了个人脸检测的小程序,可以检测标记图片.视频.摄像头中的人脸.效果还行吧,用的是opencv提供人脸库.至于具体的人脸检测原理,找资料去啃吧. 环境:VS2013+OPENCV2.4.10+Win8.1 一.基于对话框的MFC 首先,新建一个基于对话框的MFC应用程序,命名为myFaceDetect(取消"安全开发周期(SDL)检查"勾选,我自己习惯取消这个). 放置Button,设置Button的ID和Caption. 图片按钮--ID:IDC_FACEDETECT 视频

  • C++利用opencv实现人脸检测

    小编所有的帖子都是基于unbuntu系统的,当然稍作修改同样试用于windows的,经过小编的绞尽脑汁,把刚刚发的那篇python 实现人脸和眼睛的检测的程序用C++ 实现了,当然,也参考了不少大神的博客,下面我们就一起来看看: Linux系统下安装opencv我就再啰嗦一次,防止有些人没有安装没调试出来喷小编的程序是个坑, sudo apt-get install libcv-dev sudo apt-get install libopencv-dev 看看你的usr/share/opencv

  • Java+OpenCV实现人脸检测并自动拍照

    java+opencv实现人脸检测,调用笔记本摄像头实时抓拍,人脸会用红色边框标识出来,并且将抓拍的目录存放在src下,图片名称是时间戳. 环境配置:win7 64位,jdk1.8 CameraBasic.java package com.njupt.zhb.test; import java.awt.EventQueue; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; im

  • Python下应用opencv 实现人脸检测功能

    使用OpenCV's Haar cascades作为人脸检测,因为他做好了库,我们只管使用. 代码简单,除去注释,总共有效代码只有10多行. 所谓库就是一个检测人脸的xml 文件,可以网上查找,下面是一个地址: https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml 如何构造这个库,学习完本文后可以参考: http://note.sonots.com/Sc

  • 浅谈opencv自动光学检测、目标分割和检测(连通区域和findContours)

    步骤如下: 1.图片灰化: 2.中值滤波 去噪 3.求图片的光影(自动光学检测) 4.除法去光影 5.阈值操作 6.实现了三种目标检测方法 主要分两种连通区域和findContours 过程遇到了错误主要是图片忘了灰化处理,随机颜色的问题.下面代码都已经进行了解决 这是findContours的效果 下面是连通区域的结果 #include <opencv2\core\utility.hpp> #include <opencv2\imgproc.hpp> #include <o

  • python利用opencv实现颜色检测

    本文实例为大家分享了python利用opencv实现颜色检测的具体代码,供大家参考,具体内容如下 需要实现倒车辅助标记检测的功能,倒车辅助标记颜色已经确定了,所以不需要使用深度学习的方法,那样成本太高了,直接可以使用颜色检测的方法. 1.首先需要确定待检测目标的HSV值 import cv2 img = cv2.imread('l3.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) hsv = cv2.cvtColor(img, cv2.COL

  • Python OpenCV招商银行信用卡卡号识别的方法

    学在前面 从本篇博客起,我们将实际完成几个小案例,第一个就是银行卡号识别,预计本案例将写 5 篇左右的博客才可以完成,一起加油吧. 本文的目标是最终获取一套招商银行卡,0~9 数字的图,对于下图的数字,我们需要提取出来,便于后续模板匹配使用.不过下图中找到的数字不完整,需要找到尽量多的卡片,然后补齐这些数字. 提取卡片相关数字 先对上文中卡片中的数字进行相关提取操作,加载图片的灰度图,获取目标区域.在画板中模拟一下坐标区域,为了便于进行后续的操作. 具体代码如下: import cv2 as c

随机推荐