几何尺寸与公差论坛

 找回密码
 注册
查看: 244|回复: 4

振动盘识别正反,摞起来得不能取,误识别都是咋解决得

  [复制链接]
发表于 2025-4-11 16:48:06 | 显示全部楼层 |阅读模式
常见问题根源
问题类型        根本原因
❌ 正反识别错误        特征不明显、图像角度差、光照不均、算法不稳定
❌ 摞叠误识别        上下两个件贴合太紧,像“一个件”,导致被识别成 1 个
❌ 误识别/漏识别        背景干扰、工件重叠、相机/光源不合适
✅ 解决方案(分层处理)
1️⃣ 硬件层面优化
✅ 光源:

    用低角度环形光增强边缘阴影,增强正反面轮廓差异

    用偏振光+滤镜抑制反射,提升表面细节

    摞件常在边缘有“双轮廓”或“阴影跳变”,低角度光最敏感

✅ 相机:

    多加一台相机,做上视 + 斜视或 3D相机,可增强深度判断

    高分辨率能让边缘误差降低

2️⃣ 图像算法层面优化
✅ 正反识别:

    用模板匹配 + 特征提取(如SIFT/ORB) 判断图案、槽口、凸台位置

    用训练好的**CNN小模型(边缘分类)**做正反面分类(轻量、稳定)

✅ 摞件判断:

    特征点数量异常多

    边缘线有“两圈”

    轮廓灰度突变大(可用 Sobel / Laplacian)

    边缘厚度 / 侧边阴影跳变

3️⃣ 业务逻辑防呆

    📏 设置高度阈值(拍图判断厚度,判断是不是摞了)

    📸 多角度拍照合成判断

    🧠 识别后带置信度分数,低置信度的不抓取,打入拒料区

✨ Bonus:AI识别方向 & 摞叠(轻量 CNN)
场景        建议模型
正反识别        用 MobileNetv2 / EfficientNet 小模型二分类(正 vs 反)
摞叠判断        用改进版的 YOLO / SSD 检测出**“2件摞一起”的目标区域**
 楼主| 发表于 2025-4-11 16:48:30 | 显示全部楼层
总结一句话:
硬件+光源先把“问题拍清楚”,图像+AI再“看得准”,逻辑+软件兜底处理异常。
 楼主| 发表于 2025-4-11 16:49:24 | 显示全部楼层
架构图(模块流程)

[相机采图] or [图像输入]
        ↓
[OpenCV 预处理 → findContours]
        ↓
[轮廓筛选 → 自动裁剪 ROI]
        ↓
[推理引擎加载 ONNX 模型(C++)]
        ↓
[分类结果(正/反/摞叠)+位置]
        ↓
[视觉标注输出 or 工控 PLC 通信]
 楼主| 发表于 2025-4-11 16:49:54 | 显示全部楼层
整体结构设计

我们将封装成两个模块:

src/
├── PartClassifier.hpp     👈 主推理类头文件
├── PartClassifier.cpp     👈 实现文件
├── utils.hpp              👈 辅助函数(图像裁剪等)
demo/
└── main.cpp               👈 示例调用程序
 楼主| 发表于 2025-4-11 16:50:21 | 显示全部楼层
1. PartClassifier.hpp

#pragma once
#include <string>
#include <opencv2/opencv.hpp>

class PartClassifier {
public:
    explicit PartClassifier(const std::string& model_path);
    std::string classify(const cv::Mat& roi);
    void detectAndClassify(const cv::Mat& input, cv::Mat& output, bool draw_result = true);

private:
    struct Impl;
    std::shared_ptr<Impl> impl_;
};

&#9989; 2. PartClassifier.cpp (支持 ONNX 推理 + OpenCV ROI)

#include "PartClassifier.hpp"
#include <onnxruntime_cxx_api.h>
#include <algorithm>
#include <iostream>

struct PartClassifier::Impl {
    Ort::Env env;
    Ort::Session session;
    Ort::MemoryInfo memory_info;
    std::vector<int64_t> input_dims;
    const char* input_names[1] = {"input"};
    const char* output_names[1] = {"output"};

    Impl(const std::string& path)
        : env(ORT_LOGGING_LEVEL_WARNING, "part"),
          session(env, path.c_str(), Ort::SessionOptions{nullptr}),
          memory_info(Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)),
          input_dims{1, 3, 224, 224} {}

    std::string infer(const cv::Mat& roi) {
        cv::Mat img;
        cv::resize(roi, img, cv::Size(224, 224));
        img.convertTo(img, CV_32F, 1.0 / 255);
        std::vector<float> input_tensor(3 * 224 * 224);

        for (int c = 0; c < 3; ++c)
            for (int y = 0; y < 224; ++y)
                for (int x = 0; x < 224; ++x)
                    input_tensor[c * 224 * 224 + y * 224 + x] = img.at<cv::Vec3f>(y, x)[c];

        Ort::Value input_tensor_ort = Ort::Value::CreateTensor<float>(
            memory_info, input_tensor.data(), input_tensor.size(), input_dims.data(), 4);

        auto output = session.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor_ort, 1, output_names, 1);
        float* scores = output[0].GetTensorMutableData<float>();
        int label = std::max_element(scores, scores + 3) - scores;

        static std::vector<std::string> labels = {"Front", "Back", "Stacked"};
        return labels[label];
    }
};

PartClassifier::PartClassifier(const std::string& model_path)
    : impl_(std::make_shared<Impl>(model_path)) {}

std::string PartClassifier::classify(const cv::Mat& roi) {
    return impl_->infer(roi);
}

void PartClassifier::detectAndClassify(const cv::Mat& input, cv::Mat& output, bool draw_result) {
    cv::Mat gray, bin;
    cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY);
    cv::adaptiveThreshold(gray, bin, 255, cv::ADAPTIVE_THRESH_MEAN_C,
                          cv::THRESH_BINARY_INV, 21, 5);

    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(bin, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

    output = input.clone();
    for (const auto& contour : contours) {
        if (cv::contourArea(contour) < 500) continue;
        cv::Rect bbox = cv::boundingRect(contour);
        if (bbox.width < 20 || bbox.height < 20) continue;

        cv::Mat roi = input(bbox).clone();
        std::string label = impl_->infer(roi);

        if (draw_result) {
            cv::rectangle(output, bbox, cv::Scalar(0, 255, 0), 2);
            cv::putText(output, label, bbox.tl(), cv::FONT_HERSHEY_SIMPLEX, 0.6,
                        label == "Stacked" ? cv::Scalar(0, 0, 255) : cv::Scalar(255, 255, 0), 2);
        }
    }
}

&#9989; 3. 示例程序 main.cpp

#include "PartClassifier.hpp"

int main() {
    PartClassifier classifier("part_classifier.onnx");

    cv::Mat image = cv::imread("part_test.jpg");
    if (image.empty()) {
        std::cerr << "&#10060; 图像读取失败!" << std::endl;
        return -1;
    }

    cv::Mat result;
    classifier.detectAndClassify(image, result);

    cv::imshow("Result", result);
    cv::waitKey(0);
    return 0;
}

&#128230; 4. CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(PartClassifierDemo)

set(CMAKE_CXX_STANDARD 17)

find_package(OpenCV REQUIRED)
find_package(onnxruntime REQUIRED)

include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(${ONNXRUNTIME_INCLUDE_DIRS})

add_executable(demo main.cpp PartClassifier.cpp)
target_link_libraries(demo ${OpenCV_LIBS} onnxruntime)

&#9989; 构建 & 运行

mkdir build && cd build
cmake ..
make
./demo

&#129504; 可复用 API 一览

PartClassifier classifier("model.onnx");

// 直接裁剪一个 ROI 推理
std::string label = classifier.classify(cv::Mat roi);

// 自动识别图像中多个零件并标注
classifier.detectAndClassify(cv::Mat input, cv::Mat& output);
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|小黑屋|几何尺寸与公差论坛

GMT+8, 2025-4-25 19:36 , Processed in 0.038666 second(s), 21 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表