DAY14:玉山人工智慧挑战赛-中文手写字辨识(OpenCV图像处理)

问题及解决方法

  • 用YOLOv4模型裁切出来的文字,大部分的图档,都有红框等杂讯的存在,如下图。若将含有杂讯的图档丢进模型训练,可能会造成失焦而影响准确度。
      
    解决:我们选择以HSV来做颜色的追踪,以抓取红框杂讯为主。
  • 图档的中文字有蓝色及黑色居多,字体颜色有可能也会影响我们的模型准确度。
    解决:采取将图片灰阶的方法,就没有颜色的影响。

操作内容

  • HSV(Hue, Saturation, Value)

    • 由色调(Hue)、饱和度(Saturation)、亮度(Value)来组成,可表示维下图的圆锥体。

      图片来源:https://read01.com/6mD5Kx.html#.YVQfV5pByUk
      • 色调:即为颜色。
      • 饱和度:色彩的纯度,若值越高越纯,反之则会略显灰。
      • 亮度:即为颜色亮度,值越大越亮,若值为0则呈现黑色。
    • HSV也是依赖RGB,红色、绿色及蓝色,是一个颜色空间,方便我们识别。
  • HSV+OpenCV

    • 透过OpenCV可以使用内建函数来将RGB转换成HSV。
    # 将RGB转换成HSV颜色空间
      redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    • 转换成HSV後就可以来将特定的颜色找出来,助於我们找出红框的颜色,加以去除。

    • 首先,我们需要用到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('./pic/1_经.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()
    
    

    • 透过这样点选的方式,可以找出红线的值,我们就可以将它取出。我们先锁定红线,慢慢的点,大概有个阈值出来。下列程序码为追踪红线。

      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
      

      可以发现我们追踪到上面没追踪到的红色部分,还缺少最右边的红色线条,因为他有跟中文字重叠。

    • 接下来我们将追踪到的两个红色线条合起来。

      #红1
      mask1 = red1_mask(img)
      #红2
      mask2 = red2_mask(img)
      #红1 + 红2 范围
      mask3 = cv2.bitwise_or(mask1,mask2)
      

      透过bitwise_or取联集,使两张图合并在一起。

    • 取出红线之後,我们需要将他去除掉,也就是用背景颜色去填补,在这之前我们先将他膨胀,然後白色的地方转成黑色的。

      def my_dilate(img):
          kernel = np.ones((3, 3), np.uint8)
          new_img = cv2.dilate(img, kernel, iterations=1)
          return new_img
      
      # 膨胀mask3
      mask3 = my_dilate(mask3)
      #黑白反转
      mask3 = cv2.bitwise_not(mask3,mask3)
      


      黑框是特别截图下来,以示整张图片,主要是白色变黑色。

    • 先做一个动作,将原本的图片做灰阶,再来就是黑色部份我们到时候要用背景颜色来填补,所以我们要取出最常出现的颜色,也就是取众数。以下程序码。

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


      这样我们就成功把红线的杂讯去除了。

    • 这边图片还是有点模糊,我们做了高斯滤波,让图片较平滑,左边是原图,右边是高斯滤波过後的图,可以看出较平滑

      blur = cv2.GaussianBlur(image,(3,3),0)
      

    • 这边当时遇到了小问题,当时在选择灰阶和二质化的图档做训练的时候,两种我们都有尝试过,但後来发现灰阶的资讯较多,所以我们选择用灰阶的方式去训练图片。
      二质化               灰阶


分割资料夹

  • 处理完图片,我们将资料集分成train和test,比例7:3,接着就是准备进模型做训练罗!
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() 


今日小结

  • 通过HSV和OpenCV的结合,可以处理很多图片的杂讯,我们或许不是处理的最完美,但这次比赛让我们学到用OpenCV,很有趣,有很多可塑性。做影像辨识不可或缺的一项套件之一。
  • 处理完资料,就要准备丢入模型训练了,今天就先到这边吧!!
  • 小弟初学OpenCV,若有讲错的地方,请欢迎留言告知我喔><

<<:  Day 29 洞悉消费者的心

>>:  Day15 Vue directives(v-for)下

DAY 18 - 九尾狐妹妹 (2) 线稿

大家好~ 我是五岁~ 今天来继续改善昨天的九尾狐草图吧~ 首先把耳朵厚度加厚了,并且加上毛茸茸的前饰...

Day41 ( 游戏设计 ) 翻翻卡 ( 卡牌记忆 )

翻翻卡 ( 卡牌记忆 ) 教学原文参考:翻翻卡 ( 卡牌记忆 ) 这篇文章会介绍,如何在 Scrat...

Ruby on Rails 继承(Inheritance)与开放类别

到目前为止的范例都是只有单一类别,但在真实的世界里其实是更复杂的,像是如果想要再加入一个小狗类别: ...

虹语岚访仲夏夜-17(打杂的Allen篇)

感觉经历了一段乱流区,我走到柜台,跟太子点了杯冰咖啡,就在等咖啡的时候,门打开了,看到一个身影,坐到...

CSS微动画 - Slot的变化!数字钟也可以动起来

Q: 动画影片看起来卡卡的? A: 请各位见谅,跑起来真真是顺畅的呢! 上一篇的Slot效果以父层...