<div id="content_views" class="htmledit_views">
<h2><a name="t0"></a>1.简介</h2>
2D图像基础,滤波,直方图统计,模板匹配
3D处理基础,点云滤波,点云分割
基于c++开发,测试
2.缺陷检测概述
产品异常产生缺陷,缺陷检测检出产品是否存在缺陷,即使识别次品,避免造成更大的损失。
内部缺陷:气泡,孔洞,内部裂纹(检测技术:x射线,超声波,机械应力波)
外部缺陷:刮痕,破损,脏污(检测技术 2D(图像处理,滤波,分割),3D(点云,深度图))
例如:
环形3D结构光,不同高度具有不同的颜色,依赖光源的稳定性,均匀性等。
3D的数据,点云和深度图。主要以PCB检测为例,比如少锡、多锡、虚焊。通过检测爬锡高度,或者爬坡角度等指标,卡控不良。
缺陷检测的准确率很重要,漏检容易造成很严重的问题。
由于精度高,一般是先对样品扫描,扫描过后再拼接,得到样品的全景图,再进行后续处理。可以检测元器件的偏移,管脚翘脚,少锡,虚焊,浮高等。
缺陷检测的任务可以分为检测,分类,分割。检测包含了分类和坐标回归。分割提取目标轮廓信息。计算复杂度:一般分割>检测>分类。
mobile-net,yolov5,ICNet。
有监督学习,分类
无监督学习,聚类,难度大
深度学习参数少,几乎能适应各种各样的缺陷检测,项目多,存在小样本,小目标问题。对于实时性的要求需要量化模型,进行剪枝,知识蒸馏,也可以采用清凉模型。
3D成像系统 激光线扫,结构光扫描等。
光源位置方向,相机位置方向,可以求得相交位置处像素点的空间位置。
一般是条纹结构光,对拍摄的条纹结构进行解度可以得到物体表面的信息。
受物体材料朗伯反射性能影响。解码包裹相位,光栅序列,求得绝对相位,转换成点云。
点云可以分为有序点云和无序点云。
深度图灰度值表示高度值
mesh渲染,点云图并不连续的,而经过mesh渲染则可以看到连续的表面。
3.常用算法库及工具
PCL开源库
4.2D图像滤波方法
简述:
计算机视觉场景广泛,以深度学习为主,精度不高。图像处理算法。
机器视觉场景固定,以传统学习为主。图像采集,镜头控制,图像处理等算法。
操作系统 Linux,编程语言python,c++,图像处理基础知识,OpenCV使用。
1.2D滤波
去除噪声提升图像质量:均值,中值,高斯滤波
高通:比如边缘提取 canny,sobel,laplacian等算子
·
还有自适应中值滤波:
OpenCV 中没有直接提供自适应中值滤波(Adaptive Median Filter)的函数,但你可以自己实现它。自适应中值滤波器与常规中值滤波器不同,它根据像素邻域内的特定条件调整窗口大小,这使得它在去除噪声(特别是“盐和胡椒”噪声)的同时更好地保护图像细节。
自适应中值滤波的基本步骤如下:
- 选择初始窗口大小:从一个小窗口(例如 3x3)开始。
- 计算窗口内的中值:计算当前窗口内像素的中值。
- 调整窗口大小:如果中值满足特定条件,则调整窗口大小。通常,如果中值接近窗口中的最大或最小值,则增大窗口。
- 检查是否满足停止条件:如果窗口大小超过预设的最大值或者已经找到了合适的中值,则停止调整。
- 应用中值:用找到的中值替换当前像素。
以下是一个简单的自适应中值滤波的实现示例:
-
#include <opencv2/opencv.hpp>
-
#include <algorithm>
-
-
using
namespace cv;
-
-
void adaptiveMedianFilter(const Mat &src, Mat &dst, int maxSize) {
-
dst = src.
clone();
-
for (
int i =
0; i < src.rows; i++) {
-
for (
int j =
0; j < src.cols; j++) {
-
int windowSize =
3;
-
while (windowSize <= maxSize) {
-
int halfSize = windowSize /
2;
-
std::vector<uchar> neighbors;
-
for (
int x = -halfSize; x <= halfSize; x++) {
-
for (
int y = -halfSize; y <= halfSize; y++) {
-
int ni = i + x, nj = j + y;
-
if (ni >=
0 && ni < src.rows && nj >=
0 && nj < src.cols) {
-
neighbors.
push_back(src.
at<uchar>(ni, nj));
-
}
-
}
-
}
-
std::
nth_element(neighbors.
begin(), neighbors.
begin() + neighbors.
size() /
2, neighbors.
end());
-
uchar median = neighbors[neighbors.
size() /
2];
-
-
// 判断中值是否是极端值
-
auto minMax = std::
minmax_element(neighbors.
begin(), neighbors.
end());
-
if (median != *minMax.first && median != *minMax.second) {
-
dst.
at<uchar>(i, j) = median;
-
break;
-
}
-
windowSize +=
2;
// 增加窗口大小
-
}
-
}
-
}
-
}
-
-
int main() {
-
Mat image =
imread(
"path/to/your/image.jpg", IMREAD_GRAYSCALE);
-
if(image.
empty()) {
-
std::cout <<
"Could not open or find the image" << std::endl;
-
return
-1;
-
}
-
-
Mat filteredImage;
-
adaptiveMedianFilter(image, filteredImage,
7);
// 使用最大窗口大小为 7
-
-
imshow(
"Original Image", image);
-
imshow(
"Adaptive Median Filtered Image", filteredImage);
-
waitKey(
0);
-
destroyAllWindows();
-
-
return
0;
-
}
确保模板内元素和求平均为1。
深度图像
深度图像(depth image)也被称为距离影像(range image),是指将从图像采集器到场景中各点的距离(深度)作为像素值的图像,它直接反映了景物可见表面的几何形状。
深度图像经过坐标转换可以计算为点云数据,有规则及必要信息的点云数据也可以反算为深度图像数据。
深度数据流所提供的图像帧中,每一个像素点代表的是在深度感应器的视野中,该特定的(x, y)坐标处物体到离摄像头平面最近的物体到该平面的距离(以毫米为单位).
图像深度
图像深度 是指存储每个像素所用的位数,也用于量度图像的色彩分辨率。
图像深度 确定彩色图像的每个像素可能有的颜色数,或者确定灰度图像的每个像素可能有的灰度级数。它决定了彩色图像中可出现的最多颜色数,或灰度图像中的最大灰度等级。比如一幅单色图像,若每个像素有8位,则最大灰度数目为2的8次方,即256。一幅彩色图像RGB三通道的像素位数分别为4,4,2,则最大颜色数目为2的4+4+2次方,即1024,就是说像素的深度为10位,每个像素可以是1024种颜色中的一种。
2.Blob分析
提取形状,数量统计
先是前景分割,再独立分割。
在计算机视觉和图像处理领域,Blob(二值图像区域)分析是一种常用的技术,主要用于识别和分析图像中的连续像素区域(称为blobs),这些区域在形状、大小、亮度等方面具有某种共同特征。Blob分析通常用于物体检测、追踪、形状分析等任务。以下是Blob分析的一些关键步骤和技术:
-
预处理:包括降噪、增强对比度等,以提高Blob检测的准确性。
-
二值化:将图像转换为黑白二值图像,通常使用阈值分割。
-
Blob检测:在二值图像中识别连续的像素区域。常用方法包括连通区域标记(Connected Component Labeling)和轮廓检测(如OpenCV中的
findContours
)。 -
Blob特征提取:计算每个Blob的特征,如面积、周长、重心位置、形状描述子等。
-
后处理:根据需要对Blobs进行分类、筛选或追踪。
在 C++ 中使用 OpenCV 库的 threshold
函数进行图像阈值处理是一个常见的操作。该函数主要用于将图像转换成二值图像,即将图像中的像素值根据给定的阈值分为两部分。以下是 OpenCV 的 threshold
函数在 C++ 中的使用方法:
double cv::threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
参数说明
src
: 输入图像,必须是单通道图像,例如灰度图。dst
: 输出图像,它是二值化后的图像。thresh
: 阈值。maxval
: 当像素值超过(或根据阈值类型,可能是低于)阈值时应该被赋予的最大值。type
: 阈值类型,常见的类型包括:THRESH_BINARY
:如果像素值大于阈值,则赋值为maxval
,否则为0。THRESH_BINARY_INV
:如果像素值大于阈值,则赋值为0,否则为maxval
。THRESH_TRUNC
:如果像素值大于阈值,则赋值为阈值,否则保持不变。THRESH_TOZERO
:如果像素值小于阈值,则赋值为0,否则保持不变。THRESH_TOZERO_INV
:如果像素值大于阈值,则赋值为0,否则保持不变。
threshhold使用:
-
#include <opencv2/opencv.hpp>
-
#include <iostream>
-
-
using
namespace cv;
-
-
int main() {
-
// 读取图像
-
Mat src =
imread(
"path_to_image.jpg", IMREAD_GRAYSCALE);
-
if (src.
empty()) {
-
std::cerr <<
"Error: Loading image" << std::endl;
-
return
-1;
-
}
-
-
Mat dst;
-
double thresh =
100;
// 设定阈值
-
double maxval =
255;
// 设定最大值
-
-
// 应用阈值处理
-
threshold(src, dst, thresh, maxval, THRESH_BINARY);
-
-
// 显示图像
-
imshow(
"Original Image", src);
-
imshow(
"Thresholded Image", dst);
-
-
waitKey(
0);
-
return
0;
-
}
在 OpenCV 中,morphologyEx
函数用于执行各种形态学操作,如腐蚀、膨胀、开运算、闭运算等。这些操作通常用于图像预处理、特征提取或去除噪声。以下是如何在 C++ 中使用 morphologyEx
函数的简介:
void cv::morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue())
src
: 输入图像。dst
: 输出图像,与输入图像具有相同的大小和类型。op
: 形态学操作类型,如MORPH_OPEN
、MORPH_CLOSE
、MORPH_GRADIENT
、MORPH_TOPHAT
、MORPH_BLACKHAT
等。kernel
: 结构元素,用于定义操作的性质。可以使用getStructuringElement
函数创建。anchor
: 锚点位置,默认值为元素的中心。iterations
: 操作的迭代次数。borderType
: 边界类型。borderValue
: 当borderType
为BORDER_CONSTANT
时的边界值。
形态学例子:
-
#include <opencv2/opencv.hpp>
-
#include <iostream>
-
-
using
namespace cv;
-
-
int main() {
-
// 读取图像
-
Mat src =
imread(
"path_to_image.jpg", IMREAD_GRAYSCALE);
-
if (src.
empty()) {
-
std::cerr <<
"Error: Loading image" << std::endl;
-
return
-1;
-
}
-
-
Mat dst;
-
int operation = MORPH_OPEN;
// 定义形态学操作类型
-
Mat element =
getStructuringElement(MORPH_RECT,
Size(
5,
5));
// 创建结构元素
-
-
// 应用形态学操作
-
morphologyEx(src, dst, operation, element);
-
-
// 显示图像
-
imshow(
"Original Image", src);
-
imshow(
"Morphology Operation Result", dst);
-
-
waitKey(
0);
-
return
0;
-
}
在这个示例中,首先读取一幅图像。然后定义了一个形态学操作类型(这里使用的是开运算 MORPH_OPEN
)和一个结构元素。使用 morphologyEx
函数对图像进行形态学处理。最后,展示原始图像和处理后的图像。
在 OpenCV 中,floodFill
函数用于执行洪水填充操作。这是一个非常强大的工具,通常用于分割图像的某些部分、改变图像中区域的颜色或者在图像分析中识别和标记连通区域。floodFill
函数基本上是从一个种子点开始,然后向四周扩展,直到满足某些停止条件。
函数原型:
-
int cv::floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint, Scalar newVal, Rect* rect = 0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4)
-
参数说明:
image
: 输入/输出图像,必须是 1 通道或 3 通道图像。mask
: 操作的掩码,更新填充区域的边界。大小必须是(image.rows + 2) x (image.cols + 2)
,且为 8 位单通道。seedPoint
: 开始填充的种子点。newVal
: 填充区域的新值。rect
: 可选的输出参数,返回被填充区域的最小边界矩形。loDiff
和upDiff
: 填充颜色的下限和上限差值。flags
: 操作标志,定义填充模式和颜色选择方式。
例子:
-
#include <opencv2/opencv.hpp>
-
#include <iostream>
-
-
using
namespace cv;
-
-
int main() {
-
// 读取图像
-
Mat image =
imread(
"path_to_image.jpg");
-
if (image.
empty()) {
-
std::cerr <<
"Error: Loading image" << std::endl;
-
return
-1;
-
}
-
-
// 创建掩码
-
Mat mask = Mat::
zeros(image.rows +
2, image.cols +
2, CV_8UC1);
-
-
// 种子点
-
Point seedPoint =
Point(
50,
50);
// 需要根据实际情况选择合适的点
-
-
// 洪水填充
-
floodFill(image, mask, seedPoint,
Scalar(
255,
0,
0),
0,
Scalar(
10,
10,
10),
Scalar(
10,
10,
10),
4);
-
-
// 显示图像
-
imshow(
"Flood Fill Image", image);
-
waitKey(
0);
-
return
0;
-
}
在这个例子中,首先读取了一幅图像。创建了一个稍大于原图的掩码。然后选择一个种子点,并使用 floodFill
函数进行填充。最后,显示填充后的图像。
3.直方图统计分析
根据灰度等级调整对比度,增强局部特征
应该是Otsu方法。
Otsu 图像分割是一种基于阈值的图像分割方法,旨在将图像中的前景和背景分开。这个方法的主要思想是找到一个阈值,将图像中的像素分为两个类别,使得这两个类别之间的类内方差最小,而类间方差最大。这个阈值将图像分成了前景和背景两个部分,从而实现了图像分割。
以下是使用Otsu算法进行图像分割的一般步骤:
-
首先,将彩色图像转换为灰度图像,因为Otsu算法通常用于灰度图像的分割。
-
计算每个灰度级别的像素在图像中的频数分布,可以得到直方图。
-
计算图像的总体平均灰度值和总体方差。
-
遍历可能的阈值(从0到最大灰度级别),对于每个阈值,将图像分成两个部分:低于阈值的像素属于一个类别,高于阈值的像素属于另一个类别。
-
对于每个阈值,计算两个类别的类内方差和类间方差。
-
使用Otsu的准则,选择使类间方差最大化的阈值。通常,这可以通过最大化以下公式来实现:
类间方差 = 类间方差 / 类内方差
-
将选定的阈值应用于图像,将图像分割成前景和背景。
-
最终,你可以对分割后的前景和背景区域进行后续处理或分析。
Otsu算法是一种自适应的方法,不需要事先知道图像的特定阈值。它在很多情况下都表现良好,特别是对于具有双峰直方图的图像。然而,在一些情况下,它可能不够精确,因此需要根据具体应用的要求进行调整或使用其他图像分割方法。
例子:
-
#include <opencv2/opencv.hpp>
-
#include <iostream>
-
-
using
namespace cv;
-
using
namespace std;
-
-
int main() {
-
// 读取图像
-
Mat img =
imread(
"path_to_your_image.jpg", IMREAD_GRAYSCALE);
-
if (img.
empty()) {
-
cout <<
"无法读取图像" << endl;
-
return
-1;
-
}
-
-
// 初始化直方图参数
-
int histSize =
256;
// 直方图大小
-
float range[] = {
0,
256 };
-
const
float* histRange = { range };
-
-
Mat hist;
-
calcHist(&img,
1,
0,
Mat(), hist,
1, &histSize, &histRange);
-
-
// 显示直方图
-
int hist_w =
512, hist_h =
400;
-
int bin_w =
cvRound((
double)hist_w / histSize);
-
-
Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));
-
normalize(hist, hist,
0, histImage.rows, NORM_MINMAX,
-1,
Mat());
-
-
for (
int i =
1; i < histSize; i++) {
-
line(histImage,
-
Point(bin_w * (i -
1), hist_h -
cvRound(hist.
at<
float>(i -
1))),
-
Point(bin_w * i, hist_h -
cvRound(hist.
at<
float>(i))),
-
Scalar(
255,
0,
0),
2,
8,
0);
-
}
-
-
imshow(
"原始图像", img);
-
imshow(
"直方图", histImage);
-
-
waitKey(
0);
-
return
0;
-
}
在这个示例中,我们首先读取一个灰度图像,然后使用 calcHist
函数计算其直方图。直方图的每个“箱”(bin)的宽度通过图像的直方图大小来决定。接着,我们创建了一个用于显示直方图的空白图像,并使用 line
函数在其中绘制直方图。
请确保将 "path_to_your_image.jpg"
替换为你的图像文件的路径。
4.模板匹配
定位目标
opencv自带函数。但不支持旋转,缩放。他的输出结果是映射图,图中的位置表示模板在图像的相对位置处的二者的相关性。
halcon,点击file,点击browse Hdevelop Example programs,在左边method里选择合适的匹配方法。在右边选择合适的例子,这里选择第3个find ncc。
在pogram window窗口就会出现代码,查看代码,前面是读进一幅图,选择合适区域作为模板。选择并按F5执行,程序会在stop处暂停,接着按F6步进查看每副图像匹配 。