太阳能面板组件外观瑕疵识别(基于图像)

引言

近来琐事繁多,疏于整理手头的工作,实在是有违当初定下的目标。深知已错失种一棵树的最好时机,那么当下必须有所行动了。悟以往之不谏,知来者之可追。


本篇主要介绍太阳能面板外观瑕疵的图像识别方法,希望能对类似场景的需求实现有所启示或借鉴。

问题描述

太阳能面板外观瑕疵的检测是生产过程中必不可少的一步,目前是与EL检测同步,现场质检员在处理EL图像的同时对外观图像的瑕疵进行甄别并做进一步处理。与EL环节不同的是外观图像是普通相机成像而非红外,且瑕疵类别较为复杂、细小。但就异物来说可能存在锡渣、纸片、玻璃渣、甚至头发丝等多种情况,且大小、分布均是不可预知的。另外还有间距异常、型材脏污、划伤、结晶、错位等。细分起来约有接近二十个小类。下面贴两张异常样本感受下:


P1-间距异常-A7A8位置

P2-异物-玻璃渣C10

最终目的希望找出瑕疵的大概位置。由于不可抗因素导致瑕疵样本极其稀缺,想收集大量有效样本更是难上加难,深度学习所要求的样本量是极难满足的。因此这里采用传统图像处理方法尝试解决一部分问题。
下面以P1图的间距问题为例,讨论处理过程,识别异物的思路也基本类似。

思路

首先为了后期处理的方便可以先对原图进行切边处理,如下图所示,切出红线外的部分。实际上间距、异物等瑕疵大概率发生在电池片区域内,因此可以首先挖出电池片区域,再进行后续处理。这样做的好处:一是无需考虑灰白玻璃背板条带的影响易于选取更加合适的处理参数;二是易于对电池片进行切割分块处理,从而易于识别电池片上的极小瑕疵。


ROI区域

总体目标是希望通过各种处理、运算,凸显瑕疵特征,过滤干扰因素。从灰度图开始,经过二值化、腐蚀、膨胀等一系列形态学运算应该就能得到想要的效果,最后可以通过对水平或竖直方向的像素做投影,找出瑕疵特征进行判别和位置确定。
需要注意的是应该根据实际情况,选择最合适的阈值使其具有更广泛的处理能力。某些参数对某张图像处理效果可能是非常好的,对其他的图像处理效果就差很多,而有些参数对图像处理效果达不到完美但能达标且对多个图像均适用,因此最终确定参数时需要折中考虑。
对于间距问题的识别不做切边也是可以做的,影响不大,对于异物最好是切成电池片处理。

过程

  • 读取图像-切边预处理
    该部分的思路是通过开闭运算形成界限清晰的不同区域,从而提取电池片所在的ROI。代码如下:
import cv2
#import utils
import numpy as np
from imutils import contours
from skimage import measure ,exposure
from PIL import Image,ImageEnhance
from collections import Counter

def _black_edges(image):
    height, width = image.shape[:2]#读取尺寸
    size = (int(width * 0.25), int(height * 0.25))#缩小至1/4大小
    shrink = cv2.resize(image, size, interpolation=cv2.INTER_AREA)
    gray = cv2.cvtColor(shrink, cv2.COLOR_BGR2GRAY)#灰度图
    ret2, image_binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)#产生二值化阈值
    ret, binary = cv2.threshold(gray, ret2 * 0.85, 255, cv2.THRESH_BINARY)#二值化
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 8))#定义形态学运算的卷积核形状,可根据实际调整
    iOpen = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)#开运算
    iClose = cv2.morphologyEx(iOpen, cv2.MORPH_CLOSE, kernel)#闭运算-结果见下图P3
    height,width = iClose.shape
# 下面就是根据闭运算结果寻找电池片区域的上下左右分割点,依据行列中0的占比进行判断,注意需要设定起始条件incol、flag避免错误的触发阈值条件
    xx = [];yy=[];incol = 'nan';flag = True
    for x in range(width):
        if flag == True and Counter(iClose[:,x])[0]/height<=0.1:
            incol = 1
            flag = False

        elif incol==1 and Counter(iClose[:,x])[0]/height > 0.50:
            xx.append(x)
            incol = 0
        elif incol==0 and Counter(iClose[:,x])[0]/height<0.1:
            xx.append(x)
            incol = 'nan'
            break

    for y in range(height):
        if flag == False and Counter(iClose[y])[0]/width<=0.1:
            incol = 2
            flag = True
        elif incol==2 and Counter(iClose[y])[0]/width > 0.50:
            yy.append(y)
            incol = 3
        elif incol==3 and Counter(iClose[y])[0]/width<=0.1:
            yy.append(y)
            break
    return image[yy[0]*4:yy[1]*4,xx[0]*4:xx[1]*4]

image=cv2.imread("A10190700300743.jpg")
ims = _black_edges(image)#切边后的图像如下图
P3-对图像P1的闭操作结果

P4-对图像P1的切边后结果
  • 电池片切分(间距识别无需切分-仅针对异物识别)
    去除白边后再切分就比较好办了,根据行列的电池片数量,等分切割,实际计算电池片尺寸时向整数近似,切出的效果可能略有偏差但不会太大。代码如下:
ims = _black_edges(image)
#cv2.imwrite('ims.jpg',ims)
height,width,t = ims.shape
w=12;h=6
item_width = int(width / w)
item_height = int(height /h)

for j in range(0,w):
    for i in range(0,h):
        imbox = ims[i*item_height:(i+1)*item_height,j*item_width:(j+1)*item_width]
        cv2.imwrite('cut_' + str(i+1)+'_'+str(j+1) + '_result.jpg',imbox)

切出的效果如下:


切分后的电池片(cut_6_4_result.jpg)

对异物瑕疵的识别,这里仅针对大概率出现在电池片区域内的,对于可能落在电池片边界的异物可能会随着电池片切分而被裁切,大的话也还可以识别,太小可能就难了。切分后的电池片需要逐个输入写好的检测步骤。过程与检测间距是类似的。

  • 二值化
    对切边后的图像灰度化后做二值化处理:
image= ims
#image=cv2.resize(ims,(1500,800))#尺寸调整
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)

ref_num =cv2.threshold(gray,65,255,cv2.THRESH_BINARY_INV)[1]#需选择合适的阈值,多试几次
cv_show('ref2',ref_num)
cv2.imwrite('b.jpg',ref_num)
P5-对P4的二值图
  • 腐蚀操作:
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
kernel3 = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
#横向腐蚀
erode = cv2.erode(ref_num, kernel2,iterations=3)
cv2.imwrite('erode.jpg',erode)
P5-对P4的腐蚀操作

腐蚀的目的是突出纵向线条,当然这不是必须的步骤,但处理后能使得像素投影图的区分特征更加明显,更易于设计过滤规则。包括下面的梯度运算等也非必须。这里直接给出处理效果:

#横向梯度
tidu = cv2.morphologyEx(erode, cv2.MORPH_GRADIENT, kernel2,iterations=3)
cv2.imwrite('tidu.jpg',tidu)

#纵向腐蚀去除噪点
erode2 = cv2.erode(tidu,kernel,iterations=3)
cv2.imwrite('erode2.jpg',erode2)
#反二值化
f2=cv2.threshold(erode2,85,255,cv2.THRESH_BINARY_INV)[1]
cv2.imwrite('f2.jpg',f2)
#闭运算
gradX=cv2.morphologyEx(f2,cv2.MORPH_CLOSE,kernel3,iterations=3)
cv2.imwrite('gradX.jpg',gradX)
P6-gradX.jpg
  • 像素投影分析(统计0和255的个数)
    纵向的间距问题做垂直投影即可,分析横向的像素规律需要做水平投影。以下分别给出绘制垂直、水平像素投影代码:
#垂直投影
def czty(binary):
    height, width = binary.shape[:2]
    v = [0]*width
    a = 0
    for x in range(0, width):
        for y in range(0, height):
            if binary[y,x] == 0:
                a = a + 1
            else :
                continue
        v[x] = a
        a = 0
    emptyImage = np.zeros((height, width, 3), np.uint8) #为便于观察效果这里生成图像,实际操作中可将规则判断与投影写在同一个函数
    for x in range(0,width):
        for y in range(0, v[x]):
            b = (255,255,255)
            emptyImage[y,x] = b
    cv2.imwrite('czty.jpg',emptyImage)
#水平投影
def spty(binary):
    height, width = binary.shape[:2]
    a = 0;z = [0]*height
    emptyImage = np.zeros((height, width, 3), np.uint8) 
    for y in range(0, height):
        for x in range(0, width):
            if binary[y,x] == 0:
                a = a + 1
            else :
                continue
        z[y] = a
        a = 0
    for y in range(0,height):
        for x in range(0, z[y]):
            b = (255,255,255)
            emptyImage[y,x] = b
    cv2.imwrite('spty.jpg', emptyImage)

P7-对P5的垂直像素投影

至此,基于P7的结果结合特定的判出规则即可识别间距问题。规则比较简单,可以先找到白线的入口与出口,判断每个白线长度是否符合判出标准,比如小于纵向长度的5/6。

小结

本篇主要探讨了太阳能面板的外观图像瑕疵中纵向间距问题的识别方法,实际上由于两块电池片距离太近导致两者之间的汇流条被覆盖,在图像上就表现为纵向亮线的缺失。异物的识别也是类似的,对图像进行一系列的形态学处理,使之突出瑕疵特征,最后做像素投影判断瑕疵位置。
需要指出的是传统算法的弊端在于泛化能力太差,参数的鲁棒性不足;原始图像的质量和形态学处理的方式方法决定了参数的适应性。对图像的处理方式虽因人而异但大方向应该是一致的,但实际中原始图像的质量情况往往是比较复杂,受制于多种因素。如果你有足够的样本量还是首选深度学习方案。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容