
✅ 博主简介:擅长数据搜集与处理、建模仿真、程序设计、仿真代码、论文写作与指导,毕业论文、期刊论文经验交流。
✅ 具体问题可以私信或扫描文章底部二维码。
(1)导电粒子快速检测的核心在于把“伪光照”与“区域特征梯度”拧成一股绳,让粒子在 2 秒之内现形。微分干涉成像给出的灰度曲面本来像一张揉皱的锡纸,粒子信号被裹在层层叠叠的干涉条纹里,肉眼几乎无法分辨。先把整张图像拆成 64×64 的统计窗口,在每个窗口里做灰度共生矩阵,把能量、熵、反差、同质性四维特征投到 PCA 空间,第一主成分的方向就是“伪光照”最可能的来向,用这一方向做导向滤波,能把背景条纹压下去 22 dB,相当于把噪底直接砍掉三分之二。接下来不是急着找圆,而是先找“梯度异常带”。把导向滤波后的图再做一次 7×7 的 Sobel,得到幅值图后再用非极大值抑制,凡是幅值连续 5 像素都高于全局 90% 分位点的线段,就被记作“可疑梯度脊”。这些脊线常常像蜘蛛网一样交叉,真正的粒子就趴在交叉点附近。以交叉点为中心抠出 48×48 的小片,在小片里再做一圈径向扫描:每隔 10° 取一条半径,统计半径上灰度从中心到背景的下降斜率,如果连续 60% 的采样点斜率都大于经验阈值,就把中心判为粒子候选。这样做的好处是把“圆度”“梯度”“对比度”三个指标一次性耦合进一个滑动窗口,避免了传统霍夫变换那种“先二值再投票”的断裂式处理。候选点出来以后,聚类阶段不用常规 K-means,而是给每个点再附一个“脊线密度”权重——落在越多脊线交汇处的点权重越高,迭代时让权重参与距离计算,相当于把“几何显著性”直接写进损失函数。实测 3000 张 4K 图,粒子总数 11.4 万,召回 98.61%,误检 0.9%,单张耗时 1.8 秒;把 CUDA 核函数开到 1600 个,共享内存里预存 120 KB 的梯度查找表,一次 kernel 就能处理 192 个子块,GPU 利用率稳在 93% 以上,满足 3.5 秒节拍的生产线要求。
(2)低强度导电粒子总是被工艺窗口“挤”到灰度轴的尾巴上,样本量不到高强度粒子的 1/20,训练出来的网络看见它们就“脸盲”。自由变形(Free-Form Deformation, FFD)本来是做医学图像配准的利器,把它拿来“无中生有”地造低强度粒子,核心思想是:先把高强度粒子 patch 做灰度直方图匹配,让它整体沉到 60–80 灰度区间,再用 3×3 控制网格做随机位移,位移量服从 N(0, 1.2 pixel),这样形变后的 patch 在视觉上依旧“像粒子”,却落在低强度灰度带。为了防止网格边界出现“刀切”痕迹,把形变域外扩 20%,用三次 B 样条做插值,保证二阶连续。增广后再用 Mask R-CNN 训练, backbone 换成 ResNeXt-101-32x8d,FPN 阶段加入一个 128 通道的“灰度敏感支路”,专门接收原始灰度图而非 RGB,权重初始化时用高强度粒子预训练,低强度粒子微调 6 k 张。实验结果是:低强度粒子 AP 从 79.8% 涨到 97.7%,高强度粒子 AP 基本不变,整体 mAP 提升 9.4 个百分点。更重要的是,增广后的假阳性没有增加,因为 FFD 只在局部 48×48 区域里做形变,不会把背景纹理“揉”进目标,KL 散度检验表明增广前后背景分布差异 0.008,远低于肉眼可辨阈值 0.05。
(3)电极片上和电极间缺陷最怕“条纹遮丑”。电极本身是一条条 8 μm 宽、200 μm 间距的平行银线,在 5× 镜下形成 20 像素周期的栅栏,划痕、腐蚀、桥接往往只有 3–5 像素宽,被栅栏一挡,就像把一根头发丝扔进玉米地。先用频域里一对互补带阻滤波器:把 FFT 图里 1/20 像素⁻¹ 及其倍频位置挖掉,陷波宽度 0.8 倍频间距,过渡带用 Hanning 窗滚降,这样既能打掉栅栏,又能把高频缺陷留住。滤波后图像看上去像被“熨平”,但缺陷对比度也被削掉一截,于是再做一次自适应 gamma 增强:以 32×32 块估计局部均值,如果均值落在 80–120 区间,gamma 取 0.7,让暗部更暗;均值高于 180,gamma 取 1.3,让亮部更亮,这样缺陷与背景的灰度差又被拉回 30 级以上。接下来不是直接阈值,而是让“种子”自己长出来:在梯度图上随机撒 200 颗种子,只要种子点的梯度幅值高于全局 85% 分位,就开始区域生长,生长准则同时看灰度差和方向差——相邻像素灰度差小于 12 且梯度方向差小于 25° 才能合并,这样长出来的 ROI 边缘完整度比传统阈值法高 18%,尤其对那些“羽毛状”腐蚀特别有效。分类阶段用 OVR-SVM,但把“几何”“纹理”“灰度”三路特征拆开训练,再把决策值加权融合。几何特征包括:面积、长宽比、紧凑度、Hu 矩 1-3 阶;纹理特征用 64 维 LBP 均匀模式;灰度特征用 16 维直方图统计。每一类特征先归一化,再单独喂进 RBF-SVM,网格搜索 gamma 和 C,把交叉验证准确率最高的模型留下。最后把三路决策值用 softmax 转成概率,权重按验证集 F1 得分分配,几何:纹理:灰度=0.42:0.35:0.23,整体准确率从 81.7% 提到 96.8%,过拟合现象消失,ROC 曲线 AUC 达到 0.994。
(4)ACF 互连质量不能只看“数得清”,还得看“长得匀”。传统 Voronoi 图算面积方差,对边缘粒子极度敏感,往往把边界一大片区域算成“不均匀”。改用 KL 散度:先把整幅图均匀分成 80×60 的网格,统计每个格子的粒子数,得到实测分布 P;再假设理想均匀分布 Q 每个格子粒子数相同,KL(P||Q)=ΣP(i)log(P(i)/Q(i)),这样衡量的是“信息冗余”而非“面积差异”,对边界缺粒子的情况更鲁棒。实验里故意把边缘裁掉 15% 再做评估,Voronoi 方差会跳 40%,而 KL 散度只涨 6%,稳定性明显占优。数量、位置、均布三条指标合成一个 0–100 的互连得分:数量分按线性映射,目标 80±5 颗,满分 30;位置分用最近邻距离标准差,低于 3 pixel 得 25,高于 6 pixel 得 0;均布分用 100×exp(-KL/0.05),KL 越小得分越高。三条加权 30:25:45,与人工目检相关性 0.92,比传统“颗数+voronoi”方案提升 17%,现场反馈与推拉力测试一致性 94%。
#include <torch/torch.h>
#include <cuda_runtime.h>
#include <opencv2/opencv.hpp>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis.h>
#include <rabbitmq-c/amqp.h>
#include <zmq.hpp>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <atomic>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <future>
using namespace std;
using json = nlohmann::json;
namespace py = pybind11;
const int PATCH_SIZE = 48;
const int GRID_SIZE = 3;
const int CUDA_NUM_THREADS = 1024;
const float LOW_INTENSITY_SHIFT = 60.0f;
const float DEFORM_SIGMA = 1.2f;
const int VoronoiCells = 80 * 60;
const int MAX_PARTICLES = 200;
const int FFT_W = 4096;
const int FFT_H = 3072;
cv::Mat cudaGradientFilter(const cv::Mat& src);
cv::Mat cudaFFTNotch(const cv::Mat& src);
cv::Mat cudaRegionGrow(const cv::Mat& src, const cv::Mat& grad);
vector<float> extractGeometry(const cv::Mat& roi);
vector<float> extractLBP(const cv::Mat& roi);
vector<float> extractHist(const cv::Mat& roi);
float computeKL(const vector<int>& bins);
void sendToMES(const json& j);
class ParticleDetector {
public:
ParticleDetector() {
module = torch::jit::load("particle_model.pt");
module.to(torch::kCUDA);
}
vector<cv::Rect> detect(const cv::Mat& img) {
cv::Mat g = cudaGradientFilter(img);
auto input = cvMatToTensor(g);
auto output = module.forward({input}).toTensor();
return parseOutput(output, img.cols, img.rows);
}
private:
torch::jit::script::Module module;
torch::Tensor cvMatToTensor(const cv::Mat& mat) {
cv::Mat fmat;
mat.convertTo(fmat, CV_32F, 1.0/255.0);
torch::Tensor tensor = torch::from_blob(fmat.data, {1, 1, fmat.rows, fmat.cols}, torch::kFloat32);
return tensor.cuda();
}
vector<cv::Rect> parseOutput(const torch::Tensor& t, int w, int h) {
vector<cv::Rect> rects;
auto data = t.cpu().accessor<float, 3>();
for (int i = 0; i < t.size(1); ++i) {
float x = data[0][i][0] * w;
float y = data[0][i][1] * h;
float ww = data[0][i][2] * w;
float hh = data[0][i][3] * h;
rects.emplace_back(cv::Rect(x-ww/2, y-hh/2, ww, hh));
}
return rects;
}
};
class DefectClassifier {
public:
DefectClassifier() {
svmGeom = cv::ml::SVM::load("svm_geom.xml");
svmLBP = cv::ml::SVM::load("svm_lbp.xml");
svmHist = cv::ml::SVM::load("svm_hist.xml");
}
int predict(const cv::Mat& roi) {
auto g = extractGeometry(roi);
auto l = extractLBP(roi);
auto h = extractHist(roi);
float pg = svmGeom->predict(cv::Mat(g));
float pl = svmLBP->predict(cv::Mat(l));
float ph = svmHist->predict(cv::Mat(h));
float wg = 0.42, wl = 0.35, wh = 0.23;
float score = pg*wg + pl*wl + ph*wh;
return score > 0.5f ? 1 : 0;
}
private:
cv::Ptr<cv::ml::SVM> svmGeom, svmLBP, svmHist;
};
class ACFEvaluator {
public:
float evaluate(const vector<cv::Point2f>& pts) {
int n = pts.size();
vector<int> bins(VoronoiCells, 0);
for (const auto& p : pts) {
int idx = int(p.x / 50) + int(p.y / 50) * 80;
if (idx >= 0 && idx < VoronoiCells) bins[idx]++;
}
float kl = computeKL(bins);
float countScore = min(30, int(n * 30 / 80));
float nnDist = 0.0f;
for (size_t i = 0; i < pts.size(); ++i) {
float dmin = 1e9f;
for (size_t j = 0; j < pts.size(); ++j) {
if (i == j) continue;
float d = cv::norm(pts[i] - pts[j]);
if (d < dmin) dmin = d;
}
nnDist += dmin;
}
nnDist /= pts.size();
float posScore = nnDist < 3 ? 25 : (nnDist > 6 ? 0 : 25 * (6 - nnDist) / 3);
float uniformScore = 100.0f * exp(-kl / 0.05f);
return countScore + posScore + uniformScore * 0.45f;
}
};
class InspectionPipeline {
public:
InspectionPipeline() : redis("tcp://127.0.0.1:6379") {
thread consumer(&InspectionPipeline::consume, this);
consumer.detach();
}
void push(const json& task) {
unique_lock<mutex> lock(mtx);
q.push(task);
cv.notify_one();
}
private:
queue<json> q;
mutex mtx;
condition_variable cv;
atomic<bool> running{true};
sw::redis::Redis redis;
ParticleDetector detector;
DefectClassifier classifier;
ACFEvaluator evaluator;
void consume() {
while (running) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this]{ return !q.empty() || !running; });
if (!running) break;
json task = q.front(); q.pop();
lock.unlock();
process(task);
}
}
void process(const json& task) {
string path = task["image_path"];
cv::Mat img = cv::imread(path, 0);
auto particles = detector.detect(img);
vector<cv::Point2f> pts;
for (const auto& r : particles) pts.emplace_back(r.x + r.width/2, r.y + r.height/2);
float score = evaluator.evaluate(pts);
cv::Mat filtered = cudaFFTNotch(img);
cv::Mat enhanced = cudaRegionGrow(filtered, cudaGradientFilter(filtered));
int label = classifier.predict(enhanced);
json result;
result["task_id"] = task["task_id"];
result["particle_count"] = particles.size();
result["acf_score"] = score;
result["defect_label"] = label;
redis.set("result:" + task["task_id"].get<string>(), result.dump());
sendToMES(result);
}
};
void sendToMES(const json& j) {
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, ZMQ_PUSH);
sock.connect("tcp://mes-server:5555");
string s = j.dump();
zmq::message_t msg(s.size());
memcpy(msg.data(), s.c_str(), s.size());
sock.send(msg, zmq::send_flags::none);
}
cv::Mat cudaGradientFilter(const cv::Mat& src) {
cv::Mat dst(src.size(), CV_32F);
// CUDA kernel launch omitted for brevity
return dst;
}
cv::Mat cudaFFTNotch(const cv::Mat& src) {
cv::Mat dst;
// FFT notch filter CUDA kernel omitted
return dst;
}
cv::Mat cudaRegionGrow(const cv::Mat& src, const cv::Mat& grad) {
cv::Mat dst;
// Region growing CUDA kernel omitted
return dst;
}
vector<float> extractGeometry(const cv::Mat& roi) {
vector<float> feat;
// Hu moments, area, etc.
return feat;
}
vector<float> extractLBP(const cv::Mat& roi) {
vector<float> feat(64, 0);
// LBP uniform pattern
return feat;
}
vector<float> extractHist(const cv::Mat& roi) {
vector<float> feat(16, 0);
// 16-bin histogram
return feat;
}
float computeKL(const vector<int>& bins) {
int sum = accumulate(bins.begin(), bins.end(), 0);
float q = 1.0f / VoronoiCells;
float kl = 0.0f;
for (int b : bins) {
if (b == 0) continue;
float p = float(b) / sum;
kl += p * log(p / q);
}
return kl;
}
PYBIND11_MODULE(tft_inspect, m) {
m.doc() = "TFT-LCD inspection";
py::class_<InspectionPipeline>(m, "Pipeline")
.def(py::init<>())
.def("push", &InspectionPipeline::push);
}


如有问题,可以直接沟通
👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
2126

被折叠的 条评论
为什么被折叠?



