一份視覺化特徵圖的程式碼

CV技術指南(公眾號)發表於2022-03-09

 前言 本文給大家分享一份我用的特徵圖視覺化程式碼。

 

歡迎關注公眾號CV技術指南,專注於計算機視覺的技術總結、最新技術跟蹤、經典論文解讀、CV招聘資訊。

 

寫在前面的話


特徵圖視覺化是很多論文所需要做的一份工作,其作用可以是用於證明方法的有效性,也可以是用來增加工作量,給論文湊字數

具體來說就是視覺化兩個圖,使用了新方法的和使用之前的,對比有什麼區別,然後看圖寫論文說明新方法體現的作用。

吐槽一句,有時候這個圖 論文作者自己都不一定能看不懂,雖然確實視覺化的圖有些改變,但並不懂這個改變說明了什麼,反正就吹牛,強行往自己新方法編的故事上扯,就像小學一年級的作文題--看圖寫作文。

之前知乎上有一個很熱門的話題,如果我在baseline上做了一點小小的改進,卻有很大的效果,這能寫論文嗎?

這種情況最大的問題就在於要如何寫七頁以上,那一點點的改進可能寫完思路,公式推理,畫圖等內容才花了不到一頁,剩下的內容如何搞?視覺化特徵圖!!!

這一點可以在我看過的甚多論文上有所體現,反正我是沒看明白論文給的視覺化圖,作者卻能扯那麼多道道。這應該就是用來增加論文字數和增加工作量的。

總之一句話,視覺化特徵圖是很重要的工作,最好要會

 

初始化配置


這部分先完成載入資料,修改網路,定義網路,載入預訓練模型。

載入資料並預處理

這裡只載入一張圖片,就不用通過classdataset了,因為classdataset是針對大量資料的,生成一個迭代器一批一批地將圖片送給網路。但我們仍然要完成classdataset中資料預處理的部分。

資料預處理所必須要有的操作是調整大小,轉化為Tensor格式,歸一化。至於其它資料增強或預處理的操作,自己按需新增。

def image_proprecess(img_path):
    img = Image.open(img_path)
    data_transforms = transforms.Compose([
        transforms.Resize((384, 384), interpolation=3),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
    data = data_transforms(img)
    data = torch.unsqueeze(data,0)
    return data
一份視覺化特徵圖的程式碼

這裡由於只載入一張圖片,因此後面要使用torch.unsqueeze將三維張量變成四維。

 

修改網路

假如你要視覺化某一層的特徵圖,則需要將該層的特徵圖返回出來,因此需要先修改網路中的forward函式。具體修改方式如下所示。

def forward(self, x):
    x = self.model.conv1(x)
    x = self.model.bn1(x)
    x = self.model.relu(x)
    x = self.model.maxpool(x)
    feature = self.model.layer1(x)
    x = self.model.layer2(feature)
    x = self.model.layer3(x)
    x = self.model.layer4(x)
    return feature,x
一份視覺化特徵圖的程式碼

 

定義網路並載入預訓練模型

def Init_Setting(epoch):
    dirname = '/mnt/share/VideoReID/share/models/Methods5_trial1'
    model = siamese_resnet50(701, stride=1, pool='avg')
    trained_path = os.path.join(dirname, 'net_%03d.pth' % epoch)
    print("load %03d.pth" % epoch)
    model.load_state_dict(torch.load(trained_path))
    model = model.cuda().eval()
    return model
一份視覺化特徵圖的程式碼

這部分需要說明的是最後一行,要將網路設定為推理模式。

 

視覺化特徵圖


這部分主要是將特徵圖的某一通道轉化為一張圖來視覺化。

def visualize_feature_map(img_batch,out_path,type,BI):
    feature_map = torch.squeeze(img_batch)
    feature_map = feature_map.detach().cpu().numpy()

    feature_map_sum = feature_map[0, :, :]
    feature_map_sum = np.expand_dims(feature_map_sum, axis=2)
    for i in range(0, 2048):
        feature_map_split = feature_map[i,:, :]
        feature_map_split = np.expand_dims(feature_map_split,axis=2)
        if i > 0:
            feature_map_sum +=feature_map_split
        feature_map_split = BI.transform(feature_map_split)

        plt.imshow(feature_map_split)
        plt.savefig(out_path + str(i) + "_{}.jpg".format(type) )
        plt.xticks()
        plt.yticks()
        plt.axis('off')

    feature_map_sum = BI.transform(feature_map_sum)
    plt.imshow(feature_map_sum)
    plt.savefig(out_path + "sum_{}.jpg".format(type))
    print("save sum_{}.jpg".format(type))

一份視覺化特徵圖的程式碼

這裡一行一行來解釋。

1. 引數img_batch是從網路中的某一層傳回來的特徵圖,BI是雙線性插值的函式,自定義的,下面會講。

2. 由於只視覺化了一張圖片,因此img_batch是四維的,且batchsize維為1。第三行將它從GPU上弄到CPU上,並變成numpy格式。

3. 剩下部分主要完成將每個通道變成一張圖,以及將所有通道每個元素對應位置相加,並儲存。

 

雙線性插值


由於經過多次網路降取樣,後面層的特徵圖往往變得只有7x7,16x16大小。視覺化後特別小,因此需要將它上取樣,這裡取樣的方式是雙線性插值。因此,這裡給一份雙線性插值的程式碼。

class BilinearInterpolation(object):
    def __init__(self, w_rate: float, h_rate: float, *, align='center'):
        if align not in ['center', 'left']:
            logging.exception(f'{align} is not a valid align parameter')
            align = 'center'
        self.align = align
        self.w_rate = w_rate
        self.h_rate = h_rate

    def set_rate(self,w_rate: float, h_rate: float):
        self.w_rate = w_rate    # w 的縮放率
        self.h_rate = h_rate    # h 的縮放率

    # 由變換後的畫素座標得到原影像的座標    針對高
    def get_src_h(self, dst_i,source_h,goal_h) -> float:
        if self.align == 'left':
            # 左上角對齊
            src_i = float(dst_i * (source_h/goal_h))
        elif self.align == 'center':
            # 將兩個影像的幾何中心重合。
            src_i = float((dst_i + 0.5) * (source_h/goal_h) - 0.5)
        src_i += 0.001
        src_i = max(0.0, src_i)
        src_i = min(float(source_h - 1), src_i)
        return src_i
    # 由變換後的畫素座標得到原影像的座標    針對寬
    def get_src_w(self, dst_j,source_w,goal_w) -> float:
        if self.align == 'left':
            # 左上角對齊
            src_j = float(dst_j * (source_w/goal_w))
        elif self.align == 'center':
            # 將兩個影像的幾何中心重合。
            src_j = float((dst_j + 0.5) * (source_w/goal_w) - 0.5)
        src_j += 0.001
        src_j = max(0.0, src_j)
        src_j = min((source_w - 1), src_j)
        return src_j

    def transform(self, img):
        source_h, source_w, source_c = img.shape  # (235, 234, 3)
        goal_h, goal_w = round(
            source_h * self.h_rate), round(source_w * self.w_rate)
        new_img = np.zeros((goal_h, goal_w, source_c), dtype=np.uint8)

        for i in range(new_img.shape[0]):       # h
            src_i = self.get_src_h(i,source_h,goal_h)
            for j in range(new_img.shape[1]):
                src_j = self.get_src_w(j,source_w,goal_w)
                i2 = ceil(src_i)
                i1 = int(src_i)
                j2 = ceil(src_j)
                j1 = int(src_j)
                x2_x = j2 - src_j
                x_x1 = src_j - j1
                y2_y = i2 - src_i
                y_y1 = src_i - i1
                new_img[i, j] = img[i1, j1]*x2_x*y2_y + img[i1, j2] * \
                    x_x1*y2_y + img[i2, j1]*x2_x*y_y1 + img[i2, j2]*x_x1*y_y1
        return new_img
#使用方法
BI = BilinearInterpolation(8, 8)
feature_map = BI.transform(feature_map)
一份視覺化特徵圖的程式碼

 

main函式流程


上面介紹了各個部分的程式碼,下面就是整體流程。比較簡單。

imgs_path = "/path/to/imgs/"
save_path = "/save/path/to/output/"
model = Init_Setting(120)
BI = BilinearInterpolation(8, 8)

data = image_proprecess(out_path + "0836.jpg")
data = data.cuda()
output, _ = model(data)
visualize_feature_map(output, save_path, "drone", BI)
一份視覺化特徵圖的程式碼

 

視覺化效果圖


一份視覺化特徵圖的程式碼

歡迎關注公眾號CV技術指南,專注於計算機視覺的技術總結、最新技術跟蹤、經典論文解讀、CV招聘資訊。

在公眾號中回覆關鍵字“入門指南”可獲取計算機視覺入門資料。

CV技術指南建立了一個交流氛圍很不錯的群,除了太偏僻的問題,幾乎有問必答。關注公眾號新增編輯的微訊號可邀請加交流群。

一份視覺化特徵圖的程式碼

 

其它文章

目標檢測中的框位置優化總結

從零搭建Pytorch模型教程(一)資料讀取

自編碼器綜述論文:概念、圖解和應用

解決影像分割落地場景真實問題,港中文等提出:開放世界實體分割

目標檢測、例項分割、多目標跟蹤的Anchor-free應用方法總結

ICLR2022 | cosformer : 重新思考在注意力中的softmax

ICLR2022 | ViDT: 一個有效且高效的純transformer目標檢測器

關於快速學習一項新技術或新領域的一些個人思維習慣與思想總結

Panoptic SegFormer:端到端的 Transformer 全景分割通用框架

CVPR2021 | TrivialAugment:不用調優的SOTA資料增強策略

ICCV2021 | 簡單有效的長尾視覺識別新方案:蒸餾自監督(SSD)

AAAI2021 | 任意方向目標檢測中的動態Anchor學習

ICCV2021 | 用於視覺跟蹤的學習時空型transformer

ICCV2021 | 漸進取樣式Vision Transformer

MobileVIT:輕量級視覺Transformer+移動端部署

ICCV2021 | SOTR:使用transformer分割物體

ICCV2021 | PnP-DETR:用Transformer進行高效的視覺分析

ICCV2021 | Vision Transformer中相對位置編碼的反思與改進

ICCV2021 | 重新思考視覺transformers的空間維度

CVPR2021 | TransCenter: transformer用於多目標跟蹤演算法

CVPR2021|特徵金字塔的新方式YOLOF

CVPR2021 | 重新思考BatchNorm中的Batch


相關文章