微小目标识别研究(2)——基于K近邻的白酒杂质检测算法实现

news2024/12/28 22:23:18

文章目录

      • 实现思路
      • 配置opencv
      • 位置剪裁
        • 实现代码
      • 自适应中值滤波
        • 实现代码
      • 动态范围增强
        • 实现代码
      • 形态学处理
        • 实现代码
      • 图片预处理效果
      • 计算帧差
        • 连续帧帧差法原理和实现代码
        • 实现代码
      • K近邻实现
        • 基本介绍
        • 实现代码
          • 这部分是手动实现的,并没有直接调用相关的库
          • 完整的代码——调用opencv的特定的库实现的
      • 实现过程
      • 参考

实现思路

  • 使用C++进行实现,开发平台是clion,并没有使用深度学习,使用opencv进行开发

在这里插入图片描述

配置opencv

  • 一开始就出来幺蛾子,之前装好的opencv怎么都找不到包,弄了一个小时,夹着红框后面的三句,告诉clion我安装的opencv包的具体位置,可能是因为我之前没有编译成功,直接间别人编译好的直接下载下来的,不过我为什么不用python上面的opencv那。

  • 在这里插入图片描述

位置剪裁

在这里插入图片描述

  • 相机位置固定,拍摄的位置固定,背景干扰很少,只需要保存红框右半部分的内容即可,左半部分去除。

实现代码

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
#include <fstream>

using namespace std;
using namespace cv;


struct RectPoint{
    int x,y;
    int width,height;
    RectPoint(int x,int y,int width,int height):x(x),y(y),width(width),height(height){}
};

void ClipImage(String ImagePath,RectPoint rect,String TargetPath){

    // 声明Mat保存图片
    Mat img;
    Rect m_select;
    string line;

    // 判定文件存在
    ifstream input_file(ImagePath);
    if (!input_file.is_open()) {
        cerr << "Could not open the file - '"
             << ImagePath << "'" << endl;
        return;
    }

    while(getline(input_file,line)){
        // 获取文件后缀名,并进行拼接成目标文件名
        string ImageName = line.substr(line.find_last_of('\\',line.size())+1);
        string TargetName = TargetPath;
        TargetName.append(ImageName);

        // 读取并剪裁图片到特定大小保存
        img = imread(line);
        m_select = Rect(rect.x,rect.y,rect.width,rect.height);
        Mat ROI = img(m_select);
        imwrite(TargetName,ROI);

    }
}

int main()
{
    String TargetImage = R"(E:\CProject\impurityDetection\ClippedImage\)";
    String SourceImage = R"(E:\CProject\impurityDetection\image\path.txt)";
    ClipImage(SourceImage,RectPoint(600,0,1780,1480),TargetImage);
    return 0;
}

自适应中值滤波

  • 自适应中值滤波,是为了去除照相机的噪声同时,又不损坏原来的杂质信息,这里使用自适应中值滤波,具体实现如下。

在这里插入图片描述

实现代码

void Convolution(Mat &SourceImage,vector<int> &pixels,int x,int y ,int border){


    for (int k = -border; k <= border; k++)
    {
        for (int l = -border; l <= border; l++)
        {
            pixels.push_back(SourceImage.at<uchar>(x+k, y+l));
        }
    }
}

Mat SelfAdaptMedianFilter(const Mat& SourceImage){
    // 复制原图
    Mat result = SourceImage.clone();
    // 掩膜大小为3
    int ksize = 3;
    int border = ksize /2 ;

    for (int i = border; i < SourceImage.rows - border; i++)
    {
        for (int j = border; j < SourceImage.cols - border; j++)
        {
            while(1){
                // 提取当前像素的邻域
                vector<int> pixels;
                Convolution(result, pixels, i, j, border);

                // 对邻域像素值进行排序
                sort(pixels.begin(), pixels.end());

                // 取中值作为当前像素的新值
                int median = pixels[pixels.size() / 2];
                int PMax = pixels[pixels.size() - 1];
                int PMin = pixels[0];

                //判定是否为异常值
                if (median == PMax || median == PMin) {
                    // 掩膜再加2,重新计算
                    ksize += 2;
                    if (ksize + 2 > Nmax) {
                        result.at<uchar>(i, j) = median;
                        break;
                    }else{
                        // 判定传入的值,是否出现越界的情况
                        if((i - ksize / 2 < 0 || i + ksize / 2 < SourceImage.cols) ||
                             j - ksize / 2 < 0 || j + ksize / 2 > SourceImage.rows)
                            result.at<uchar>(i,j) = (PMax + PMin) / 2;
                    }
                } else {
                    //中值不是极值,判定原像素点是不是极值,然后在进行输出
                    if (result.at<uchar>(i, j) == PMax || result.at<uchar>(i, j) == PMin)
                        result.at<uchar>(i,j) = median;
                    break;
                }

            }
        }
    }

    return result;
}

动态范围增强

  • 这里就是图像的直方图均衡化,使得整个模型的图片颜色对比度更大,显示出更多的细节。

实现代码

   //对图片进行直方图均衡化,凸显出前后差异
        Mat equalized;
        // 转成二值化,并变为直方图均衡化
        cvtColor(ROI, equalized, COLOR_BGR2GRAY);
        equalizeHist(equalized, equalized);

形态学处理

  • 膨胀操作 :通过增加图像中的物体的像素数量,使得物体的大小和面积增加,边缘变得更加明显,改变了原来字体粗细

在这里插入图片描述

  • 腐蚀操作 :在卷积核大小中对图片进行卷积。取图像中(3 * 3)区域内的最小值。可以消除图像中的毛刺和噪声。改变了原来字体粗细
    在这里插入图片描述
  • 开运算 :先腐蚀运算,在进行膨胀运算。在不损害字体信息的情况下,去除了噪声和毛刺。通过调整卷积可以进一步减少噪声

在这里插入图片描述

  • 闭运算 :先膨胀运算,在进行腐蚀运算。在不去除任何噪声的情况下,补全了缺失的信息。

在这里插入图片描述

  • 这里选择开运算,具体实现代码如下,别的可以参考知乎链接

实现代码

   // 对图像进行开运算,
        Mat morphologied;
        int size = 3;
        // shape是内核的形状,size是内核的尺寸,锚点的位置,对于矩形来说,全部都是 1 ,不用调整
        cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT,
                                                    cv::Size(2*size + 1, 2*size+1),
                                                    cv::Point(size, size));
        cout<<element<<endl;
        morphologyEx(equalized, morphologied, cv::MORPH_OPEN, element);

图片预处理效果

在这里插入图片描述

计算帧差

  • 主要有两种方法,分别是静态参考帧差分法和连续帧帧差法,下图为两个效果,很明显的可以就看到连续帧帧差法的效果更好,常见的静态帧是将多个帧进行平均,然后以平均之后的帧作为参考帧,然后后面每帧都是和当前帧作比较,没有动作的连续性。

在这里插入图片描述
在这里插入图片描述

连续帧帧差法原理和实现代码

  • 实现原理如下图
    在这里插入图片描述

  • 使用absdiff函数,具体描述如下,计算frame1和frame2的帧差,然后将结果保存到framediff中

cv::Mat frameDiff;
cv::absdiff(frame1, frame2, frameDiff);

实现代码


//计算帧差,并将最终的结果进行返回
Mat DifFrame(Mat PreFrame,Mat CurFrame,Mat NextFrame){

    //计算帧差
    Mat FrameDiffPre , FrameDiffNext;
    absdiff(PreFrame,CurFrame,FrameDiffPre);
    absdiff(CurFrame,NextFrame,FrameDiffNext);

    // 分别进行二值化,设定二值化的阈值
    Mat BinImgPre , BinImgNext;
    threshold(FrameDiffPre,BinImgPre,128,255,cv::THRESH_BINARY);
    threshold(FrameDiffNext,BinImgPre,128,255,cv::THRESH_BINARY);

    // 将图片进行与运算,然后将结果进行输出
    Mat ResFrame;
    bitwise_and(FrameDiffPre,FrameDiffNext,ResFrame);

    //返回最终处理过后的帧
    return ResFrame;

}

// 计算帧差的外部函数,读取原来处理过的文件,计算所有图片的帧差
void AllDifFrame(const string& ImagePath,const string& TargetPath){

    // 保存文件名
    string line;
    // 判定文件存在
    ifstream input_file(ImagePath);
    if (!input_file.is_open()) {
        cerr << "Could not open the file - '"
             << ImagePath << "'" << endl;
        return;
    }

    // 需要提前读取两个,然后依次往后进行迭代
    Mat PreFrame,CurFrame,NextFrame;
    getline(input_file,line);
    PreFrame = imread(line);
    // 读取下一个需要处理的帧
    getline(input_file,line);
    CurFrame = imread(line);

    // 逐行读取文件名
    while(getline(input_file,line)){

        // 当前需要处理的帧
        NextFrame = imread(line);

        // 获取文件后缀名,并进行拼接成目标文件名
        string ImageName = line.substr(line.find_last_of('\\',line.size())+1);
        string TargetName = TargetPath;
        TargetName.append(ImageName);


        // 三帧相邻出进行帧差运算,算出的帧差进行按位相与运算
        Mat TempFrame;
        absdiff(PreFrame,CurFrame,PreFrame);
        absdiff(CurFrame,NextFrame,TempFrame);
        bitwise_and(PreFrame,TempFrame,TempFrame);

        //计算计算过进行保存
        imwrite(TargetName,TempFrame);

        // 三帧往前进行迭代
        PreFrame = CurFrame;
        CurFrame = NextFrame;

    }

}

K近邻实现

基本介绍

  • 思路 : 将样本在特征空间中,根据一定的分类方法,归类于与该样本最相似的K个样本中大多数的那一类。需要一部分已经标注过的样本,然后将未标注过的样本进行分类。

  • 特点

    • 对于异常目标的容忍度比较高,因为不依靠参数直接分类,而是寻找多个近邻目标的联合分类
    • 支持多分类,对于类域交叉重叠依然有较好的分类能力,因为白酒中的杂质在特征上有很强的相似性,在类域上会有很强的重叠性
    • 训练过程复杂度低:实时更新样本数据集,再识别一种杂质之后,将该样本增加到样本数据中,使得分类效果更加稳定。
  • 实现过程

    • 准备数据集:需要将要分类的目标的特征向量与一些已知分类的目标的特征向量组成一个数据集。
    • 特征提取:使用OpenCV库提取目标的特征向量。
      • 注意 这里提取的特征是完整的一幅图片,因为我这里已经对图片进行了预处理,让特征很明显,所以不需要进行特征提取
    • 训练模型:使用KNN算法对数据集进行训练,生成分类器。
    • 分类:使用训练好的分类器对新的目标进行分类。
  • 补充

    • 针对图片进行的KNN分类,是一种高维数据,KNN算法可以使用降维技术,来提高效率和准确率,比如说主成分分析(PCA)
    • KNN,适用于小数据集和低维度数据集
    • 可以通过对距离加权的方式,来提升预测的准确率

实现代码

这部分是手动实现的,并没有直接调用相关的库
// 定义一个结构体,用于保存数据点和标签
struct DataPoint {
    vector<double> features;   // 特征数据
    int label;                      // 标签
};

// 计算欧氏距离
double euclideanDistance(const vector<double>& v1, const vector<double>& v2) {
    double dist = 0.0;
    for (int i = 0; i < v1.size(); i++) {
        dist += pow((v1[i] - v2[i]), 2);
    }
    return sqrt(dist);
}

// 预测函数,返回预测标签
int predict(const vector<DataPoint>& trainData, const vector<double>& testData, const int k) {

    // 对于测试集,计算每一个测试样本和数据集中的,所有样本的欧式距离
    vector<pair<int, double>> distances;   // 存储距离和标签
    for (const auto & i : trainData) {
        double distance = euclideanDistance(i.features, testData);
        distances.emplace_back(i.label, distance);
    }

    // 根据结果进行排序,指定排序函数,仅仅对距离进行比较
    sort(distances.begin(), distances.end(), [](pair<int, double> a, pair<int, double> b) {
        return a.second < b.second;
    });

    //   获取最后k个样本最高的分类类别
    vector<int> kLabels(k);
    for (int i = 0; i < k; i++) {
        kLabels.push_back(distances[i].first);
    }

    // 获取数量类别最大的分类
    int maxLabel = 0;
    int maxCount = 0;
    for (auto iter = kLabels.begin();iter != kLabels.end();iter ++){
        int times = count(kLabels.begin(),kLabels.end(),*(iter));
        if(times > maxCount){
            maxCount = times;
            maxLabel = *(iter);
        }
    }

    return maxLabel;

}

完整的代码——调用opencv的特定的库实现的
#include<opencv2/opencv.hpp>
#include<iostream>
#include <fstream>

using namespace std;
using namespace cv;
using namespace cv::ml;


// 读取图片并形成数据集
void KNNTraning(const string& ImagePath, Mat& Images, Mat& Labels) {
    /*
     * 将图片转成一维数据,并保存在MAT中
     * path: 读取文件需要使用的路径
     * images:保存所有图片的数据集
     * labels:对应每一张图片的标签
     */
    // 声明Mat保存图片
    Mat img;
    string line;
    string ImgLabel;
    int flag = 0;

    // 判定文件存在
    ifstream input_file(ImagePath);
    if (!input_file.is_open()) {
        cerr << "Could not open the file - '"
             << ImagePath << "'" << endl;
        return;
    }

    while(getline(input_file,line)) {

        // 获取文件后缀名,并进行拼接成目标文件名

        string ImageName = line.substr(line.find_last_of('\\',line.size())+1);
        if(ImageName != ImgLabel)
            flag ++;

        // 将图片转成一维的数据,并合并到一个大的mat中
        img = imread(line, cv::IMREAD_GRAYSCALE);
        Images.push_back(img.reshape(0,1));
        Labels.push_back(flag);
    }

    // 将Mat转变成数据集
    Images.convertTo(Images,CV_32F);
    Images = Images / 255;
    int nImgCount = Images.rows;
    int nTrainCount = nImgCount / 4 * 3;
    Mat trainData,trainLabels;
    trainData = Images(Range(0,nTrainCount),Range::all());
    trainLabels = Labels(Range(0,nTrainCount),Range::all());
    Ptr<TrainData> tData = TrainData::create(trainData, ROW_SAMPLE, trainLabels);

    // 训练KNN模型
    Ptr<KNearest> model = KNearest::create();
    // 设定k值,最终选取是哪个类别
    model->setDefaultK(5);
    // 设置分类还是回归,true为分类,false为回归
    model->setIsClassifier(true);
    // 设置训练数据,进行训练。就相当于将前面的标注数据和测试数据进行了一个训练绑定
    model->train(tData);
    model->save("model.xml");
}

float predict(string ImgPath,string modelPath){

    // 加载模型
    cv::Ptr<cv::ml::KNearest> knn = cv::ml::StatModel::load<cv::ml::KNearest>(modelPath);

    //读取图片
    Mat imgTest = imread(ImgPath);
    imgTest.convertTo(imgTest,CV_32F);
    imgTest = imgTest / 255;
    imgTest = imgTest.reshape(0,1);

    //输出预测结果
    float ret = knn->predict(imgTest);

    return ret;
}

实现过程

参考

  • Opencv 图像处理之膨胀与腐蚀 【https://zhuanlan.zhihu.com/p/110330329】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/401481.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

千川投放50问(完)!如何跑出高投产?

第四十一问&#xff1a;计划初期成本很高&#xff0c;是否要关掉重新跑&#xff1f;首先看一下是不是初期回传延迟导致的成本偏高。如果成本没有高的&#xff0c;不建议暂停&#xff0c;先观察一段时间数据&#xff0c;给它一点学习时间。当系统积累过足够的模型之后&#xff0…

08-Oracle游标管理(定义,打开、获取数据及关闭游标)

目标 1.确定何时需要显示游标2.声明、打开和关闭显示游标3.从显示游标中提取数据4.了解与游标有关的属性5.使用游标FOR循环检索游标中的数据6.在游标FOR循环的子查询中声明游标7.评估使用逻辑运算符结合在一起的布尔条件游标 1、在使用一个PL/SQL块来执行DML语句或只返回一行结…

2月更新 | Visual Studio Code Python

我们很高兴地宣布&#xff0c;2023年2月版 Visual Studio Code Python 和 Jupyter 扩展现已推出&#xff01;此版本包括以下改进&#xff1a;从激活的终端启动 VS Code 时的自动选择环境 使用命令 Python: Create Environmen 时可选择需求文件或可选依赖项 预发布&#xff1a;改…

性能优化之HBase性能调优

HBase是Hadoop生态系统中的一个组件&#xff0c;是一个分布式、面向列存储的内存型开源数据库&#xff0c;可以支持数百万列&#xff08;MySQL4张表在HBase中对应1个表&#xff0c;4个列&#xff09;、超过10亿行的数据存储。可用作&#xff1a;冷热数据分离HBase适合作为冷数据…

坐标系、视窗体(裁剪区域),存储着色器

坐标系 两种常见的投影/坐标系&#xff0c;正交和透视&#xff0c;实际上只是特定的4x4变换矩阵。啥都不规定默认的就是-1.0~1.0的笛卡尔坐标系。 正交&#xff1a; 在opengl的核心框架下&#xff0c;没有提供任何内置渲染管线&#xff0c;所以在提交一个几何图形进行渲染之前&…

Ubuntu Protobuf 安装(测试有效)

安装流程 下载软件 下载自己要安装的版本&#xff1a;https://github.com/protocolbuffers/protobuf 下载源码编译&#xff1a; 系统环境&#xff1a;Ubuntu16&#xff08;其它版本亦可&#xff09;&#xff0c;Protobuf-3.6.1 编译源码 cd protobuf# 当使用 git clone 下来的…

【C语言】操作符详解总结(万字)

操作符详解1. 操作符分类2. 算术操作符3. 移位操作符3.1 整数的二进制是怎么形成的3.2 左移操作符3.3 右移操作符4. 位操作符5. 赋值操作符6. 单目操作符6.1 单目操作符介绍6.2 sizeof 和 数组7. 关系操作符8. 逻辑操作符9. 条件操作符9.1 练习19.2 练习210. 逗号表达式11. 下标…

【Vue】vue2导出页面内容为pdf文件,自定义选中页面内容导出为pdf文件,打印选中页面内容,预览打印内容

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、安装html2canvas和jspdf二、导出pdf使用步骤1.在utils文件夹下创建htmlToPdf.js2.在main.js中引入3.在页面中使用三、打印预览1. 引入print-js2.页面中impor…

PG数据库实现高可用方案(包括通用型方案Corosync+pacemaker协作)

实现高可用方案首先了解一下高可用集群高可用&#xff1a;透明切换&#xff0c;故障切换&#xff0c;连接管理器/集群管理器pgpool-Ⅱ&#xff1a;连接池、复制、负载均衡功能PatroniCorosyncpacemaker高可用解决方案Corosyncpacemakercorosyncpacemaker架构协作资源分配&#…

功耗降低99%,Panamorph超清VR光学架构解析

近期&#xff0c;投影仪变形镜头厂商Panamorph获得新型VR显示技术专利&#xff08;US11493773B2&#xff09;&#xff0c;该专利方案采用了紧凑的结构&#xff0c;结合了Pancake透镜和光波导显示模组&#xff0c;宣称比传统VR方案的功耗、发热减少99%以上&#xff0c;可显著提高…

通讯录(C++实现)

系统需求通讯录是一个可以记录亲人、好友信息的工具。本章主要利用C来实现一个通讯录管理系统系统中需要实现的功能如下:添加联系人:向通讯录中添加新人&#xff0c;信息包括&#xff08;姓名、性别、年龄、联系电话、家庭住址&#xff09;最多记录1000人显示联系人:显示通讯录…

【006】Redis主从/哨兵/分片集群Docker搭建

项目源码合集 https://gitee.com/qiuyusy/small-project-study 搭建过程疯狂踩坑,记录一下希望各位少走弯路 目录主从搭建配置文件redis.conf运行容器测试优化哨兵集群配置文件运行容器测试代码读写分离分片集群mkdir -p /opt/docker/redis_study/redis_0/conf mkdir -p /opt/…

藏经阁(五)温湿度传感器 SHT3x-DIS 手册 解析

文章目录芯片特性芯片内部框图芯片引脚定义芯片温湿度范围芯片寄存器以及时序讲解信号转换公式芯片特性 湿度和温度传感器完全校准&#xff0c;线性化温度补偿数字输出供电电压范围宽&#xff0c;从2.4 V到5.5 VI2C接口通讯速度可达1MHz和两个用户可选地址典型精度 2% RH和 0.…

Python 二分查找:bisect库的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

优先级队列详解

目录优先级队列简介关于堆为什么得用完全二叉树用堆来实现优先级队列插入/删除/获取优先级最高的元素模拟实现使用PriorityQueue的注意事项PriorityQueue常用接口优先级队列的构造方法优先级队列简介 PriorityQueue&#xff0c;即优先级队列。它可以保证每次出出来的数据是队列…

探究:kafka生产者/消费者与多线程安全

目录 1. 多线程安全 1.1. 生产者是多线程安全的么&#xff1f; 1.1. 消费者是多线程安全的么&#xff1f; 2. 消费者规避多线程安全方案 2.1. 每个线程维护一个kafkaConsumer 2.2. [单/多]kafkaConsumer实例 多worker线程 2.3.方案优缺点对比 1. 多线程安全 1.1. 生产…

我的Git stash不小心清空了怎么办,提了代码能反悔吗

文章目录1. 前言2. git stash清空场景2. git stash clear后如何还原3.Git撤销已经推送(push)至远端仓库的信息1. 前言 本文总结的知识很实用哈&#xff0c;虽然是git工具的不常用操作&#xff0c;但是绝对不是冷知识&#xff0c;学会可以从会用git升级到git高手。 主要是两种场…

Centos7 安装Mysql8.0

1、到指定目录下下载安装包[rootVM-0-14-centos ~]# cd /usr/local/src2、下载mysql8[rootVM-0-14-centos src]# wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz3、解压mysql8, 通过xz命令解压出tar包&#xff0c; 然后通过t…

KDZD耐电压高压击穿强度测试仪

一、技术参数 01、输入电压&#xff1a; 交流 220 V。 02、输出电压&#xff1a; 交流 0--50KV ; 直流 0—50kv 。 03、电器容量&#xff1a;3KVA。 04、高压分级&#xff1a;0—50KV&#xff0c;&#xff08;全程可调&#xff09;。 05、升压速率&#xff1a;0.1KV/s-…

c++11 标准模板(STL)(std::unordered_map)(八)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…