0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

手机镜头目标提取、缺陷检测与图像畸变校正

新机器视觉 来源:博客园 作者:LittleNyima 2020-08-28 13:50 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前两天参加了北师的数学建模校赛,B题是一道图像处理的题,于是趁机练习了一下OpenCV,现在把做的东西移植过来。

(2020.5.31补充:此方法在竞赛中取得二等奖。这次的参赛论文的确存在一些问题,例如没有对结果进行量化评估、对处理方式的具体细节叙述得不够明确、参考文献不够丰富(好吧,其实没有引用参考文献)等。)

题目大意

给出一张生产线上拍摄的手机镜头的图像(如下),要求解决三个问题:

建立模型构造出一种分割方法,可以将左右两个镜头的待测区域(白色环形内区域)准确地分离出来。

建立模型构造一种检测方法,自动地在待测区域之内将所有缺陷点找出,缺陷点为人眼可识别的白点,最小可为一个像素点。要求给出缺陷点的数学描述,并根据该描述建立检测模型,自动确定每个缺陷点的位置和像素大小。给出右侧镜头中按像素大小排序的最大的前五个缺陷点的位置坐标。

由于在实际拍照中镜头可能会在模具中抖动,所以拍摄的图片可能并不是正对镜头的,此为图像的偏心现象。比如图中左侧图像就是正对的情况,右侧就是不正对(偏心)的情况。建立模型构造一种校正方法,校正右侧图像的偏心现象。呈现校正效果,并给出第2问所求五个缺陷点校正后的位置坐标。

问题求解

问题一

这个问题是目标检测,并且需求十分明确:提取出白色圆环中的区域的图像。观察图像可以发现图中白色的部分几乎只有需要检测的白色圆环,其他的白色区域基本上都是不规则图形以及一些噪点。一种比较简单的处理方式是直接选取一个合适的阈值二值化,把除了需要的白色圆环之外的区域全部置位黑色。不过为了鲁棒性我们并没有使用这种简单粗暴的方式。

我们的预处理方法是二值化去除多余细节开运算去除噪点高斯滤波减小像素间梯度,完成预处理后再进行轮廓搜索。二值化采取了全局二值化,主要是在最大类间方差法(OTSU法)与三角形法两者之间进行选取,实验发现后者会使黑白区域边界模糊且曲折,并且很多白色噪点(第二问要检测)受到了干扰,因此选择了前者作为二值化方法。开运算的卷积核为20×20矩形卷积核,进行了一次效果就很好了。高斯滤波的直径(严格来说并不能叫做直径)经验性地确定为了5。预处理后的效果如下图所示。

预处理结束之后直接使用OpenCV内置的findContours()寻找边界,这个函数非常方便的一点是它可以根据轮廓之间的嵌套关系,对各个轮廓构造层次关系,函数原型为:

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])

其中的必要参数为:

image:输入的图像

mode:轮廓的检索模式,共有四种取值。

cv2.RETR_EXTERNAL:只检索外轮廓。

cv2.RETR_LIST:检索轮廓不建立层次关系。

cv2.RETR_CCOMP:建立两层的层次关系(即父子关系),父轮廓为外轮廓,子轮廓为相应内孔。若内孔中还有内孔,则下一层的内孔作为另一个父轮廓。

cv2.RETR_TREE:对轮廓建立等级树的层次关系。

method:轮廓的逼近方法,共有四种取值。

cv2.CHAIN_APPROX_NONE:储存所有轮廓点,相邻两个点的横纵坐标之差均不超过1。

cv2.CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息。

cv2.CHAIN_APPROX_TC89_L1和cv2.CHAIN_APPROX_TC89_KCOS都是使用teh-Chinl chain近似算法

对于圆环来说直接选取CCOMP模式,图中的所有轮廓中只有圆环的外轮廓有子轮廓,从而找出所有的目标边界。根据此边界创建遮罩,再将遮罩与原图做按位与即可分割出目标图像。最后做出来的结果相当不错。(就不放在这里了,有兴趣的可以自己拿原图跑一下代码)

1 ''' 2 * detection.py 3 Runtime environment: 4 python = 3.7.4 5 opencv-python = 4.1.1.26 6 numpy = 1.17.2 7 ''' 8 9 from cv2 import imread, IMREAD_GRAYSCALE, threshold, THRESH_BINARY, THRESH_OTSU,10 getStructuringElement, MORPH_RECT, erode, dilate, GaussianBlur, findContours,11 RETR_CCOMP, CHAIN_APPROX_SIMPLE, IMREAD_COLOR, drawContours, bitwise_and,12 imwrite13 from numpy import zeros, shape, uint814 15 def detection():16 original = imread('Original.png', IMREAD_GRAYSCALE)17 _, binary = threshold(original, 0, 255, THRESH_BINARY | THRESH_OTSU)18 imwrite('problem1_binary.png', binary)19 kernel = getStructuringElement(MORPH_RECT, (20, 20))20 eroded = erode(binary, kernel)21 dilated = dilate(eroded, kernel)22 blur = GaussianBlur(dilated, (5, 5), 0)23 imwrite('problem1_preprocess.png', blur)24 contours, hierarchies = findContours(blur, RETR_CCOMP, CHAIN_APPROX_SIMPLE)25 chromatic = imread('Original.png', IMREAD_COLOR)26 drawContours(chromatic, contours, -1, (0, 0, 255), 10)27 imwrite('problem1_contours.png', chromatic)28 chromatic = imread('Original.png', IMREAD_COLOR)29 for hierarchy in hierarchies[0, :]:30 if hierarchy[2] != -1:31 drawContours(chromatic, contours, hierarchy[2], (255, 0, 255), 15)32 imwrite('problem1_target_contours.png', chromatic)33 chromatic = imread('Original.png', IMREAD_COLOR)34 count = 035 for hierarchy in hierarchies[0, :]:36 if hierarchy[2] != -1:37 mask = zeros(shape(chromatic), dtype = uint8)38 drawContours(mask, contours, hierarchy[2], (255, 255, 255), -1)39 imwrite('mask' + str(count) + '.png', mask)40 imwrite('detection' + str(count) + '.png', bitwise_and(chromatic, mask))41 count += 142 43 if __name__ == '__main__':44 detection()

问题二

检测缺陷点还要计算大小,这很明显是一个图搜索问题。把问题一预处理第一步,也就是二值化得到的图像与遮罩叠加,所需要搜索的缺陷点就都显现出来了。需要做的只是遍历图像中所有点,然后对每个点进行广度优先搜索就可以了。这个问题也比较顺利地解决了,唯一的缺点是遍历广搜运行起来有一点慢,要运行数十秒才能得到结果。

1 ''' 2 Runtime environment: 3 python = 3.7.4 4 opencv-python = 4.1.1.26 5 numpy = 1.17.2 6 ''' 7 8 from cv2 import imread, IMREAD_GRAYSCALE, threshold, THRESH_BINARY, THRESH_OTSU, 9 imwrite, bitwise_and, IMREAD_COLOR, circle10 from numpy import shape, zeros, uint811 12 def findDefect():13 original = imread('Original.png', IMREAD_GRAYSCALE)14 _, binary = threshold(original, 0, 255, THRESH_BINARY | THRESH_OTSU)15 mask = imread('mask0.png', IMREAD_GRAYSCALE)16 target = bitwise_and(binary, mask)17 imwrite('problem2_target.png', target)18 flag = zeros(shape(target), dtype = uint8)19 defects = []20 for i in range(shape(target)[0]):21 for j in range(shape(target)[1]):22 if target[i][j] == 255 and flag[i][j] == 0:23 queue = []24 head, tail= 0, 025 x, y = i, j26 queue.append(None)27 queue[head] = (x, y)28 flag[x][y] = 129 head += 130 while head > tail:31 if x > 0 and target[x - 1][y] == 255 and flag[x - 1][y] == 0:32 queue.append(None)33 queue[head] = (x - 1, y)34 flag[x - 1][y] = 135 head += 136 if y > 0 and target[x][y - 1] == 255 and flag[x][y - 1] == 0:37 queue.append(None)38 queue[head] = (x, y - 1)39 flag[x][y - 1] = 140 head += 141 if x < shape(target)[0] - 1 and target[x + 1][y] == 255 and flag[x + 1][y] == 0:42 queue.append(None)43 queue[head] = (x + 1, y)44 flag[x + 1][y] = 145 head += 146 if y < shape(target)[1] - 1 and target[x][y + 1] == 255 and flag[x][y + 1] == 0:47 queue.append(None)48 queue[head] = (x, y + 1)49 flag[x][y + 1] = 150 head += 151 (x, y) = queue[tail]52 tail = tail + 153 size = len(queue)54 xsum, ysum = 0, 055 for (x, y) in queue:56 xsum += x57 ysum += y58 defects.append((size, xsum // size, ysum // size))59 defects.sort()60 print(defects[::-1], len(defects))61 print(defects[-5:])62 return defects[-5:]63 64 def visualize(defects):65 original = imread('Original.png', IMREAD_COLOR)66 for defect in defects:67 circle(original, (defect[2], defect[1]), 10, (0, 0, 255), -1)68 imwrite('defects.png', original)69 70 if __name__ == '__main__':71 defects = findDefect()72 visualize(defects)

最后得到了116个缺陷点,虽然大多数都只有1~2个像素但不得不吐槽这个镜头的加工技术确实不太行。

问题三

这个问题是对镜头在模具内抖动造成的偏心畸变进行修正,再重新计算缺陷点坐标。修正畸变是本次各个问题中最为棘手的一个部分。查找了一下资料,偏心畸变是由于图像中目标的光轴与摄像机的光轴不重合造成的,这也是偏心畸变在英文中被称为decentering distortion的原因。在本问题中,大概是这样:

如果把镜头内表面看做圆锥面的话,偏心畸变的产生原因就是这个圆锥稍微“倒下”了一点。想要从几何上对齐进行修正,就要将这个圆锥“扶正”,具体方式是将圆锥面上的每个点都映射到另一个正立的圆锥上,使得其在母线上的位置比例关系不变。

如上图,这是从圆锥的底面看向顶点的视图。目标是将红色的圆锥母线映射到蓝色的圆锥母线上,在左图看来,就是对于圆O内任意一点P,连接O'P并延长交圆O于Q,连接OQ,在OQ上找到一点P'使得O'P/O'Q=OP'/OQ,P'即为所求。具体的公式推导就不推了,主要过程就是先将原坐标系中的坐标映射到这个圆O的坐标系中,得到目标点的坐标后再映射回去(因为线性代数很多都忘记了所以数学推导十分受苦QAQ)。最后的修正效果如下:

左图是修正前的原图,右图是修正后的图像。虽然直观上看并没有太大变化,但仔细观察中间的深色原点以及深灰色圆形阴影的位置,就可以看出整幅图像得到了一个从左下到右上的校正。最后的总体效果还是比较令人满意的,在新的图像上重复问题一、二的算法,问题即得解。

问题三的代码如下,主要有两部分,第一部分是求中间小的深色圆形阴影位置的代码,第二部分是进行畸变校正的代码(实现比较暴力,相应地运行效率也比较低)。

1 ''' 2 * locating.py 3 Runtime environment: 4 python = 3.7.4 5 opencv-python = 4.1.1.26 6 numpy = 1.17.2 7 ''' 8 9 from cv2 import imread, IMREAD_GRAYSCALE, threshold, THRESH_OTSU, THRESH_BINARY,10 imshow, waitKey, imwrite, THRESH_TRIANGLE, adaptiveThreshold, ADAPTIVE_THRESH_MEAN_C,11 ADAPTIVE_THRESH_GAUSSIAN_C, HoughCircles, HOUGH_GRADIENT, circle,12 getStructuringElement, MORPH_RECT, erode, dilate, medianBlur, GaussianBlur,13 Canny, findContours, RETR_CCOMP, CHAIN_APPROX_SIMPLE, drawContours,14 IMREAD_COLOR, RETR_TREE, minEnclosingCircle15 from numpy import uint1616 17 if __name__ == '__main__':18 original = imread('detection0.png', IMREAD_GRAYSCALE) # read original image as grayscale image19 kernel = getStructuringElement(MORPH_RECT, (20, 20))20 eroded = erode(original, kernel)21 dilated = dilate(eroded, kernel)22 dilated = dilate(dilated, kernel)23 eroded = erode(dilated, kernel)24 blur = GaussianBlur(eroded, (5, 5), 0)25 original = blur26 binary = adaptiveThreshold(original, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 99, 2)27 imwrite('adaptive_mean.png', binary) # save image adaptive mean method(loc.)28 origin = imread('adaptive_mean.png', IMREAD_GRAYSCALE)29 kernel = getStructuringElement(MORPH_RECT, (40, 40))30 eroded = erode(origin, kernel)31 dilated = dilate(eroded, kernel)32 blur = GaussianBlur(dilated, (5, 5), 0)33 origin = blur34 contours, hierarchies = findContours(origin, RETR_TREE, CHAIN_APPROX_SIMPLE)35 print(hierarchies)36 chromatic = imread('Original.png', IMREAD_COLOR)37 for i in range(len(hierarchies[0])):38 if hierarchies[0][i][2] == -1:39 break40 length = len(contours[i])41 (x0, y0), r = minEnclosingCircle(contours[i])42 sum = [0, 0]43 for k in contours[i]:44 sum = sum + k45 print(sum // length)46 x, y = tuple(sum[0] // length)47 circle(chromatic, (int(x0), int(y0)), 5, (0, 255, 0), -1)48 circle(chromatic, (int(x0), int(y0)), int(r), (0, 255, 0), 10)49 X, Y, R = (2585, 1270, 433)50 circle(chromatic, (X, Y), 5, (0, 0, 255), -1)51 circle(chromatic, (X, Y), R, (0, 0, 255), 10)52 print(int(x0), int(y0), int(r))53 print(X, Y, R)54 imwrite('contours.png', chromatic)

1 """ 2 * calibrate.py 3 Runtime environment: 4 python = 3.7.4 5 opencv-python = 4.1.1.26 6 numpy = 1.17.2 7 """ 8 9 from math import sqrt10 from cv2 import imread, IMREAD_GRAYSCALE, imwrite, medianBlur11 from numpy import shape12 13 14 def dist(p1, p2):15 r = (float(p1[0] - p2[0]) ** 2 + float(p1[1] - p2[1]) ** 2) ** 0.516 return r17 18 19 def calibrate():20 x, y, r = 2567.0, 1289.0, 63.021 x0, y0, r0 = 2585.0, 1270.0, 433.022 dist0 = dist((x, y), (x0, y0))23 input_img = imread('Original.png', IMREAD_GRAYSCALE)24 output = imread('Original.png', IMREAD_GRAYSCALE)25 tan_theta = float(y - y0) / float(x0 - x)26 sin_theta = tan_theta / sqrt(1 + tan_theta * tan_theta)27 cos_theta = 1 / sqrt(1 + tan_theta * tan_theta)28 sin_theta, cos_theta = sin_theta.real, cos_theta.real29 for i in range(shape(input_img)[1]):30 for j in range(shape(input_img)[0]):31 original = (i, j)32 if dist(original, (x0, y0)) < r0:33 neo = (cos_theta * float(i - x0) - sin_theta * float(j - y0),34 -sin_theta * float(i - x0) - cos_theta * float(j - y0))35 a = float(neo[1]) ** 2 + (float(neo[0]) + dist0) ** 236 b = -2.0 * float(neo[1]) * dist0 * (float(neo[0]) + dist0)37 c = float(neo[1]) ** 2 * (dist0 ** 2 - r0 ** 2)38 delta = b ** 2 - 4 * a * c39 if delta < 0 or a == 0 or float(neo[1]) == 0:40 continue41 yr = (sqrt(delta) - b) / (2 * a)42 if (yr * float(neo[1])) < 0:43 yr = (0 - b - sqrt(delta)) / (2 * a)44 xr = ((float(neo[0]) + dist0) * yr / float(neo[1])) - dist045 x2, y2 = xr / yr * float(neo[1]), float(neo[1])46 real = (cos_theta * x2 - sin_theta * y2 + x0, -sin_theta * x2 - cos_theta * y2 + y0)47 output[int(real[1])][int(real[0])] = input_img[int(original[1])][int(original[0])]48 imwrite('problem3_after_mapping.png', output)49 medianed = medianBlur(output, 3)50 imwrite('Result3.png', medianed)51 52 53 if __name__ == '__main__':54 calibrate()

总结

虽然之前处理过一些图像处理问题但从来没有像这次一样完整地做一次题目,也没有深入地了解过各个运算的内在原理。这次的图像处理问题前两个比较基础,最后一个比较有挑战性,感觉对于学习OpenCV还是很有帮助的。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 图像
    +关注

    关注

    2

    文章

    1095

    浏览量

    42157
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66833
  • OpenCV
    +关注

    关注

    33

    文章

    651

    浏览量

    44411

原文标题:OpenCV实战 | 手机镜头目标提取、缺陷检测与图像畸变校正

文章出处:【微信号:vision263com,微信公众号:新机器视觉】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    远心镜头中的光学畸变和远心度是什么意思?

    远心镜头中的光学畸变与远心度:工业测量为什么如此看重这两个指标?在机器视觉或工业检测领域,普通镜头往往无法满足精密测量需求,其核心原因不在分辨率,而在于几何成像的稳定性。涉及到的两个关
    的头像 发表于 12-03 17:23 56次阅读
    远心<b class='flag-5'>镜头</b>中的光学<b class='flag-5'>畸变</b>和远心度是什么意思?

    远心镜头该如何选型?

    远心镜头最大的价值在于倍率恒定、畸变极低,所以它通常应用在高精度检测和测量里。但不同应用需求差别很大,远心镜头的价格差距也比较大。如果没有明确的选型逻辑,很容易踩坑。这里分享几个核心的
    的头像 发表于 12-02 15:59 134次阅读
    远心<b class='flag-5'>镜头</b>该如何选型?

    电压放大器在全导波场图像目标识别的损伤检测实验的应用

    图像目标识别的智能损伤检测方法,通过结合超声导波检测技术与深度学习算法,系统探究了损伤引起的波场畸变特性及其识别机制。 测试设备:扫描激光多
    的头像 发表于 12-02 11:37 66次阅读
    电压放大器在全导波场<b class='flag-5'>图像</b><b class='flag-5'>目标</b>识别的损伤<b class='flag-5'>检测</b>实验的应用

    远心镜头的应用领域有哪些?

    远心镜头主要用在哪些行业?在机器视觉和工业检测领域,经常会听到“远心镜头”这个词。很多人第一反应是:这和普通镜头有什么区别?其实远心镜头的核
    的头像 发表于 12-01 15:32 80次阅读
    远心<b class='flag-5'>镜头</b>的应用领域有哪些?

    什么是变倍镜头

    /检测的场景(比如电子元件检测、瑕疵检测、生物成像)。可以在不更换镜头的情况下,切换观察倍率或视场。和摄影镜头不同,它更强调光学
    的头像 发表于 12-01 15:31 82次阅读
    什么是变倍<b class='flag-5'>镜头</b>

    Moritex 5X高精度大靶面远心镜头助力晶圆缺陷检测

    Moritex 5X高精度大靶面远心镜头助力晶圆缺陷检测
    的头像 发表于 10-17 17:04 202次阅读
    Moritex  5X高精度大靶面远心<b class='flag-5'>镜头</b>助力晶圆<b class='flag-5'>缺陷</b><b class='flag-5'>检测</b>

    基于LockAI视觉识别模块:C++目标检测

    检测是计算机视觉领域中的一个关键任务,它不仅需要识别图像中存在哪些对象,还需要定位这些对象的位置。具体来说,目标检测算法会输出每个检测到的对
    发表于 06-06 14:43

    VirtualLab Fusion应用:F-Theta扫描镜头的性能评估

    摘要 F-Theta 透镜通常用于Galvo类扫描仪的激光材料加工系统。 对于这样的镜头,聚焦点沿目标平面的位移与镜头焦距和扫描角度的乘积成正比。然而,没有完美的 F-Theta 系统,因此在
    发表于 03-05 09:37

    SWIR 中的图像校正

    传感器相比,InGaAs 传感器本质上更容易出现严重的传感器图案和像素缺陷。这些缺陷出现在每个InGaAs 传感器上。 以透明且严格的方式克服最终图像中的这些图案和缺陷是科学 InGa
    的头像 发表于 02-24 06:24 565次阅读
    SWIR 中的<b class='flag-5'>图像</b><b class='flag-5'>校正</b>

    远心工业镜头:高精度尺寸检测、定位应用的理想选择

    超低畸变、超高清晰度的远心工业镜头非常适用于高精度检测
    的头像 发表于 02-17 17:16 1630次阅读
    远心工业<b class='flag-5'>镜头</b>:高精度尺寸<b class='flag-5'>检测</b>、定位应用的理想选择

    大视野与高分辨率难兼得,FA 镜头有何破局之法?

    相互制约。扩大视野时,光线传播路径变复杂,像差和畸变问题随之而来,导致图像边缘模糊、分辨率降低。在检测大面积电路板时,传统镜头追求大视野就无法看清微小元件细节;而
    的头像 发表于 01-21 16:49 1138次阅读
    大视野与高分辨率难兼得,FA <b class='flag-5'>镜头</b>有何破局之法?

    远心镜头应用手机按键检测

    远心镜头手机按键检测方案具有高效、准确、稳定等优点。该方案能够实现对手机按键的全方位检测,包括按键的位置、高度、间隙以及弹力等参数。同时,
    的头像 发表于 01-20 10:18 676次阅读
    远心<b class='flag-5'>镜头</b>应用<b class='flag-5'>手机</b>按键<b class='flag-5'>检测</b>

    千万级 FA 镜头应用线路板缺陷检测

    FA 镜头即工业镜头,千万级则代表其具备千万像素级别的超高分辨率。在检测线路板时,镜头利用光学成像原理,将线路板上的细节清晰地投射到图像传感
    的头像 发表于 01-06 14:23 949次阅读
    千万级 FA <b class='flag-5'>镜头</b>应用线路板<b class='flag-5'>缺陷</b><b class='flag-5'>检测</b>

    VirtualLab Fusion应用:畸变分析仪

    摘要 镜头是成像系统设计的一个组成部分。因此,对任何光学工程师来说,能够详细分析它们的性能是至关重要的。一个众所周知的不利影响是畸变,它导致光束的横向位置相对于焦平面的参考位置的偏差。在这
    发表于 01-02 16:41

    AI模型部署边缘设备的奇妙之旅:目标检测模型

    目标物体周围复杂的背景信息可能会干扰分类结果,使得分类器难以专注于真正重要的区域。 在深入探讨了图像分类任务及其面临的挑战之后,我们现在将目光转向一个更为复杂的计算机视觉问题——目标检测
    发表于 12-19 14:33