基于cv2的图片处理之轮廓、截图(一)

      虫子的博客今天开通了。开通博客,一是为了记录自己的成长足迹,二是作为技术积累,更重要的是,为了分享,希望能帮助后来者,也希望牛人指点,提出宝贵的意见和建议。

        cv2是一个图片处理的python第三方库,是常用的图片处理工具之一。本文的写作背景是笔者在做一个对图片中特定字母和数字识别的工程,需要自己准备一批图片用于训练构建模型。本文所用到的最初的素材是图-1,那么需要做的工作就是将图中的字母和数字截取处理,并裁剪成统一的大小。具体过程就是先将图片转化为二值图,然后找出图片中的所有轮廓,根据轮廓的范围确定截图的位置,最终裁剪出每个字符。

基于cv2的图片处理之轮廓、截图(一)_第1张图片

图-1

        第一步,读取图片,将之转化为二值图。

import cv2
import numpy as np
import math
def bins_image(dir,filename,threadhold):   '''将图片转为二值图'''
    image = cv2.imread(dir + filename) #读取图片
    imgry = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) #转化为灰度图
    ret,bins = cv2.threshold(imgry,threadhold,255,cv2.THRESH_BINARY)   #转化为二值图,阈值为threahold,小于阈值设为0,大于阈值设为100
    return bins
由于项目需要,这里需要将图片改为黑底白字。
def back_color(image,threadhold):
    '''改变图片的背景颜色,默认改为黑色,即字体为白,背景为黑'''
    width = len(image[0])
    height = len(image)
    array = np.ravel(image) #将图片这个二维数组转化为一维数组
    series = [255 if x <=threadhold else 0 for x in array]  #若像素的灰度值小于threadhold,转化为白色(255),否则为黑色(0)
    img = np.reshape(series,(height,width))  #再将一维数组转化图片
    return series,img

    第二步,找出图片中的轮廓,筛选出符合条件的轮廓,这里筛选轮廓的条件是:1、轮廓的面积必须大于某值,以排除掉噪点;2、轮廓不属于其他轮廓(即没有父轮廓,如字符8,8本身是一个轮廓,一上一下两个圈分别又是8本身这个轮廓的子轮廓)。同时,也要对特殊字符1和I做处理,在对这样的特殊字符截图后在两侧加上黑框。

加边框的代码(本质上是矩阵合并):

def add_width(widthMean,abnormal_img):
    '''对高远大于宽的轮廓图片加宽'''
    '''raw_bins为原图片的二值图,widthMean平均宽度,abnormal_scopes为非正常的截图'''
    ab_width = len(abnormal_img[0]) #图片的宽
    ab_height = len(abnormal_img)   #图片的高
    addWidth = math.floor((widthMean - ab_width) / 2) #每边需要增加的宽度
    border = np.zeros(shape=(addWidth, ab_height), dtype=int)
    '''在左边加入边框,第一个参数abnormal_img为被加的原矩阵,第二个参数0,表示在0处加入,
    values=border表示加入的矩阵border,axis=1表示纵向加入(axis=0表示横向加入)'''
    new_bins = np.insert(abnormal_img,0,values=border,axis=1) #在左边加入背景
    '''纵向加入边框'''
    new_bins = np.insert(new_bins,len(new_bins[0]),values=border,axis=1)
    return new_bins

裁剪图片的代码(本质上是矩阵的拆分截取):

def crop_image(image,scope):
    '''将原图片按照scope进行切割,image为被截取的图片,
    scope为需要截取的轮廓的范围,形式为[x1,x2,y1,y2],(x1,y1)为包裹轮廓的直矩形的左上点,(x2,y2)为右下点'''
    x1 = scope[0]
    x2 = scope[1]
    y1 = scope[2]
    y2 = scope[3]
    img = image[y1:y2,x1:x2]
    #cv2.imwrite(path+name,img)
    return img

寻找并筛选轮廓的代码:

def search_outline(bins):
    '''搜寻符合条件的轮廓,根据轮廓得到截图集合'''
    width = len(bins[0])
    height = len(bins)
    area = width * height
    cropImageDict = {} #用于保存根据轮廓截取的图片
    expImageDict = {} #用于保存特字符,如I,1
    hSum = 0  #总高度
    wSum = 0  #总宽度
    min_area = area / NOISE_FACTOR
    img, cnts, hierarchy = cv2.findContours(bins, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    '''说明:cnts中的每一个元素cn是一个列表,形状为(n,1,2),即有n个元素,每个元素是[[x,y]]的形式'''
    for i in range(0,len(cnts)):
        cn = cnts[i]
        x,y,w,h = cv2.boundingRect(cn)
        '''说明:boundingRect能够得到包裹轮廓cn的直矩形(不是最小内接矩形),
        x,y分别是矩形左上点的横坐标和纵坐标,w,h分别是矩形的宽和高'''
        if x * y > min_area and hierarchy[0][i][3] == -1:  #hierarchy[0][i][3] == -1表示不属于任何轮廓
            scope = [x, x + w - 1, y, y + h - 1]  # (x+w-1,y+h-1)表示直矩形右下点的坐标
            image = crop_image(bins, scope)
            if h / w < NARROW_FACTOR: #高与宽的比值小于某个范围,认为是正常的字符,否则是特殊字符
                hSum += h
                wSum += w
                cropImageDict[i] = image
            else:
                expImageDict[i] = image
    '''计算平均宽度和平均高度'''
    wMean = wSum / len(cropImageDict)
    hMean = hSum / len(cropImageDict)
    '''对异常的字符截图加上黑色边框'''
    for k in expImageDict:
        expImg = expImageDict[k]
        correctImg = add_width(wMean,expImg)
        cropImageDict[k] = correctImg
    return cropImageDict, wMean, hMean

以上代码中cropImageDict是一个字典,key为轮廓编号,value为截图的二维矩阵,而关于轮廓层级hierarchy的说明见:

点击打开链接。

       第三步,对截取的图片进行归一化并写入指定位置。

主要代码:

def normalize_bins(bins,width,height):
    '''将二值图归一化为指定大小,返回一个二值图以及其对应的一维数组'''
    '''改变图片的大小,bins为图片的矩阵,(width,height)为需要改变成的形状'''
    r_bins = cv2.resize(bins,(width,height),interpolation=cv2.INTER_CUBIC)
    array_bins = np.ravel(r_bins)
    arr_bins = [255 if x >= 130  else 0 for x in array_bins]
    '''注意:这里需要最后得到结果是二值图,但是在resize后,有些像素会介于0和255之间,
    所以需要将所有的resize后的图片再转化为二值图,过程可以简单描述为
     二维灰度图→变形→一维矩阵→二值化处理→一维二值矩阵→二维二值图'''
    image = np.resize(arr_bins,(height,width))  
    return arr_bins,image

     第四部,将以上过程综合起来:

def createTrainImg():
    '''创建需要训练的图片'''
    filename = 'QQ123.jpg'
    rawBins = bins_image(RAW_DIR,filename,150)
    series,blackBins = back_color(rawBins,180)
    cv2.imwrite(RAW_DIR+'handle_'+filename,blackBins)
    blackImage = np.uint8(np.absolute(blackBins)) #必须有类型的转化,否则cv2.findContours方法报错
    cropImageDict, wMean, hMean = search_outline(blackImage)
    bin_arrays = normalize_images(wMean,hMean,cropImageDict,CUT_DIR) #该方法就是循环使用normalize_bins方法

以上就是将图片二值化,寻找轮廓,裁剪的过程。




你可能感兴趣的:(图片处理,python,cv2,python,cv2,图片处理)