基于c++ opencv 工业线激光质量评估算法
偏差越小,激光线越“直”,系统几何误差越小。沿激光线长度方向统计灰度峰值或面积的变异系数 CV(σ/μ)。连续采集 N 帧(通常≥1000 帧),统计中心位置抖动(3σ)和亮度抖动(CV)。RMS 偏差(Root-Mean-Square Error) 含义:激光中心点到拟合直线的“平均”距离,比最大偏差更能代表整体直线度。面积变异系数:把激光线每 1 列(或 1 行)当成一段,计算该段阈值后的面积
工业领域、机器视觉领域线激光器应用十分广泛,线激光质量直接决定了后续检测的精度。本算法可用于从图像角度粗略评估线激光质量,指导激光器的挑选或者调节。
工业相机激光线质量评估可以用五个字概括: 细、直、亮、稳、匀
线宽(Laser line width)
图像上激光线横截面的半高全宽(FWHM)或 1/e² 像素数。线越细,后续做亚像素中心提取的精度越高,理想值≤2~3 pixel。
线性度 / 直线度(Linearity or Straightness)
把整帧激光中心点拟合成一条直线,计算各点到该直线的最大偏差或 RMS 偏差,常用单位 mm 或 pixel。偏差越小,激光线越“直”,系统几何误差越小。
RMS 偏差(Root-Mean-Square Error) 含义:激光中心点到拟合直线的“平均”距离,比最大偏差更能代表整体直线度。
能量均匀性(Intensity uniformity)
沿激光线长度方向统计灰度峰值或面积的变异系数 CV(σ/μ)。CV≤5 % 认为均匀性良好,可保证不同位置信噪比一致,避免局部过曝或信号太弱。
CV(Coefficient of Variation)定义:标准差 / 均值,无量纲,可用来衡量“亮度沿激光线是否均匀”,也可衡量“面积沿激光线是否均匀”。
面积变异系数:把激光线每 1 列(或 1 行)当成一段,计算该段阈值后的面积,再求面积的 CV。
横截面一致性(Cross-section consistency)
取线上多段做横截面灰度曲线,比较其峰值、半高宽、对称性。若各截面曲线形状接近,说明激光在 Z 方向(高度)变化时测量灵敏度一致。
业界最常用 Pearson 偏度系数(skewness) 或 左右能量比。
信噪比(SNR)
激光中心区域峰值灰度与背景噪声(无激光区 σ)之比。SNR>30 dB 可稳定阈值分割;SNR 过低会放大中心提取随机误差。
激光条纹场景:信号取峰值灰度,噪声取“无激光区域”灰度标准差。
时间稳定性(Temporal stability)
连续采集 N 帧(通常≥1000 帧),统计中心位置抖动(3σ)和亮度抖动(CV)。抖动越小,后续 3D 轮廓的重复精度越高。
本算法实现除信噪比、时间稳定性以外的其他指标,这是激光图像:
下面是 .hpp文件:
#pragma once
#include <opencv2/opencv.hpp>
#include <vector>
#include <numeric>
#include <cmath>
#include <matplot/matplot.h>
#include <iostream>
#include <algorithm>
struct LaserColData {
int idx;
double center;
double fwhm;
cv::Mat1f profile;
};
class LaserAnalyzerBase {
public:
explicit LaserAnalyzerBase(const cv::Mat& src, double cropRatio = 0.1)
: raw_(src.clone()), cropRatio_(cropRatio) { preprocess(); }
const std::vector<LaserColData>& data() const { return cols_; }
int width() const { return W_; }
protected:
cv::Mat raw_;
double cropRatio_;
int W_;
std::vector<LaserColData> cols_;
void preprocess() {
int left = static_cast<int>(raw_.cols * cropRatio_);
int right = raw_.cols - left;
raw_ = raw_.colRange(left, right).clone();
W_ = raw_.cols;
cols_.resize(W_);
for (int x = 0; x < W_; ++x) {
LaserColData& d = cols_[x];
d.idx = x;
raw_.col(x).convertTo(d.profile, CV_32F);
d.center = centroid1D(d.profile);
d.fwhm = calcFWHM(d.profile);
}
}
static double centroid1D(const cv::Mat1f& prof) {
double wSum = 0, wxSum = 0;
for (int i = 0; i < prof.rows; ++i) {
double v = std::max(prof(i), 0.0f);
wSum += v; wxSum += i * v;
}
return wSum > 0 ? wxSum / wSum : 0.0;
}
static double calcFWHM(const cv::Mat1f& prof) {
double minVal, maxVal;
cv::Point minLoc, maxLoc;
cv::minMaxLoc(prof, &minVal, &maxVal, &minLoc, &maxLoc);
float half = (maxVal - minVal) * 0.5f + minVal;
int left = -1, right = -1;
double left_value = 0,right_value = 0;
for (int i = maxLoc.y; i >= 0; --i)
if (prof(i) < half) { left = i; break; }
for (int i = maxLoc.y; i < prof.rows; ++i)
if (prof(i) < half) { right = i; break; }
left_value = (left*prof(left) + (left + 1)*prof(left + 1))/(prof(left) + prof(left + 1));
right_value = (right*prof(right) + (right - 1)*prof(right - 1))/(prof(right) + prof(right - 1));
return (left == -1 || right == -1) ?
std::nan("") : static_cast<double>(right_value - left_value);
}
};
class WidthStats : public LaserAnalyzerBase {
public:
using LaserAnalyzerBase::LaserAnalyzerBase;
void compute() {
double sum = 0, cnt = 0, sq_sum = 0;
for (const auto& d : data()) {
if (std::isnan(d.fwhm)) continue;
cnt++; sum += d.fwhm; sq_sum += d.fwhm * d.fwhm;
}
mean_ = cnt ? sum / cnt : 0.0;
double var = cnt ? (sq_sum / cnt - mean_ * mean_) : 0.0;
stdev_ = std::sqrt(var);
cv_ = (mean_ > 0) ? stdev_ / mean_ : 0.0;
}
void print() const {
std::cout << "----- 线宽统计 -----\n";
std::cout << "mean FWHM = " << mean_ << " px\n";
std::cout << "stddev = " << stdev_ << " px\n";
std::cout << "CV = " << cv_ << "\n";
}
void plot() const {
std::vector<double> v;
for (const auto& d : data()) if (!std::isnan(d.fwhm)) v.push_back(d.fwhm);
matplot::hist(v, 30);
matplot::xlabel("FWHM (px)");
matplot::ylabel("count");
matplot::title("Width distribution");
matplot::show();
}
private:
double mean_=0, stdev_=0, cv_=0;
};
class Linearity : public LaserAnalyzerBase {
public:
using LaserAnalyzerBase::LaserAnalyzerBase;
void compute() {
std::vector<cv::Point2f> pts;
for (const auto& d : data()) pts.emplace_back(static_cast<float>(d.idx), static_cast<float>(d.center));
cv::fitLine(pts, line_, cv::DIST_L2, 0, 0.01, 0.01);
double A = line_[1], B = -line_[0], C = line_[0]*line_[3] - line_[1]*line_[2];
double den = std::sqrt(A*A + B*B);
dev_.resize(pts.size());
double sum2 = 0, maxD = 0;
for (size_t i = 0; i < pts.size(); ++i) {
double d = std::abs(A*pts[i].x + B*pts[i].y + C) / den;
dev_[i] = d; sum2 += d*d; if (d > maxD) maxD = d;
}
rms_ = std::sqrt(sum2 / pts.size());
maxDev_ = maxD;
}
void print() const {
std::cout << "----- 线性度 -----\n";
std::cout << "RMS 偏差 = " << rms_ << " px\n";
std::cout << "Max 偏差 = " << maxDev_ << " px\n";
}
void plot() const {
std::vector<double> x, y;
for (size_t i = 0; i < dev_.size(); ++i) { x.push_back(i); y.push_back(dev_[i]); }
matplot::plot(x, y, "b-");
matplot::xlabel("column");
matplot::ylabel("deviation (px)");
matplot::title("Linearity deviation");
matplot::show();
}
private:
cv::Vec4f line_;
std::vector<double> dev_;
double rms_=0, maxDev_=0;
};
class PearsonSkew : public LaserAnalyzerBase {
public:
using LaserAnalyzerBase::LaserAnalyzerBase;
void compute() {
skew_.resize(data().size());
double sum = 0; size_t cnt = 0;
for (size_t i = 0; i < data().size(); ++i) {
skew_[i] = pearsonSkew1D(data()[i].profile);
sum += skew_[i]; ++cnt;
}
meanSkew_ = cnt ? sum / cnt : 0.0;
}
void print() const {
std::cout << "----- 横截面对称性(Pearson) -----\n";
std::cout << "mean skewness = " << meanSkew_ << "\n";
std::cout << "|skew|<0.2 对称良好;|skew|>0.5 明显偏斜\n";
}
void plot() const {
std::vector<double> x, y;
for (size_t i = 0; i < skew_.size(); ++i) { x.push_back(i); y.push_back(skew_[i]); }
matplot::plot(x, y, "r-");
matplot::xlabel("column");
matplot::ylabel("Pearson skew");
matplot::title("Cross-section symmetry");
matplot::show();
}
private:
std::vector<double> skew_;
double meanSkew_=0;
/* 工具:Pearson 偏度 */
static double pearsonSkew1D(const cv::Mat1f& prof) {
int n = prof.rows;
double wSum=0, wxSum=0, w2Sum=0, w3Sum=0;
for (int i=0;i<n;++i){ double v=std::max(prof(i),0.0f); wSum+=v; wxSum+=i*v; }
if (wSum==0) return 0.0;
double mu=wxSum/wSum;
for (int i=0;i<n;++i){ double v=std::max(prof(i),0.0f), t=i-mu, t2=t*t; w2Sum+=v*t2; w3Sum+=v*t2*t; }
double sig2=w2Sum/wSum; if (sig2<1e-10) return 0.0;
double sig=std::sqrt(sig2);
return (w3Sum/wSum)/(sig*sig2);
}
};
下面是主函数.cpp调用:
#include "laser_analyzer.hpp"
int main(){
cv::Mat img = cv::imread("图片路径", cv::IMREAD_GRAYSCALE);
if (img.empty()) { std::cerr << "❗ 读图失败\n"; return -1; }
//cv::imshow("laser",img);
//cv::waitKey(0);
WidthStats w(img); w.compute(); w.print();
Linearity l(img); l.compute(); l.print();
PearsonSkew p(img); p.compute(); p.print();
return 0;
}
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)