六種常見的數據擴增方式(附代碼)

在某些場景下的目標檢測中,樣本數量較小,致使檢測的效果比較差,這時就須要進行數據擴增。本文介紹經常使用的6類數據擴增方式,包括裁剪、平移、改變亮度、加入噪聲、旋轉角度以及鏡像。
html

每一部分的參考資料也附在代碼的介紹位置,你們能夠參考。python

裁剪(須要改變bbox):裁剪後的圖片須要包含全部的框,不然會對圖像的原始標註形成破壞。app

 def _crop_img_bboxes(self,img,bboxes):
        '''
        裁剪後圖片要包含全部的框
        輸入:
            img:圖像array
            bboxes:該圖像包含的全部boundingboxes,一個list,每一個元素爲[x_min,y_min,x_max,y_max]
                    要確保是數值
        輸出:
            crop_img:裁剪後的圖像array
            crop_bboxes:裁剪後的boundingbox的座標,list
        '''
        #------------------ 裁剪圖像 ------------------
        w = img.shape[1]
        h = img.shape[0]
        
        x_min = w
        x_max = 0
        y_min = h
        y_max = 0
        for bbox in bboxes:
            x_min = min(x_min, bbox[0])
            y_min = min(y_min, bbox[1])
            x_max = max(x_max, bbox[2])
            y_max = max(y_max, bbox[3])
            name = bbox[4]
        
        # 包含全部目標框的最小框到各個邊的距離
        d_to_left = x_min
        d_to_right = w - x_max
        d_to_top = y_min
        d_to_bottom = h - y_max
        
        # 隨機擴展這個最小範圍
        crop_x_min = int(x_min - random.uniform(0, d_to_left))
        crop_y_min = int(y_min - random.uniform(0, d_to_top))
        crop_x_max = int(x_max + random.uniform(0, d_to_right))
        crop_y_max = int(y_max + random.uniform(0, d_to_bottom))
        
        # 確保不出界
        crop_x_min = max(0, crop_x_min)
        crop_y_min = max(0, crop_y_min)
        crop_x_max = min(w, crop_x_max)
        crop_y_max = min(h, crop_y_max)
        
        crop_img = img[crop_y_min:crop_y_max, crop_x_min:crop_x_max]
        
        #------------------ 裁剪bounding boxes ------------------
        crop_bboxes = list()
        for bbox in bboxes:
            crop_bboxes.append([bbox[0]-crop_x_min, bbox[1]-crop_y_min,
                               bbox[2]-crop_x_min, bbox[3]-crop_y_min,name])
        
        return crop_img, crop_bboxes

平移(須要改變bbox):平移後的圖片須要包含全部的框,不然會對圖像的原始標註形成破壞。dom

def _shift_pic_bboxes(self, img, bboxes):
        '''
        平移後須要包含全部的框
        參考資料:https://blog.csdn.net/sty945/article/details/79387054
        輸入:
            img:圖像array
            bboxes:該圖像包含的全部boundingboxes,一個list,每一個元素爲[x_min,y_min,x_max,y_max]
                    要確保是數值
        輸出:
            shift_img:平移後的圖像array
            shift_bboxes:平移後的boundingbox的座標,list
        '''
        #------------------ 平移圖像 ------------------
        w = img.shape[1]
        h = img.shape[0]
        
        x_min = w
        x_max = 0
        y_min = h
        y_max = 0
        for bbox in bboxes:
            x_min = min(x_min, bbox[0])
            y_min = min(y_min, bbox[1])
            x_max = max(x_max, bbox[2])
            y_max = max(x_max, bbox[3])
            name = bbox[4]
        
        # 包含全部目標框的最小框到各個邊的距離,即每一個方向的最大移動距離
        d_to_left = x_min
        d_to_right = w - x_max
        d_to_top = y_min
        d_to_bottom = h - y_max
        
        #在矩陣第一行中表示的是[1,0,x],其中x表示圖像將向左或向右移動的距離,若是x是正值,則表示向右移動,若是是負值的話,則表示向左移動。 
        #在矩陣第二行表示的是[0,1,y],其中y表示圖像將向上或向下移動的距離,若是y是正值的話,則向下移動,若是是負值的話,則向上移動。
        x = random.uniform(-(d_to_left/3), d_to_right/3)
        y = random.uniform(-(d_to_top/3), d_to_bottom/3)
        M = np.float32([[1, 0, x], [0, 1, y]])
        
        # 仿射變換
        shift_img = cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) #第一個參數表示咱們但願進行變換的圖片,第二個參數是咱們的平移矩陣,第三個但願展現的結果圖片的大小
        
        #------------------ 平移boundingbox ------------------
        shift_bboxes = list()
        for bbox in bboxes:
            shift_bboxes.append([bbox[0]+x, bbox[1]+y, bbox[2]+x, bbox[3]+y, name])
        
        return shift_img, shift_bboxes

改變亮度:改變亮度比較簡單,不須要處理bounding boxes函數

def _changeLight(self,img):
        '''
        adjust_gamma(image, gamma=1, gain=1)函數:
        gamma>1時,輸出圖像變暗,小於1時,輸出圖像變亮
        輸入:
            img:圖像array
        輸出:
            img:改變亮度後的圖像array
        '''
        flag = random.uniform(0.5, 1.5) ##flag>1爲調暗,小於1爲調亮
        return exposure.adjust_gamma(img, flag) 

加入噪聲:加入噪聲也比較簡單,不須要處理bounding boxes.net

    def _addNoise(self,img):
        '''
        輸入:
            img:圖像array
        輸出:
            img:加入噪聲後的圖像array,因爲輸出的像素是在[0,1]之間,因此得乘以255
        '''
        return random_noise(img, mode='gaussian', clip=True) * 255

旋轉:旋轉後的圖片須要包含全部的框,不然會對圖像的原始標註形成破壞。須要注意的是,旋轉時圖像的一些邊角可能會被切除掉,須要避免這種狀況。orm

 def _rotate_img_bboxes(self, img, bboxes, angle=5, scale=1.):
        '''
        參考:https://blog.csdn.net/saltriver/article/details/79680189
              https://www.ctolib.com/topics-44419.html
        關於仿射變換:https://www.zhihu.com/question/20666664
        輸入:
            img:圖像array,(h,w,c)
            bboxes:該圖像包含的全部boundingboxs,一個list,每一個元素爲[x_min, y_min, x_max, y_max],要確保是數值
            angle:旋轉角度
            scale:默認1
        輸出:
            rot_img:旋轉後的圖像array
            rot_bboxes:旋轉後的boundingbox座標list
        '''
        #---------------------- 旋轉圖像 ----------------------
        w = img.shape[1]
        h = img.shape[0]
        # 角度變弧度
        rangle = np.deg2rad(angle)
        # 計算新圖像的寬度和高度,分別爲最高點和最低點的垂直距離
        nw = (abs(np.sin(rangle)*h) + abs(np.cos(rangle)*w))*scale  
        nh = (abs(np.cos(rangle)*h) + abs(np.sin(rangle)*w))*scale
        # 獲取圖像繞着某一點的旋轉矩陣
        # getRotationMatrix2D(Point2f center, double angle, double scale)
                            # Point2f center:表示旋轉的中心點
                            # double angle:表示旋轉的角度
                            # double scale:圖像縮放因子
                            #參考:https://cloud.tencent.com/developer/article/1425373
        rot_mat = cv2.getRotationMatrix2D((nw*0.5, nh*0.5), angle, scale) # 返回 2x3 矩陣
        # 新中心點與舊中心點之間的位置
        rot_move = np.dot(rot_mat,np.array([(nw-w)*0.5, (nh-h)*0.5,0]))
        # the move only affects the translation, so update the translation
        # part of the transform
        rot_mat[0,2] += rot_move[0]
        rot_mat[1,2] += rot_move[1]
        # 仿射變換
        rot_img = cv2.warpAffine(img, rot_mat, (int(math.ceil(nw)), int(math.ceil(nh))), flags=cv2.INTER_LANCZOS4) # ceil向上取整
        
        #---------------------- 矯正boundingbox ----------------------
        # rot_mat是最終的旋轉矩陣
        # 獲取原始bbox的四個中點,而後將這四個點轉換到旋轉後的座標系下
        rot_bboxes = list()
        for bbox in bboxes:
            x_min = bbox[0]
            y_min = bbox[1]
            x_max = bbox[2]
            y_max = bbox[3]
            name = bbox[4]
            point1 = np.dot(rot_mat, np.array([(x_min+x_max)/2, y_min,1]))
            point2 = np.dot(rot_mat, np.array([x_max, (y_min+y_max)/2, 1]))
            point3 = np.dot(rot_mat, np.array([(x_min+x_max)/2, y_max, 1]))
            point4 = np.dot(rot_mat, np.array([x_min, (y_min+y_max)/2, 1]))
            
            # 合併np.array
            concat = np.vstack((point1, point2,point3,point4)) # 在豎直方向上堆疊
            # 改變array類型
            concat = concat.astype(np.int32)
            # 獲得旋轉後的座標
            rx, ry, rw, rh = cv2.boundingRect(concat)
            rx_min = rx
            ry_min = ry
            rx_max = rx+rw
            ry_max = ry+rh
            # 加入list中
            rot_bboxes.append([rx_min, ry_min, rx_max, ry_max,name])
        
        return rot_img, rot_bboxes

鏡像:旋轉後的圖片須要包含全部的框,不然會對圖像的原始標註形成破壞。這裏只介紹兩種鏡像方式:水平翻轉和垂直翻轉htm

 # 鏡像
    def _flip_pic_bboxes(self, img, bboxes):
        '''
        參考:https://blog.csdn.net/jningwei/article/details/78753607
        鏡像後的圖片要包含全部的框
        輸入:
            img:圖像array
            bboxes:該圖像包含的全部boundingboxs,一個list,每一個元素爲[x_min, y_min, x_max, y_max],要確保是數值
        輸出:
            flip_img:鏡像後的圖像array
            flip_bboxes:鏡像後的bounding box的座標list
        '''
        # ---------------------- 鏡像圖像 ----------------------
        import copy
        flip_img = copy.deepcopy(img)
        if random.random() < 0.5:
            horizon = True
        else:
            horizon = False
        h, w, _ = img.shape
        if horizon: # 水平翻轉
            flip_img = cv2.flip(flip_img, -1)
        else:
            flip_img = cv2.flip(flip_img, 0)
        # ---------------------- 矯正boundingbox ----------------------
        flip_bboxes = list()
        for bbox in bboxes:
            x_min = bbox[0]
            y_min = bbox[1]
            x_max = bbox[2]
            y_max = bbox[3]
            name = bbox[4]
            if horizon:
                flip_bboxes.append([w-x_max, y_min, w-x_min, y_max, name])
            else:
                flip_bboxes.append([x_min, h-y_max, x_max, h-y_min, name])
        
        return flip_img, flip_bboxes
相關文章
相關標籤/搜索