天天看點

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
本文收錄于CVPR2020,是華為諾亞方舟研究院的成果,主要解決的是,去除對數字螢幕拍照産生摩爾紋,有一定的應用價值。

論文位址:https://arxiv.org/pdf/2004.00406.pdf

代碼位址(Tensorflow+Keras實作):https://github.com/zhenngbolun/Learnbale_Bandpass_Filter

Image demoireing是涉及紋理和顔色恢複的多方面圖像恢複任務。在本文中,提出了一種新穎的多尺度bandpass 卷積神經網絡(MBCNN)來解決這個問題。作為端到端解決方案,MBCNN分别解決了兩個子問題。對于紋理恢複子問題,提出了一個可學習的帶通濾波器(LBF),以了解去除摩爾紋之前的頻率。對于顔色恢複子問題,提出了兩步色調映射政策,該政策首先應用全局色調映射來校正全局色彩shift,然後對每個像素執行顔色的局部微調。通過消融研究,我們證明了MBCNN不同元件的有效性。在兩個公共資料集上的實驗結果表明,本文的方法大大優于最新方法(在PSNR方面超過2dB)。

簡介

數字螢幕在現代日常生活中無處不在:我們在家裡有電視螢幕,在辦公室有筆記本電腦/桌上型電腦螢幕,在公共場所有大尺寸LED螢幕。拍攝這些螢幕的圖檔以快速儲存資訊已成為一種慣例。然而,在對這些螢幕拍照的時候通常會出現波紋圖像,進而降低了照片的圖像品質。當兩個重複的圖案互相幹擾時,出現摩爾紋圖案。在拍攝螢幕圖檔的情況下,相機濾色鏡陣列(CFA)會幹擾螢幕的亞像素布局。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

與去噪、去馬賽克、顔色恒定、銳化等其他圖像修複問題不同,人們對圖像去僞存真(demireing)的關注較少,它是指從被摩爾紋污染的圖像中恢複基本的幹淨圖像。這個問題在很大程度上仍然是一個未解決的問題,由于摩爾紋圖案在頻率、形狀、顔色等方面的巨大變化。

最近的很多工作試圖通過多尺度設計來消除不同頻段的摩爾紋。DMCNN 提出使用具有多分辨率分支的多尺度CNN處理摩爾紋圖案,并對不同尺度的輸出求和以獲得最終輸出。MDDM 通過引入基于動态特征編碼器的自适應執行個體規範化改進了DMCNN。DCNN提出了一種從粗到細的結構來去除兩尺度的摩爾條紋。對粗尺度結果進行上采樣,并将其與細尺度輸入連接配接起來,以進行進一步的殘差學習。MopNet 使用多尺度特征聚合子子產品來處理複雜頻率,并使用另外兩個子子產品來處理邊沿和預定義的波紋類型。本文的模型還采用了針對三個不同比例的分支的多比例設計。在不同尺度之間,本文的模型采用漸進式上采樣政策以平滑地提高分辨率。

本文的方法:Multiscale bandpass CNN

數位相機捕獲的含摩爾紋的圖像可以模組化為:

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

其中ψ-1是ψ的反函數,在圖像處理領域被稱為色調映射函數。以此模型模組化,圖像去摩爾紋任務可以分為兩步,即摩爾條紋去除和色調映射。

1、Multiscale bandpass CNN

為了對遙感圖像中的物體分割前景進行顯式模組化,本文提出了一種前景感覺關系網絡(FarSeg),如圖2所示。FarSeg由特征金字塔網絡(FPN)、前景場景(F-S)關系子產品、輕量級解碼器和前景感覺(F-A)優化組成。FPN負責多尺度對象分割。在F-S關系子產品中,首先将誤報問題表述為前景中缺乏區分性資訊的問題,然後介紹潛在場景語義和F-S關系以改善對前景特征的區分。輕量級解碼器僅設計用于恢複語義特征的空間分辨率。為了使網絡在訓練過程中集中在前景上,提出了F-A優化來減輕前景背景不平衡的問題。

1.1、 Multi-Branch Encoder

整體的模型在三個scales上工作,并具有三種不同類型的blocks,分别是波紋紋理去除塊(MTRB),全局色調映射塊(GTMB)和局部色調映射塊(LTMB)。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

首先将具有h×w×c形狀的輸入圖像I可逆地向下采樣為四個h/2×w/2×4c形狀的子圖像。下面的網絡由三個分支組成,每個分支用于恢複特定比例的波紋圖像,同時每個分支順序地執行摩爾紋去除和色調映射,最終輸出放大後的圖像,并将其融合到更小比例的分支中。在分支I和II中,将目前分支的特征和較粗的縮放分支的輸出特征融合後,将其他GTMB和MTRB堆疊在一起,以消除縮放比例引起的紋理和顔色錯誤。

def MBCNN(nFilters, multi=True):
   conv_func = conv_relu
   def pre_block(x, d_list, enbale = True):
       t = x
       for i in range(len(d_list)):
           _t = conv_func(t, nFilters, 3, dilation_rate=d_list[i])
           t = layers.Concatenate(axis=-1)([_t,t])
       t = conv(t, 64, 3)
       t = adaptive_implicit_trans()(t)
       t = conv(t,nFilters*2,1)
       t = ScaleLayer(s=0.1)(t)
       if not enbale:
           t = layers.Lambda(lambda x: x*0)(t)
       t = layers.Add()([x,t])
       return t

   def pos_block(x, d_list):
       t = x
       for i in range(len(d_list)):
           _t = conv_func(t, nFilters, 3, dilation_rate=d_list[i])
           t = layers.Concatenate(axis=-1)([_t,t])
       t = conv_func(t, nFilters*2, 1)
       return t

   def global_block(x):
       t = layers.ZeroPadding2D(padding=(1,1))(x)
       t = conv_func(t, nFilters*4, 3, strides=(2,2))
       t = layers.GlobalAveragePooling2D()(t)
       t = layers.Dense(nFilters*16,activation='relu')(t)
       t = layers.Dense(nFilters*8, activation='relu')(t)
       t = layers.Dense(nFilters*4)(t)
       _t = conv_func(x, nFilters*4, 1)
       _t = layers.Multiply()([_t,t])
       _t = conv_func(_t, nFilters*2, 1)
       return _t

   output_list = []
   d_list_a = (1,2,3,2,1)
   d_list_b = (1,2,3,2,1)
   d_list_c = (1,2,2,2,1)
   x = layers.Input(shape=(None, None, 3))                 #16m*16m
   _x = Space2Depth(scale=2)(x)
   t1 = conv_func(_x,nFilters*2,3, padding='same')          #8m*8m
   t1 = pre_block(t1, d_list_a, True)
   t2 = layers.ZeroPadding2D(padding=(1,1))(t1)
   t2 = conv_func(t2,nFilters*2,3, padding='valid',strides=(2,2))              #4m*4m
   t2 = pre_block(t2, d_list_b,True)
   t3 = layers.ZeroPadding2D(padding=(1,1))(t2)
   t3 = conv_func(t3,nFilters*2,3, padding='valid',strides=(2,2))              #2m*2m
   t3 = pre_block(t3,d_list_c, True)
   t3 = global_block(t3)
   t3 = pos_block(t3, d_list_c)
   t3_out = conv(t3, 12, 3)
   t3_out = Depth2Space(scale=2)(t3_out)           #4m*4m
   output_list.append(t3_out)
   _t2 = layers.Concatenate()([t3_out,t2])
   _t2 = conv_func(_t2, nFilters*2, 1)
   _t2 = global_block(_t2)
   _t2 = pre_block(_t2, d_list_b,True)
   _t2 = global_block(_t2)
   _t2 = pos_block(_t2, d_list_b)
   t2_out = conv(_t2, 12, 3)
   t2_out = Depth2Space(scale=2)(t2_out)           #8m*8m
   output_list.append(t2_out)
   _t1 = layers.Concatenate()([t1, t2_out])
   _t1 = conv_func(_t1, nFilters*2, 1)
   _t1 = global_block(_t1)
   _t1 = pre_block(_t1, d_list_a, True)
   _t1 = global_block(_t1)
   _t1 = pos_block(_t1, d_list_a)
   _t1 = conv(_t1,12,3)
   y = Depth2Space(scale=2)(_t1)                           #16m*16m
   output_list.append(y)
   if multi != True:
       return models.Model(x,y)
   else:
       return models.Model(x,output_list)
           

複制

1.2、Moire texture removal

摩爾紋可以表示為:

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

按照這種公式,我們可以先估計不同尺度和頻率的波紋紋理的分量,然後基于所有估計的分量重建波紋紋理。Block-DCT是處理頻率相關問題的有效方法。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

其中D表示Block-DCT函數。

Learnable Bandpass Filter

受隐式DCT的啟發,可以用深度CNN直接估計 implicit frequency spectrum(IFS) 。由于變換都是線性的,是以可以用一個簡單的卷積層來模組化。由于Moire紋理的頻譜總是有規律的,我們可以使用帶通濾波器來放大某些頻率,減弱其他頻率。然而,在模組化之前我們很難得到頻譜,因為在不同的尺度上,會有幾個頻率,而且它們也會互相影響。為了解決這個問題,提出了一種可學習的帶通濾波器(LBF)來學習摩爾紋圖像的先驗。LBF為每一個頻率引入了一個可學習的權重。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
class adaptive_implicit_trans(layers.Layer):
   def __init__(self, **kwargs):
       super(adaptive_implicit_trans, self).__init__(**kwargs)

   def build(self, input_shape):
       conv_shape = (1,1,64,64)
       self.it_weights = self.add_weight(
           shape = (1,1,64,1),
           initializer = initializers.get('ones'),
           constraint = constraints.NonNeg(),
           name = 'ait_conv')
       kernel = np.zeros(conv_shape)
       r1 = sqrt(1.0/8)
       r2 = sqrt(2.0/8)
       for i in range(8):
           _u = 2*i+1
           for j in range(8):
               _v = 2*j+1
               index = i*8+j
               for u in range(8):
                   for v in range(8):
                       index2 = u*8+v
                       t = cos(_u*u*pi/16)*cos(_v*v*pi/16)
                       t = t*r1 if u==0 else t*r2
                       t = t*r1 if v==0 else t*r2
                       kernel[0,0,index2,index] = t
       self.kernel = k.variable(value = kernel, dtype = 'float32')

   def call(self, inputs):
       #it_weights = k.softmax(self.it_weights)
       #self.kernel = self.kernel*it_weights
       self.kernel = self.kernel*self.it_weights
       y = k.conv2d(inputs,
                       self.kernel,
                       padding = 'same',
                       data_format='channels_last')
       return y

   def compute_output_shape(self, input_shape):
       return input_shape
           

複制

1.3 Tone mapping 色調映射

RGB顔色空間是一個非常大的空間,包含256的3次方種顔色,是以很難進行逐點色調映射。觀察到摩爾紋圖像和幹淨圖像之間存在顔色偏移,本文提出了一種兩步色調映射政策,其中包含兩種類型的色調映射塊:全局色調映射塊(GTMB)和局部色調映射塊(LTMB)。

全局色調映射塊Global tone mapping block

注意力機制已經被證明在許多任務中是有效的,并且已經提出了幾種通道注意子產品。GTMB可以看作是一個通道注意子產品。然而,GTMB與現有的通道注意子產品在幾個方面有所不同。 首先,現有的通道注意力塊總是由一個Sigmoid單元激活,而GTMB中的γ沒有這樣的限制。其次,通道注意力是直接應用在現有通道注意力塊的輸入上,而GTMB中的γ是應用在局部特征Flocal上。最後,現有的通道注意力子產品的目的是進行自适應的channel-wise特征重新校準;GTMB的目标是進行全局的顔色偏移,避免不規則和不均勻的局部顔色僞影。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

局部色調映射塊Local tone mapping block

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

2、 損失函數

在本文中,将L1損失用作基本損失函數,因為已經證明 L1損失比L2損失對圖像恢複任務更有效。但是,L1損失本身是不夠的,因為它是無法提供結構資訊的逐點損失,而摩爾紋是 structural artifact。本文提出了Advanced Sobel Loss(ASL)來解決此問題。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

與經典Sobel Loss相比,ASL提供了兩個額外的45°方向Loss,它們可以提供更豐富的結構資訊。

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

總的損失函數為:

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?

實驗與結果

資料集: LCDMoire(Aim 2019 challenge on image demoreing: datasetand study. InICCVW, 2019)、TIP2018( Moire photorestoration using multiresolution convolutional neural net-works.)

實驗結果

CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?
CVPR2020 | 對數字螢幕拍照時的摩爾紋怎麼去除?