• OpenCV——级联分类器的样本训练记录


    目录

    前言

    准备工作

    硬件

    软件

    训练过程

    第一步:准备样本

    第二步:生成样本描述文件

    第三步:生成样本文件vec

    第四步:训练分类器

    过程中遇到的问题

    总结


    前言

    目前主流的较前卫的目标检测方案是基于深度学习,而传统的方案则是基于手工特征,即通过对图形进行滑动窗口遍历计算机特征值,并训练特征分类器以达到检测的目的。本文则是基于级联分类器的样本训练过程的记录。

    准备工作

    1. 硬件

      可以长时间训练特征文件的电脑,样本文件的高宽比较大的时候,训练可能会耗费几十个小时甚至几天时间。

    2. 软件

    • 样本处理工具:推荐 XnConvert-win-x64.exe,可以批量重命名、旋转、镜像、灰度、缩放等。曾用过imagetuner_8.0.exe、reaConverterLite-Setup.exe,必要的时候还可以结合使用everything.exe重命名。imagetuner_8.0.exe功能比较简单,reaConverterLite-Setup.exe这个软件界面好用但是批量处理经常崩溃,而且需要通过资源管理器关闭界面再次开始,好处是重命名的功能,可以修改替换原有文件名,这个软件有收费版,没有尝试。

    训练过程

    第一步:准备样本

    正样本与负样本。考虑各种场景下的正样本照片,负样本照片即为背景照片,正负样本的数量比以1:3为宜,如果负样本的图幅度很大,可以考虑降低负样本的数量,因为是通过正样本在负样本上的滑动遍历。

    这个部分最大量的工作就是样本查找,对于正样本就是对其轮廓按照矩形或者圆形抠图,对与负样本就是寻找大量的背景图片。当样本数量不够时,可以考虑扩充样本。

    正样本抠图

    注意,不要用截屏工具。

    扩充样本

    对称、旋转

    第二步:生成样本描述文件

    可以通过cmd命令行实现,当然最方便可以写个程序一次生成,这是我用c++编写的两个小程序,目前用着没发现bug,够用。

    正样本描述文件生成c++伪码

    #include
    #include
    #include
    #include
    #include
    #pragma comment(lib, "gdiplus.lib")

    using namespace std;
    using namespace Gdiplus;

    int main()
    {
        //输入文件目录
        char dir[256] = { 0 };
        cout << "Enter a directory: ";
        cin.getline(dir, 255);
        

        //检查文件目录路径是否合法,添加"\\*.*"
        char dirNew[261] = { 0 };
        strcpy_s(dirNew, dir);
        strcat_s(dirNew, "\\*.*");

        //遍历目录下的文件,并读取图片信息,写入描述文件
        GdiplusStartupInput gdiplusstartupinput;
        ULONG_PTR gdiplustoken;
        GdiplusStartup(&gdiplustoken, &gdiplusstartupinput, NULL);

        intptr_t handle;
        _finddata_t findData;

        handle = _findfirst(dirNew, &findData);
        if (handle == -1)        // 检查是否成功
        {
            cout << "no file" << endl;
            system("Pause");
            return 0;
        }
        //创建空白描述文件
        char filePath[256] = { 0 };
        strcpy_s(filePath, dir);
        strcat_s(filePath, "\\posdata.dat");
        FILE *fp = NULL;
        errno_t err = fopen_s(&fp, filePath, "w+");
        if (err != 0)
        {
            cout << "open err" << endl;
            system("Pause");
            return 0;
        }

        do
        {

            //如果当前文件有子文件夹
            if (findData.attrib & _A_SUBDIR)
            {
                /*if (strcmp(findData.name, ".") == 0 || strcmp(findData.name, "..") == 0)
                    continue;*/

                //cout << findData.name << "\t

    \n";

                 在目录后面加上"\\"和搜索到的目录名进行下一次搜索
                //strcpy_s(dirNew, dir);
                //strcat_s(dirNew, "\\");
                //strcat_s(dirNew, findData.name);

                //listFiles(dirNew);
            }
            else
            {
                cout << findData.name << "\t" << findData.size << " bytes.\n";
                //判断如果文件类型不是图片,则跳过
                string fileName(findData.name);
                string fileType = fileName.substr(strlen(findData.name) - 4);
                if (!(fileType == ".jpg" || fileType == ".jpeg" || fileType == ".jpe" || fileType == ".bmp"))
                    continue;

                //添加文件夹名
                //fputs("cw45/", fp);

                fputs(findData.name, fp);
                fputs(" 1 0 0 ", fp);

                //路径字符转宽字符
                char ImgPath[512] = { 0 };
                sprintf_s(ImgPath, "%s%s%s", dir, "\\", findData.name);
                int len = 0;
                len = strlen(ImgPath);
                int unicodeLen = ::MultiByteToWideChar(CP_ACP, 0, ImgPath, -1, NULL, 0);
                wchar_t *pUnicode = new wchar_t[unicodeLen + 1];
                memset(pUnicode, 0, (unicodeLen + 1) * sizeof(wchar_t));
                ::MultiByteToWideChar(CP_ACP, 0, ImgPath, -1, (LPWSTR)pUnicode, unicodeLen);

                wstring infilename((wchar_t*)pUnicode);

                Bitmap* bmp = new Bitmap(infilename.c_str());
                UINT height = bmp->GetHeight();
                UINT width = bmp->GetWidth();
                cout << "width " << width << ", height " << height << endl;

                fprintf(fp, "%d", width);

                fputs(" ", fp);

                fprintf(fp, "%d", height);

                delete[] pUnicode;
                delete bmp;
                fputs("\n", fp);
            }
        } while (_findnext(handle, &findData) == 0);

        // 关闭搜索句柄

        _findclose(handle);    

        GdiplusShutdown(gdiplustoken);

        fclose(fp);

        system("Pause");

        return 0;
    }

    负样本描述文件生成C++伪码

    #include
    #include
    #include

    using namespace std;


    int main()
    {
        //输入文件目录
        char dir[256] = { 0 };
        cout << "Enter a directory: ";
        cin.getline(dir, 255);
        

        //TODO:检查文件目录路径是否合法,是否添加"\\*.*"
        char dirNew[261] = { 0 };
        strcpy_s(dirNew, dir);
        strcat_s(dirNew, "\\*.*");


        intptr_t handle;
        _finddata_t findData;

        handle = _findfirst(dirNew, &findData);
        if (handle == -1)        // 检查是否成功
        {
            cout << "no file" << endl;
            system("Pause");
            return 0;
        }
        //创建空白描述文件
        char filePath[256] = { 0 };
        strcpy_s(filePath, dir);
        strcat_s(filePath, "\\negdata.dat");
        FILE *fp = NULL;
        errno_t err = fopen_s(&fp, filePath, "w+");
        if (err != 0)
        {
            cout << "open err" << endl;
            system("Pause");
            return 0;
        }

        do
        {
            if (findData.attrib & _A_SUBDIR)
            {
                //if (strcmp(findData.name, ".") == 0 || strcmp(findData.name, "..") == 0)
                //    continue;

                //cout << findData.name << "\t

    \n";

                 在目录后面加上"\\"和搜索到的目录名进行下一次搜索
                //strcpy_s(dirNew, dir);
                //strcat_s(dirNew, "\\");
                //strcat_s(dirNew, findData.name);

                //listFiles(dirNew);
            }
            else
            {
                //cout << findData.name << "\n";
                //判断如果文件类型不是图片,则跳过
                string fileName(findData.name);
                string fileType = fileName.substr(strlen(findData.name) - 4);
                if (!(fileType == ".jpg" || fileType == ".jpeg" || fileType == ".jpe" || fileType == ".bmp"))
                    continue;

                //路径字符转宽字符
                char ImgPath[512] = { 0 };
                sprintf_s(ImgPath, "%s%s%s", dir, "\\", findData.name);

                cout << ImgPath << "\n";

                fputs(ImgPath, fp);

                fputs("\n", fp);
            }
        } while (_findnext(handle, &findData) == 0);

        _findclose(handle);    // 关闭搜索句柄

        fclose(fp);

        system("Pause");

        return 0;
    }

    第三步:生成样本文件vec

    第四步:训练分类器

    打开windows命令处理程序,红色字符输入,黑色为输出

    Microsoft Windows [版本 6.1.7601]

    版权所有 (c) 2009 Microsoft Corporation。保留所有权利。

    C:\Users\admin>cd /d G:\

    G:\>cd JLCZ\OpenCV\opencv-3.4.2-vc14_vc15\opencv\build\x64\vc14\bin\

    G:\JLCZ\OpenCV\opencv-3.4.2-vc14_vc15\opencv\build\x64\vc14\bin>

    opencv_createsamples.exe -info G:\JLCZ\MachineVision\03_CreateSamples\v0.2\posdata\posdata.dat -vec G:\JLCZ\MachineVision\03_CreateSamples\v0.2\airplane_wheel_0_2.vec -num 5206 -w 150 -h 150

    Info file name: G:\JLCZ\MachineVision\03_CreateSamples\v0.2\posdata\posdata.dat

    Img file name: (NULL)

    Vec file name: G:\JLCZ\MachineVision\03_CreateSamples\v0.2\airplane_wheel_0_2.vec

    BG  file name: (NULL)

    Num: 5206

    BG color: 0

    BG threshold: 80

    Invert: FALSE

    Max intensity deviation: 40

    Max x angle: 1.1

    Max y angle: 1.1

    Max z angle: 0.5

    Show samples: FALSE

    Width: 150

    Height: 150

    Max Scale: -1

    RNG Seed: 12345

    Create training samples from images collection...

    Done. Created 5206 samples

    G:\JLCZ\OpenCV\opencv-3.4.2-vc14_vc15\opencv\build\x64\vc14\bin>

    opencv_traincascade.exe -data G:\JLCZ\MachineVision\04_CreateClassifier\v0.2 -vec G:\JLCZ\MachineVision\03_CreateSamples\v0.2\airplane_wheel_0_2.vec -bg G:\JLCZ\MachineVision\03_CreateSamples\v0.2\negdata\negdata.dat -numPos 4900 -numNeg 4284 -numStages 15 -featureType LBP -w 150 -h 150 -minHitRate 0.995 -maxFalseAlarmRate 0.5

    过程中遇到的问题

    1.vs动态链接库指针报错,提示为空

    原因:参数错误,参数与值之间没有空格,添加空格就好了。

    2.cmd训练出现“Train dataset for temp stage can not be filled. Branch training terminated ..."

    解决:负样本图片路径错误,在描述文件中*.dat中修改就好了。

    3.在createsamples执行之后需要注意cmd返回的命令,生成了多少样本,以该样本数量为准。假设你有5206和样本,但是createsamples只创建了1000个,意味着vec文件中只存储了1000个,这是有问题的,参数应该与返回值相同。检查参数发现 正样本数量的参数应该是“-num”,而输入为“_num”则出现了这种情况。解决办法为重新确认参数,生成vec。

    4.opencv_traincascade.exe执行的参数,输入的正样本数量numPos需要小于实际得到的正样本数量,numPos+(numStages-1)*numPos*(1-minHitRate)《=准备的训练样本

    5.

    6.在控制台使用system(“pause”)不显示图像

    为什么opencv里面这个用system(“pause”)就不能载入图像?

    总结

    (这本是去年未完成的部分,可惜一直拖到现在,以至于想不起来具体总结的内容,下次使用这个训练器之后再来总结吧)

    引用

    1.OpenCV中Adaboost训练的经验总结

    2.物体检测正负样本的选择注意事项

    3.OpenCV样本训练经验

    4.正式使用opencv里的训练和检测 - opencv_createsamples、opencv_traincascade-2.4.11版本

    5.opencv训练自己的xml分类器以及如何获取opencv_createsamples.exe和opencv_traincascade.exe

  • 相关阅读:
    Redis-AOF持久化是怎样实现的
    「Qt中文教程指南」如何创建基于Qt Widget的应用程序(三)
    设计模式-11-模板模式
    分类树,我从2s优化到0.1s
    让人头痛的大事务问题到底要如何解决?
    买卖股票的最佳时机
    wsl不能启动 - 参考的对象类型不支持尝试的操作。
    redis 持久化
    Linux 中的 chroot 命令及示例
    ThreadLocal夺命11连问
  • 原文地址:https://blog.csdn.net/weixin_42364825/article/details/116722035