【第5天】资料前处理-去除杂讯与灰阶

现况

  1. 清洗後的图档,部分仍有红框等杂讯,或是中文字体颜色不同(蓝色、黑色),如下图。

  2. 若将含有不同颜色中文字或不同位置红框的图档纳入模型训练,可能影响後续模型的辨识效果。(如:同一个中文字,因字体颜色是黑色或蓝色,导致辨识结果错误)

  3. 我们选择以HSV进行颜色追踪,在尽可能保留中文字字迹的前提下,利用opencv mask功能去除红框。此外,将图档以灰阶图与二值化图呈现,比较两者效果。

  4. 最终选择以灰阶图作为训练样本,并将资料集以7:3比例分配成train与test,供後续训练模型之用。


工具/套件

  1. opencv-python
  2. PIL(pillow)
  3. numpy
  4. shutil

内容

  1. HSV(Hue, Saturation, Value)

    1.1 HSV

    • 由色调(Hue)、饱和度(Saturation)、亮度(Value)三个分量组成,颜色分布如下。

      图片来自於:https://www.itread01.com/content/1549945446.html

    • HSV颜色空间,能更直观的表示人眼对色彩的感受。

    • 基本上,HSV只要确定色调(1个分量),就可确定是何种颜色;一般的RGB,则是依照每Red、Green、Blue(3个分量)的比例,才能确定是何种颜色。

    1.2 颜色追踪(HSV+opencv)

    • HSV便於表现特定的颜色,且opencv读取的图档为RGB,可透过内建函数转换成HSV。
    # 将RGB转换成HSV颜色空间
    redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    • 转换成HSV後,以H值锁定色调後,只要调整表和度与亮度的阈值范围,就可以追踪特定颜色。因此,常用於分割特定颜色物件。(如:用来追踪红框,再以mask将红框去除)
  2. HSV颜色追踪与mask

    2.1 HSV调色盘-查询HSV值

    • 程序码
    import cv2
    import numpy as np
    
    # 读取中文路径图档(图片读取为BGR)
    def cv_imread(filePath):
        cv_img = cv2.imdecode(np.fromfile(filePath, dtype=np.uint8), -1)
        return cv_img
    
    # 点击欲判定HSV值的图片位置(以滑鼠左键单击)
    def mouse_click(event, x, y, flags, para):
        if event == cv2.EVENT_LBUTTONDOWN:
            print("BGR:", img[y, x])
            print("GRAY:", gray[y, x])
            print("HSV:", hsv[y, x])
            print('='*30)
    
    if __name__ == '__main__':
        # 读取图档
        img = cv_imread('./data/04-清洗标签後图片/清洗标签final/可用/2_惠.jpg')
        img = cv2.resize(img, (320, 240))
        # 转换成gray与HSV
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        cv2.namedWindow("img")
        cv2.setMouseCallback("img", mouse_click)
        while True:
            cv2.imshow('img', img)
            if cv2.waitKey() == ord('q'):
                break
        cv2.destroyAllWindows()
    
    • 效果

    2.2 锁定HSV阈值与Demo:载入图档後,藉由滑动滚轮调整HSV值,逐一挑选出红框、蓝色字体、黑色字体的HSV阈值,示意图如下。


    图片来自於:https://blog.csdn.net/weixin_42216109/article/details/89520423

  3. 得到红框、蓝色字体、黑色字体HSV阈值後,开始去除杂讯,并比较灰阶图、二值化图,程序码如下。

    3.1 追踪红框、蓝色字体、黑色字体

    • 红框:因为红框有2种,所以分别以red1_mask与red2_mask追踪。
    import cv2
    import numpy as np
    import os
    from PIL import Image
    
    def red1_mask(img):
        # 红1
        lower = np.array([150, 80, 94])
        upper = np.array([180, 255, 255])
        redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask1 = cv2.inRange(redhsv1, lower, upper)
        return mask1
    
    def red2_mask(img):
        # 红2
        lower = np.array([0, 80, 89])
        upper = np.array([10, 255, 255])
        redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(redhsv, lower, upper)
        return mask
    
    • 追踪蓝色中文字
    def blue_mask(img):
        # 蓝
        lower = np.array([90, 43, 46])
        upper = np.array([124, 255, 255])
        redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(redhsv, lower, upper)
        return mask
    
    • 追踪黑色中文字
    def black_mask(img):
        # 黑
        lower = np.array([0, 0, 0])
        upper = np.array([255, 255, 135])
        redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(redhsv, lower, upper)
        return mask
    

    3.2 设定膨胀函数

    def my_dilate(img):
        kernel = np.ones((3, 3), np.uint8)
        new_img = cv2.dilate(img, kernel, iterations=1)
        return new_img
    

    3.3 找图档中的众数,用来取代红色的mask区域

    def get_mode(img):
        # 阈值取众数
        # bincount():统计非负整数的个数,不能统计浮点数
        counts = np.bincount(img.flatten())
        # counts的index代表出现的数,counts[index]代表出现数的次数
        # 今要求counts[index] 排序後最大跟第二大的counts的index(代表众数跟出现第二多次的数)
        # 最後一个元素是counts最大值的index ,倒数第二是二大
        counts_sort = np.argsort(counts)
        index = counts_sort[-1]
        # 以防图片出现大量黑色面积
        # 出现大量黑色区块的话,取第二多数
        if index <= 100:
            index = counts_sort[-2]
            return index
        # 否则就return原本的众数
        return index
    

    3.4 合并mask区域(删去红框、保留中文字)

    • 过程
    # 合并mask区域
    def process_img(img, turn=None):
        # 红1
        mask1 = red1_mask(img)
        # 红2
        mask2 = red2_mask(img)
        # 合并红1+红2之范围
        mask3 = cv2.bitwise_or(mask1, mask2)
        # 膨胀mask3
        mask3 = my_dilate(mask3)
        # 黑白反转
        mask3 = cv2.bitwise_not(mask3, mask3)
    
        # 图档转换成灰阶
        image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        # 取众数
        image_mode =get_mode(image)
        # 把红色的mask区域换成众数
        image[mask3 == 0] = image_mode
    
        # 显示mask後图片
        image = cv2.resize(image, (160, 120))
        cv2.imshow('mask', image)
        cv2.waitKey()
    
        # 高斯模糊後取众数
        blur = cv2.GaussianBlur(image, (3, 3), 0)
        c = get_mode(blur) * 0.7
        # 二值化
        ret, thresh1 = cv2.threshold(image, c, 255, cv2.THRESH_BINARY)
        if turn == 'binary':
            thresh1 = cv2.resize(thresh1, (160, 120))
            cv2.imshow('binary', thresh1)
            cv2.waitKey()
            return thresh1
        if turn == 'gray':
            blur = cv2.resize(blur, (160, 120))
            cv2.imshow('gray', blur)
            cv2.waitKey()
            return blur
    if __name__ == '__main__':
        img = cv_imread('./data/04_清洗标签後图片/清洗标签final/可用/2_惠.jpg')
        # 显示原图片
        img = cv2.resize(img, (160, 120))
        cv2.imshow('origin', img)
        cv2.waitKey()
        process_img(img, 'binary')
        process_img(img, 'gray')
    
    • 成果展示

      <原图>

      <灰阶图>

      <二值化图>

    3.5 虽然将图档二值化,训练模型效率较高,但可能造成图像信息严重丢失;灰阶则可以兼顾训练效率与保留较多的梯度信息。因此,我们最终选择以灰阶图作为训练样本。

  4. 分配trian与test资料集:先统计每个中文字的照片张数,再以7:3比例分配成train与test。

    4.1 过程

    import os
    import random
    import shutil
    import os
    import shutil
    
    # 分成训练集跟资料集
    src_dir_name = './train/'
    target_dir_name = './test/'
    test_size = 0.3
    labels = set(os.listdir(src_dir_name))
    
    def word_classfier():
        word_list_dir = []
        for i in os.listdir(src_dir_name):
            if i.endswith('.jpg'):
                word_list_dir.append(i.split('.')[0][-1])
    
        word_list_dir = set(word_list_dir)
        print(word_list_dir)
    
        for i in os.listdir(src_dir_name):
            if i.endswith('.jpg'):
                if i.split('.')[0][-1] in word_list_dir:
                    try:
                        os.mkdir(src_dir_name+i.split('.')[0][-1])
                    except FileExistsError:
                        pass
                    shutil.move(src_dir_name+i,src_dir_name+i.split('.')[0][-1]+'/'+i)
    
    def move_test_data(test_data:list):
        for i in test_data:
            word_subfolder = i.split('.')[0][-1]
            if word_subfolder in labels:
                print(src_dir_name+word_subfolder+'/'+i)
                try:
                    os.mkdir(target_dir_name +word_subfolder)
                except FileExistsError:
                    pass
                shutil.move(src_dir_name+word_subfolder+'/'+i,target_dir_name +word_subfolder+'/'+i)
            elif  word_subfolder not in labels:
                word_subfolder = i.split('.')[0][0]
                print(src_dir_name+word_subfolder+'/'+i)
                try:
                    os.mkdir(target_dir_name +word_subfolder)
                except FileExistsError:
                    pass
                try:
                    shutil.move(src_dir_name+word_subfolder+'/'+i,target_dir_name +word_subfolder+'/'+i)
                except FileNotFoundError:
                    shutil.move(src_dir_name + word_subfolder + '/' + i, target_dir_name + word_subfolder + '/' + i)
    
    def test_train_split():
        try:
            os.mkdir(target_dir_name)
        except FileExistsError:
            pass
        for i in os.listdir(src_dir_name):
            #每个字的照片数
            dir_length = len(os.listdir(src_dir_name+i))
            #3:7抽样
            test_size = round(0.3 * dir_length)
            test_data = random.sample(os.listdir(src_dir_name+i), k=test_size)
            move_test_data(test_data)
    
    if __name__ == '__main__':
        # 把字分类成800个资料夹
        word_classfier()
        # 分成训练集跟测试集
        test_train_split()
    

    4.2 成果

    • train与test

    • train资料夹内


小结

  1. 在辨识手写中文字时,可能遭遇图档内字迹有部分缺失,导致无法正确辨识。因此,希望能在图档中加入椒盐杂讯(Salt & Pepper Noise),再进行模型训练,降低辨识错误的可能性。
  2. 下一章,是资料前处理的最後一步,目标是:「在图档中随机加入椒盐杂讯,并进行资料扩增」。

让我们继续看下去...


参考资料

  1. HSV颜色空间识别区域颜色
  2. HSV颜色识别-HSV基本颜色分量范围
  3. HSV调色盘-查询HSV值
  4. HSV调色盘-色彩追踪与mask

<<:  Day5. 在没有运转前,世界就是静止的 - Runner

>>:  (Day 20) Object.create 建立多层继承

[13th][Day17] docker registry

image 拥有一个公开的 registry 是很方便的,但有时候会想要 build & 储存 一些...

简单了解VR头盔中,重要且相辅相成的Eye tracking 与Foveated Rendering技术 2

上篇在这里:1 今天来继续简单了解Foveated Rendering吧~ 眼睛的中央处(Fovea...

[day-30] 从U-net 学到了什麽

心得 这次的铁人赛又完赛了,想起第一次参加,学习自己不熟的东西,每天都要实作,实作到最後竟然断赛了,...

[Day06] 什麽是摩尔投票法

#169 - Majority Element 连结: https://leetcode.com/...

# Day 18 Physical Memory Model (三)

直接看下去! 文件 文件原文:Physical Memory Model 翻译: ZONE_DEVI...