如何开展一个机器视觉检测项目?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何开展一个机器视觉检测项目?相关的知识,希望对你有一定的参考价值。

当接触一个全新的 机器视觉 检测项目时,如何开展一个机器视觉检测项目?机器视觉检测项目基本流程有哪些?简单流程如下:确定客户需求、方案设计、软件开发、现场调试、文档交接。在实际项目中,各个流程可能互相耦合,不过整体流程是基本明确的,整理后如下。

一、确定客户需求

项目伊始,需要准确、详细地了解客户需求,这个过程需要多次现场考察、反复与客户沟通,才能落实客户需求。主要确定项目的应用类型、节拍要求、精度要求、安装空间、光照环境、通讯接口等内容。

应用类型——确定机器视觉应用类型(测量、识别、检测、引导定位),了解产品表面状态、外形尺寸等影响 视觉检测 因素的变化情况,初步评估能否满足需求

节拍要求——客户对生产效率方面的要求,量化视觉检测步骤时间

精度要求——针对各检测功能点及客户生产要求量化视觉检测精度

安装空间——确认现场环境对视觉系统的安装是否有限制

光照环境——确认现场环境是否有强光、日光干扰等特殊影响

通讯接口——确认现场与视觉系统配合的数据传输接口类型、I/O接口类型等

二、方案设计

视觉系统 是一个各部分互相配合的有机整体,并不是简单的组合,所以一个项目的方案设计关乎着整个项目的成败,从初步方案,到ZUI终方案,以及中间经历的各个版本,需要整个团队共同评审,才能敲定ZUI终方案。整体方案内容主要包括需求分析、视觉硬件设计、视觉软件设计、可行性验证、开发计划。

需求分析——整理客户关键需求,并分析需求可行性

机器视觉硬件设计——包括视觉系统平台、相机、镜头、光源的选择

机器视觉软件设计——采用第三方视觉软件,抑或自行开发视觉处理软件

可行性验证——搭建软硬件环境,初步测试能否满足客户需求

开发计划——罗列项目开发计划,模块化项目节点,跟进项目进度

另外,一个完整的项目应包括机械、电气、视觉等其他部分,以上只是简单陈述下视觉方案的设计内容,而ZUI终呈现给客户的完整项目方案还应包括机械设计、电气设计。

三、软件开发

软件开发主要包括人机交互界面、底层算法,测试运行。

人机交互界面开发——简单易用、处理结果直观显示;落实软件框架,功能化软件模块;软件框架多采用生产者/消费者模式,功能模块一般包括图像采集模块、算法处理模块,数据保存模块,通讯模块等。

底层算法开发——落实算法处理工具(Halcon、OpenCV、NI Vision等);开发算法处理流程;生成动态库.dll

测试运行——模拟现场出现的各种情况,测试软件算法的稳定性、鲁棒性。

四、现场调试

现场调试是一个比较繁琐的过程,主要体现在调试过程中的不确定性因素较多,例如环境光的影响、机械振动的影响、硬件工作的稳定性等。主要流程包括设备安装、模块调试、系统联调、自动运行。

设备安装—— 运动部件安装;相机、镜头、光源安装;视觉系统内部线缆附件走线;视觉控制器、光源控制器安装;外部通信、I/O线缆走线等;

模块调试—— 相机功能调试(触发拍照等);工件检测特征视觉参数调试(相机参数、镜头参数、光源位置和亮度等);外部通讯调试等;

系统联调—— 调试完整视觉程序;正常生产检测调试等;

自动运行—— 开机自动运行;

五、文档交接

需要与客户进行文档交接时,说明已进入项目尾部,此时应编写操作文档并进行现场培训。

操作手册—— 软件基本操作;常见问题及解决方法;

现场培训—— 项目工作流程;软件操作;问题解决步骤;

深圳 瑞视特科技 有限公司(www.0755vc.com) 有多年的机器视觉行业经验的,在机器视觉的应用领域上积累了不少的案例,大家可以了解一下。
参考技术A

机器视觉检测项目的开展一般包括以下几个步骤:

    需求分析:首先需要明确检测项目的目的、检测对象和检测要求等,进行需求分析,以确定机器视觉检测系统的功能和性能指标。

    系统设计:根据需求分析的结果,设计机器视觉检测系统的硬件和软件结构,并选择适合的图像采集设备、图像处理算法和人工智能模型等。

    数据采集:对需要检测的物体进行图像采集,并生成对应的数据集,用于后续的训练和测试。

    算法开发:根据需求分析和系统设计,开发适合的图像处理算法和人工智能模型,并进行模型训练和优化,以提高检测系统的准确性和效率。

    系统集成:将图像采集设备、图像处理算法和人工智能模型等集成到一起,形成完整的机器视觉检测系统,并进行测试和调试。

    上线部署:将机器视觉检测系统部署到实际生产环境中,并进行运行和维护,确保检测系统的稳定性和可靠性。
    总之,机器视觉检测项目的开展需要综合考虑需求分析、系统设计、数据采集、算法开发、系统集成和上线部署等多个方面,需要专业的技术人员和团队进行配合和协作,以确保机器视觉检测系统的功能和性能满足实际需求。

毕业设计 - 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python

1 简介

今天学长向大家介绍一个机器视觉项目

基于机器视觉opencv的手势检测 手势识别 算法

毕设帮助,开题指导,技术解答
🇶746876041

2 传统机器视觉的手势检测

普通机器视觉手势检测的基本流程如下:


其中轮廓的提取,多边形拟合曲线的求法,凸包集和凹陷集的求法都是采用opencv中自带的函数。手势数字的识别是利用凸包点以及凹陷点和手部中心点的几何关系,简单的做了下逻辑判别了(可以肯定的是这种方法很烂),具体的做法是先在手部定位出2个中心点坐标,这2个中心点坐标之间的距离阈值由程序设定,其中一个中心点就是利用OpenNI跟踪得到的手部位置。有了这2个中心点的坐标,在程序中就可以分别计算出在这2个中心点坐标上的凸凹点的个数。当然了,这样做的前提是用人在做手势表示数字的同时应该是将手指的方向朝上(因为没有像机器学习那样通过样本来训练,所以使用时条件要苛刻很多)。利用上面求出的4种点的个数(另外程序中还设置了2个辅助计算点的个数,具体见代码部分)和简单的逻辑判断就可以识别出数字0~5了。其它的数字可以依照具体的逻辑去设计(还可以设计出多位数字的识别),只是数字越多设计起来越复杂,因为要考虑到它们之间的干扰性,且这种不通用的设计方法也没有太多的实际意义。

2.1 轮廓检测法

使用 void convexityDefects(InputArray contour, InputArray convexhull, OutputArray convexityDefects) 方法

该函数的作用是对输入的轮廓contour,凸包集合来检测其轮廓的凸型缺陷,一个凸型缺陷结构体包括4个元素,缺陷起点坐标,缺陷终点坐标,缺陷中离凸包线距离最远的点的坐标,以及此时最远的距离。参数3即其输出的凸型缺陷结构体向量。

其凸型缺陷的示意图如下所示:


第1个参数虽然写的是contour,字面意思是轮廓,但是本人实验过很多次,发现如果该参数为目标通过轮廓检测得到的原始轮廓的话,则程序运行到onvexityDefects()函数时会报内存错误。因此本程序中采用的不是物体原始的轮廓,而是经过多项式曲线拟合后的轮廓,即多项式曲线,这样程序就会顺利地运行得很好。另外由于在手势识别过程中可能某一帧检测出来的轮廓非常小(由于某种原因),以致于少到只有1个点,这时候如果程序运行到onvexityDefects()函数时就会报如下的错误:

int Mat::checkVector(int _elemChannels, int _depth, bool _requireContinuous) const



    return (depth() == _depth || _depth <= 0) &&

        (isContinuous() || !_requireContinuous) &&

        ((dims == 2 && (((rows == 1 || cols == 1) && channels() == _elemChannels) || (cols == _elemChannels))) ||

        (dims == 3 && channels() == 1 && size.p[2] == _elemChannels && (size.p[0] == 1 || size.p[1] == 1) &&

         (isContinuous() || step.p[1] == step.p[2]*size.p[2])))

    ? (int)(total()*channels()/_elemChannels) : -1;


该函数源码大概意思就是说对应的Mat矩阵如果其深度,连续性,通道数,行列式满足一定条件的话就返回Mat元素的个数和其通道数的乘积,否则返回-1;而本文是要求其返回值大于3,有得知此处输入多边形曲线(即参数1)的通道数为2,所以还需要求其元素的个数大于1.5,即大于2才满足ptnum > 3。简单的说就是用convexityDefects()函数来对多边形曲线进行凹陷检测时,必须要求参数1曲线本身至少有2个点(也不知道这样分析对不对)。因此本人在本次程序convexityDefects()函数前加入了if(Mat(approx_poly_curve).checkVector(2, CV_32S) > 3)来判断,只有满足该if条件,才会进行后面的凹陷检测。这样程序就不会再出现类似的bug了。

第2个参数一般是由opencv中的函数convexHull()获得的,一般情况下该参数里面存的是凸包集合中的点在多项式曲线点中的位置索引,且该参数以vector的形式存在,因此参数convexhull中其元素的类型为unsigned int。在本次凹陷点检测函数convexityDefects()里面根据文档,要求该参数为Mat型。因此在使用convexityDefects()的参数2时,一般将vector直接转换Mat型。

参数3是一个含有4个元素的结构体的集合,如果在c++的版本中,该参数可以直接用vector来代替,Vec4i中的4个元素分别表示凹陷曲线段的起始坐标索引,终点坐标索引,离凸包集曲线最远点的坐标索引以及此时的最远距离值,这4个值都是整数。在c版本的opencv中一般不是保存的索引,而是坐标值。

2.2 算法结果

数字“0”的识别结果:

数字“1”的识别结果

数字“2”的识别结果

数字“3”的识别结果:

数字“4”的识别结果:

数字“5”的识别结果:

2.3 整体代码实现

2.3.1 算法流程

学长实现过程和上面的系统流程图类似,大概过程如下:

  • 1. 求出手部的掩膜

  • 2. 求出掩膜的轮廓

  • 3. 求出轮廓的多变形拟合曲线

  • 4. 求出多边形拟合曲线的凸包集,找出凸点

  • 5. 求出多变形拟合曲线的凹陷集,找出凹点

  • 6. 利用上面的凸凹点和手部中心点的几何关系来做简单的数字手势识别

(这里用的是C语言写的,这个代码是学长早期写的,同学们需要的话,学长出一个python版本的)

#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"

#include <iostream>

#define DEPTH_SCALE_FACTOR 255./4096.
#define ROI_HAND_WIDTH 140
#define ROI_HAND_HEIGHT 140
#define MEDIAN_BLUR_K 5
#define XRES  640
#define YRES  480
#define DEPTH_SEGMENT_THRESH 5
#define MAX_HANDS_COLOR 10
#define MAX_HANDS_NUMBER  10
#define HAND_LIKELY_AREA 2000
#define DELTA_POINT_DISTENCE 25     //手部中心点1和中心点2距离的阈值
#define SEGMENT_POINT1_DISTANCE 27  //凸点与手部中心点1远近距离的阈值
#define SEGMENT_POINT2_DISTANCE 30  //凸点与手部中心点2远近距离的阈值

using namespace cv;
using namespace xn;
using namespace std;


int main (int argc, char **argv)

    unsigned int convex_number_above_point1 = 0;
    unsigned int concave_number_above_point1 = 0;
    unsigned int convex_number_above_point2 = 0;
    unsigned int concave_number_above_point2 = 0;
    unsigned int convex_assist_above_point1 = 0;
    unsigned int convex_assist_above_point2 = 0;
    unsigned int point_y1 = 0;
    unsigned int point_y2 = 0;
    int number_result = -1;
    bool recognition_flag = false;  //开始手部数字识别的标志

    vector<Scalar> color_array;//采用默认的10种颜色
    
        color_array.push_back(Scalar(255, 0, 0));
        color_array.push_back(Scalar(0, 255, 0));
        color_array.push_back(Scalar(0, 0, 255));
        color_array.push_back(Scalar(255, 0, 255));
        color_array.push_back(Scalar(255, 255, 0));
        color_array.push_back(Scalar(0, 255, 255));
        color_array.push_back(Scalar(128, 255, 0));
        color_array.push_back(Scalar(0, 128, 255));
        color_array.push_back(Scalar(255, 0, 128));
        color_array.push_back(Scalar(255, 128, 255));
    
    vector<unsigned int> hand_depth(MAX_HANDS_NUMBER, 0);
    vector<Rect> hands_roi(MAX_HANDS_NUMBER, Rect(XRES/2, YRES/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT));

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);    //显示分割出来的手的区域
    namedWindow("handrecognition", CV_WINDOW_AUTOSIZE); //显示0~5数字识别的图像

    COpenNI openni;
    if(!openni.Initial())
        return 1;

    if(!openni.Start())
        return 1;
    while(1) 
        if(!openni.UpdateData()) 
            return 1;
        
        /*获取并显示色彩图像*/
        Mat color_image_src(openni.image_metadata_.YRes(), openni.image_metadata_.XRes(),
                            CV_8UC3, (char *)openni.image_metadata_.Data());
        Mat color_image;
        cvtColor(color_image_src, color_image, CV_RGB2BGR);
        Mat hand_segment_mask(color_image.size(), CV_8UC1, Scalar::all(0));

        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) 

            point_y1 = itUser->second.Y;
            point_y2 = itUser->second.Y + DELTA_POINT_DISTENCE;
            circle(color_image, Point(itUser->second.X, itUser->second.Y),
                   5, color_array.at(itUser->first % color_array.size()), 3, 8);

            /*设置不同手部的深度*/
            hand_depth.at(itUser->first % MAX_HANDS_COLOR) = (unsigned int)(itUser->second.Z* DEPTH_SCALE_FACTOR);//itUser->first会导致程序出现bug

            /*设置不同手部的不同感兴趣区域*/
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,
                                               ROI_HAND_WIDTH, ROI_HAND_HEIGHT);
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x =  itUser->second.X - ROI_HAND_WIDTH/2;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y =  itUser->second.Y - ROI_HAND_HEIGHT/2;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width = ROI_HAND_WIDTH;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height = ROI_HAND_HEIGHT;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x <= 0)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x  = 0;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x > XRES)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x =  XRES;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y <= 0)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = 0;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y > YRES)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y =  YRES;
        
        imshow("color image", color_image);

        /*获取并显示深度图像*/
        Mat depth_image_src(openni.depth_metadata_.YRes(), openni.depth_metadata_.XRes(),
                            CV_16UC1, (char *)openni.depth_metadata_.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
        Mat depth_image;
        depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
        imshow("depth image", depth_image);

        //取出手的mask部分
        //不管原图像时多少通道的,mask矩阵声明为单通道就ok
        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) 
            for(int i = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x; i < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width, XRES); i++)
                for(int j = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y; j < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height, YRES); j++) 
                    hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                                & ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
                
         
        medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
        Mat hand_segment(color_image.size(), CV_8UC3);
        color_image.copyTo(hand_segment, hand_segment_mask);

        /*对mask图像进行轮廓提取,并在手势识别图像中画出来*/
        std::vector< std::vector<Point> > contours;
        findContours(hand_segment_mask, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);//找出mask图像的轮廓
        Mat hand_recognition_image = Mat::zeros(color_image.rows, color_image.cols, CV_8UC3);

        for(int i = 0; i < contours.size(); i++)   //只有在检测到轮廓时才会去求它的多边形,凸包集,凹陷集
            recognition_flag = true;
            /*找出轮廓图像多边形拟合曲线*/
            Mat contour_mat = Mat(contours[i]);
            if(contourArea(contour_mat) > HAND_LIKELY_AREA)    //比较有可能像手的区域
                std::vector<Point> approx_poly_curve;
                approxPolyDP(contour_mat, approx_poly_curve, 10, true);//找出轮廓的多边形拟合曲线
                std::vector< std::vector<Point> > approx_poly_curve_debug;
                approx_poly_curve_debug.push_back(approx_poly_curve);

                 drawContours(hand_recognition_image, contours, i, Scalar(255, 0, 0), 1, 8); //画出轮廓
    //            drawContours(hand_recognition_image, approx_poly_curve_debug, 0, Scalar(256, 128, 128), 1, 8); //画出多边形拟合曲线

                /*对求出的多边形拟合曲线求出其凸包集*/
                vector<int> hull;
                convexHull(Mat(approx_poly_curve), hull, true);
                for(int i = 0; i < hull.size(); i++) 
                    circle(hand_recognition_image, approx_poly_curve[hull[i]], 2, Scalar(0, 255, 0), 2, 8);

                    /*统计在中心点1以上凸点的个数*/
                    if(approx_poly_curve[hull[i]].y <= point_y1) 
                        /*统计凸点与中心点1的y轴距离*/
                        long dis_point1 = abs(long(point_y1 - approx_poly_curve[hull[i]].y));
                        int dis1 = point_y1 - approx_poly_curve[hull[i]].y;
                        if(dis_point1 > SEGMENT_POINT1_DISTANCE && dis1 >= 0机器视觉定位是啥?和机器视觉检测有啥不同?

表面缺陷检测,当机器拥有视觉

机器视觉检测都检测啥?原理是啥?

机器视觉的应用领域

机器视觉行业实践技巧

机器视觉系统有啥功能?