书签智能产线——视觉部分

一、设计方案

1图像预处理模块功能统计

\预处理操作** \操作具体分类** \函数接口**
\数据传递** Mat类型转BYTEBYTE类型转Mat MatToByteByteToMat
\二值化** 二值化不同算法 Threshold
\形态学操作** 闭运算开运算膨胀膨胀 MorphologicalOperation
\图像滤波** 高斯滤波中值滤波 Filter
\边缘检测** Canny算子Sobl算子 EdgeDetect

函数入口使用BYTE*类型传递,在函数内部使用ByteToMat将图片数据转换为Mat类型,方便使用Opencv处理,最后将处理完的图像使用MatToByte传出,这样在c#端可以接收到处理完的图片数据。

2扫码模块

​ 可视化视觉编程框架中的扫码模块设计的主要目的是为了实现对二维码或条形码的扫描和解码操作。这个模块的设计旨在让用户能够通过简单的操作,使用摄像头或其他视觉传感器来捕捉图像中的二维码或条形码,并将其转换为可识别的文本或数据。

​ 在扫码前进行图像预处理,提高扫码成功率;前期使用成熟的扫码库和算法,例如opencv_wechat和zbar库,避免重复开发和时间浪费;后期同一实现二维码和条码的快速扫描与解码算法,保证高效率和准确性;提供简单易用的接口和文档,方便其他模块的集成和使用;提供可扩展的接口和参数,以满足不同需求的用户。

后期优化算法,使其能够同时解析二维码和条码等多种类型,降低算法复杂度,流程图如下所示:

3次品检测模块

​ 此智能书签生产线书签次品检测模块的设计旨在提高书签生产线的质量控制能力,确保书签的外观质量和可读性。具体目的包括:检测书签正反面、检测书签图案及图案角度是否正确,以及检测书签上二维码及角度是否正确。

​ 为了实现上述目的,本模块采用以下处理流程进行书签次品检测:首先,将书签图像进行二值化处理,将彩色图像转换为黑白图像,以便后续的图像处理操作。接下来,利用图像处理技术,通过寻找轮廓找出书签的位置,确定书签的边界。基于书签的最小外接矩形,进行透视变换,对书签进行校正,以纠正书签的倾斜和变形,提高后续处理的准确性。

通过霍夫圆检测算法,对书签上的孔洞或特定形状的标记进行检测,以判断书签的正反面。使用模板匹配算法,逐个角度匹配预定义的图案模板,检测图案是否正确,并记录图案角度的偏差。借助OpenCV微信扫码库,对书签上的二维码进行解码和识别,获取二维码的内容,并检测二维码的角度偏差。

根据上述检测结果,对书签进行分类、标记或剔除次品,将检测结果输出,供后续处理或记录。

通过以上设计实现,该智能书签生产线书签次品检测模块可以有效地检测书签正反、图案及图案角度、二维码及角度,并对次品进行识别和处理。这将提高书签生产线的质量控制能力,降低次品率,提高生产效率。同时,该模块的设计可以应用于实际生产环境中,为书签生产过程中的质量控制提供可靠的解决方案。

4视觉定位

​ 本次项目中,定位部分主要负责书签定位与外壳定位,算法实现是基于轮廓发现,寻找五个书签中第三个书签的像素坐标,在标定时,通过以机器手正好抓取的坐标为原点通过世界坐标与像素坐标的转换实现定位。通过对轮廓的几何位置计算其斜率。

二、识别测试以及函数接口,部分代码

1函数接口

int MatToByte(Mat& inMat, BYTE* outBYTE)

函数名称:MatToByte

功能描述:将cv::Mat对象中的像素值转换为BYTE类型数据,存储到指定的内存地址。

参数列表:

inMat: cv::Mat类型,输入图像数据。

outBYTE: BYTE类型指针,输出图像数据存储地址。

返回值:int类型,返回0表示转换成功,返回其他值表示转换失败。

int ByteToMat(Mat& outMat, BYTE* inBYTE, int width, int height, int channel)

函数名称:ByteToMat

功能描述:将BYTE类型数据转换为cv::Mat对象中的像素值。

参数列表:

outMat: cv::Mat类型,输出图像数据。

inBYTE: BYTE类型指针,输入图像数据存储地址。

width: int类型,图像宽度。

height: int类型,图像高度。

channel: int类型,图像通道数。

返回值:int类型,返回0表示转换成功,返回其他值表示转换失败。

int Threshold(BYTE* inData,BYTE* outData,int width, int height, int channel, double thresh, double maxval, int type)

函数名称:Threshold

功能描述:对输入的图像数据进行阈值化处理。

参数列表:

inData: BYTE类型指针,输入图像数据存储地址。

outData: BYTE类型指针,输出图像数据存储地址。

width: int类型,图像宽度。

height: int类型,图像高度。

channel: int类型,图像通道数。

thresh: double类型,阈值。

maxval: double类型,最大值。

type: int类型,阈值化类型。

返回值:int类型,返回0表示处理成功,返回其他值表示处理失败。

void Detect(BYTE* inData, BYTE* outData, int height, int width, int channel_)

函数名称:Detect

函数说明:用于检测二维码并在图像上标出检测结果。

参数说明:

inData:输入图像数据指针,类型为BYTE*。

outData:输出图像数据指针,类型为BYTE*。

height_:输入图像高度,类型为int。

width_:输入图像宽度,类型为int。

channel_:输入图像通道数,类型为int。

返回值:无

void GarbInit()

函数名称:GarbInit

函数说明:用于初始化摄像头。

参数说明:无

返回值:无

void GarbImg(BYTE* outData, int width, int height, int channel)

函数名称:GarbImg

函数说明:用于从摄像头获取图像数据。

参数说明:

outData:输出图像数据指针,类型为BYTE*。

width:输出图像宽度,类型为int。

height:输出图像高度,类型为int。

channel:输出图像通道数,类型为int。

返回值:无

int MorphologicalOperation(BYTE* inData, BYTE* outData, int width, int height, int channel, int kernelShape, int kernelSizeX, int kernelSizeY, int option)

函数名称:MorphologicalOperation

函数说明:进行形态学操作。

参数说明:

- inData:输入图像数据指针,类型为BYTE*。

- outData:输出图像数据指针,类型为BYTE*。

- width:图像宽度,类型为int。

- height:图像高度,类型为int。

- channel:图像通道数,类型为int。

- kernelShape:内核形状(0:矩形,1:十字形,2:椭圆形),类型为int。

- kernelSizeX:内核的 X 方向尺寸,类型为int。

- kernelSizeY:内核的 Y 方向尺寸,类型为int。

- option:形态学操作选项(0:闭,1:开,2:梯度,3:顶帽,4:黑帽,5:膨胀,6:腐蚀),类型为int。

返回值:操作是否成功的标志,类型为int。如果操作成功,则返回0;否则返回其他值。

int Filter(BYTE* inData, BYTE* outData, int width, int height, int channel, int level, int option)

函数名称:Filter

函数说明:对图像进行滤波操作。

参数说明:

- inData:输入图像数据指针,类型为BYTE*。

- outData:输出图像数据指针,类型为BYTE*。

- width:图像宽度,类型为int。

- height:图像高度,类型为int。

- channel:图像通道数,类型为int。

- level:滤波级别,类型为int。

- option:滤波选项(0:方框滤波,1:均值滤波,2:高斯滤波,3:中值滤波),类型为int。

返回值:操作是否成功的标志,类型为int。如果操作成功,则返回0;否则返回其他值。

int EdgeDetect(BYTE* inData, BYTE* outData, int width, int height, int channel, int low,int high, int option)

函数名称:EdgeDetect

函数说明:对图像进行边缘检测。

参数说明:

- inData:输入图像数据指针,类型为BYTE*。

- outData:输出图像数据指针,类型为BYTE*。

- width:图像宽度,类型为int。

- height:图像高度,类型为int。

- channel:图像通道数,类型为int。

- low:边缘检测的低阈值,类型为int。取值范围为0-100。

- high:边缘检测的高阈值,类型为int。取值范围为0-100。

- option:边缘检测选项(0:Canny边缘检测,1:Sobel边缘检测,2:Laplacian边缘检测),类型为int。

返回值:操作是否成功的标志,类型为int。如果操作成功,则返回0;否则返回其他值。

void Defectdetect(BYTE* inData,int width, int height, int channel,double &score,double &angle)

函数名称:Defectdetect

功能描述:检测缺陷并返回得分和角度。

参数列表:

-inData: BYTE 类型指针,输入图像数据存储地址。

-width: int 类型,输入图像的宽度。

-height: int 类型,输入图像的高度。

-channel: int 类型,输入图像的通道数。

-score: double 引用,输出的缺陷得分。

-angle: double 引用,输出的缺陷角度。

返回值:无。

double Detect(BYTE* inData, BYTE* outData, int width, int height, int channel, char* codedata)

函数说明:用于检测书签上的二维码并获取二维码信息及角度。

参数说明:

inData:输入图像数据指针,类型为BYTE*。

outData:输出图像数据指针,类型为BYTE*。

width:图像宽度,类型为int。

height:图像高度,类型为int。

channel:图像通道数,类型为int。

codedata:二维码信息存储指针,类型为char*。

返回值:二维码的角度,类型为double。#

2识别测试

识别效果:书签识别,外壳识别

image-20250302141025221

代码参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
分类模块核心代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。
float distance(Point2f a, Point2f b) {
float distances;
distances = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
return distances;
}

float xielv(Point2f a, Point2f b) {
float xie;
xie = (a.y - b.y) / (a.x - b.x);
return xie;
}


string vs(BYTE* inBYTE, int width, int height, int channel) {

if (inBYTE == nullptr)
{
return "-1";
}
int type = channel == 1 ? CV_8UC1 : CV_8UC3;
Mat src = cv::Mat(height, width, type);

// 逐行拷贝像素值到 cv::Mat 对象中
int index = 0;
for (int i = 0; i < height; ++i) {
std::memcpy(src.ptr(i), inBYTE + index, width * channel);
index += width * channel;
}
float x2, y2;
float ang;
Mat test;
src.copyTo(test);
threshold(src, src, 50, 255, THRESH_BINARY);
Point2f bookqian[5];
int lunkuo[5];
int book = 0;
Point2f temp;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
vector<vector<Point>> filteredContours;
findContours(src, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
// 检查轮廓数量

for (int i = 0; i < contours.size(); i++) {
vector<Point>points;
double area = contourArea(contours[i]);
/*面积排除噪声*/

if (area < 180000 || area>320000)

continue;
filteredContours.push_back(contours[i]);
// std::cout << "area="<<area << endl;
/*找有子轮廓的*/
//if (hierarchy[i][2] < 0 && hierarchy[i][2] >= contours.size())
// continue;

Moments rect;
rect = moments(contours[i], false);
bookqian[book] = Point2f(rect.m10 / rect.m00, rect.m01 / rect.m00);
lunkuo[book] = i;
if (book < 4) {
book++;
}
else {
book = 0;
}

}
// 检查筛选后的轮廓数量
if (filteredContours.empty()) {
// cout << "未找到符合条件的轮廓,无法继续执行程序。" << endl;
return 0;
}

// 检查筛选后的轮廓数量是否为五个
if (filteredContours.size() != 5) {
// cout << "筛选后的轮廓数量不等于五个,无法继续执行程序。" << endl;
return 0;
}
//排序法
for (int o = 1; o <= 4; o++)//外层循环是比较的轮数,数组内有10个数,那么就应该比较10-1=9轮
{
for (int j = 0; j <= 4 - o; j++)//内层循环比较的是当前一轮的比较次数,例如:第一轮比较9-1=8次,第二轮比较9-2=7次
{
if (bookqian[j].x > bookqian[j + 1].x)//相邻两个数如果逆序,则交换位置
{
temp = bookqian[j];
bookqian[j] = bookqian[j + 1];
bookqian[j + 1] = temp;
}

}

}
circle(test, 4 * bookqian[2], 100, Scalar(255, 0, 0), 1, 8, 2);
// cout << bookqian[2].x << endl;
// cout << bookqian[2].y << endl;
float x1, y1;
x1 = 1202.7 - bookqian[2].x;
y1 = 1030.04 - bookqian[2].y;

float k1 = 0.1233, k2 = 0.1233;
x2 = -(k1 * x1) - 167.17;
y2 = k2 * y1 + 621.442;


// cout << x2 << endl;
// cout << y2 << endl;
vector<RotatedRect>minRects(contours.size());
minRects[lunkuo[book]] = minAreaRect(Mat(contours[lunkuo[book]]));
Point2f rectPoints[4];
minRects[lunkuo[book]].points(rectPoints);

float a;
float b;
int c = 10;
float ka;
float kb;
float tan;
float arct = 0;
a = distance(rectPoints[0], rectPoints[1]);
b = distance(rectPoints[1], rectPoints[2]);
if (a > 2 * b) {
c = 0;
}
else if (2 * a < b) {
c = 1;
}
if (c == 0) {
kb = xielv(rectPoints[1], rectPoints[2]);
tan = kb;
arct = atan(tan);
}
else if (c == 1) {
ka = xielv(rectPoints[0], rectPoints[1]);
tan = ka;
arct = atan(tan);
}
// 计算角度范围
int angleRange = 90; // 设置角度范围为90度

// 计算角度
float angle = arct * 57.296; // 将弧度转换为角度

// 调整角度范围到竖直为0度、左偏-90度、右偏+90度
if (angle < -angleRange) {
angle += 180; // 右偏,加180度
}
else if (angle > angleRange) {
angle -= 180; // 左偏,减180度
}
ang = -angle - 45;
if (abs(x2) <= 60 && abs(y2) <= 60) {
string X = to_string(x2);
string Y = to_string(y2);
string A = to_string(ang);

string str = X + ',' + Y + ',' + "0," + "180," + "0," + A;
const char* X1 = str.c_str();
return X1;
}
// cout << angle << endl;
// cout << bookqian[2] << endl;
resize(test, test, Size(0, 0), 0.5, 0.5, 1);
// line(test, Point(2000, 0), Point(2000, 1500), Scalar(255, 0, 0), 2, 8, 0);
// line(test, Point(0, 1500), Point(4000, 1500), Scalar(255, 0, 0), 2, 8, 0);
// imshow("test", test);
// return 0;
}