end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

CPUでの Floor-Plan-Detection の間取り図 Raster to Vector 変換

住宅の間取り図の Raster to Vector変換を行うFloor-Plan-Detection は GPUでの動作を前提?としているようですが、 内部で使用する CubiCasa5k の model_best_val_loss_var.pkl モデルは 200mb程度でしたので、CPUで動作させてみました。

参考url

実行環境

windows 11 + miniconda ( python 3.9 )

git clone や model , blender download

CONDA> git clone https://github.com/rbg-research/Floor-Plan-Detection
CONDA> cd Floor-Plan-Detection

download https://drive.google.com/uc?id=1gRB7ez1e4H7a9Y09lLqRuna0luZO5VRK
as model_best_val_loss_var.pkl ( 203MB )

# vector化したものを 3D表示したい場合に使用します
download https://www.blender.org/download/release/Blender2.93/blender-2.93.1-windows-x64.zip
unzip blender-2.93.1-windows-x64.zip ( 203MB )

source の修正

github.com の Floor-Plan-Detection が 2021年頃の為でしょうか、 python 3.9 でエラーとなる箇所がありましたので、修正

utils/plotting.py

「ValueError: A colormap named "rooms_furu" is already registered.」の エラーとなりましたので、utils/plotting.py を以下のように修正しました

old)   cmap3 = colors.ListedColormap(cpool, 'rooms_furu')
new)   cmap3 = colors.ListedColormap(cpool, 'rooms_icons')

config.py

vector化したものを 更に3D表示まではしないのであれば、変更は不要です

import os

image_path = 'c:/Users/end0t/dev/test_floor_plan.png'
target_path = './floorplan' # will export in two formats (.blend and .stl)
program_path = os.getcwd()
blender_install_path = program_path+"/blender-2.93.1-windows-x64/blender.exe"
blender_script_path = program_path+"/floorplan_to_3dObject_in_blender.py"

SR_scale = 2
SR_method = 'lapsrn'

CubiCasa = True

utils/post_prosessing.py

scipy.stats.mode() の ver.1.9 での仕様変更の為

old) wall_width = stats.mode(widths).mode[0]
new) wall_width = stats.mode(widths, keepdims=True).mode[0]

Floor-Plan-Detection cpu実行版

2D_FloorPlan_to_3D_CubiCasa_ftb.ipynb を改造したものです

import os
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import torch
import torch.nn.functional as F
import cv2
from torch.utils.data import DataLoader

import FloorplanToSTL as stl
import config

from utils.FloorplanToBlenderLib import *
from model import get_model
from utils.loaders import FloorplanSVG, DictToTensor, Compose, RotateNTurns
from utils.plotting import segmentation_plot, polygons_to_image, draw_junction_from_dict
import utils.plotting

from utils.post_prosessing import split_prediction, get_polygons, split_validation
from mpl_toolkits.axes_grid1 import AxesGrid

img_path = "c:/Users/end0t/dev/test_floor_plan.png"
wall_height = 1
scale       = 100
pkl_path = "model_best_val_loss_var.pkl"

room_classes=["Background","Outdoor","Wall","Kitchen","Living Room","Bed Room",
              "Bath","Entry","Railing","Storage","Garage","Undefined"]
icon_classes=["No Icon","Window","Door","Closet","Electrical Applience",
              "Toilet","Sink","Sauna Bench","Fire Place","Bathtub","Chimney"]


def main():
    """ segmentation 可視化で label毎に異なる色を割当てる color map作成。"""
    utils.plotting.discrete_cmap()

    rot:RotateNTurns = RotateNTurns()
    
    """ https://github.com/CubiCasa/CubiCasa5k/tree/master/floortrans/models """
    model:hg_furukawa_original = get_model('hg_furukawa_original', 51)

    split     = [21, len( room_classes ), len( icon_classes )]
    n_classes = split[0] + split[1] + split[2] # = 44
    """ 最終出力層を上書き (class数44に合わせる) """
    model.conv4_ = torch.nn.Conv2d( 256, n_classes, bias=True, kernel_size=1 )
    """ 出力sizeを入力画像に合わせるための upsampling 層 修正 """
    model.upsample = torch.nn.ConvTranspose2d( n_classes,
                                               n_classes,
                                               kernel_size=4,
                                               stride=4 )

    checkpoint = torch.load( pkl_path, map_location='cpu' )  #CPU
    model.load_state_dict( checkpoint['model_state'] )
    model.eval()        # 推論modeへ切替え
    model.to('cpu')     # CPUで動かす

    
    img = cv2.imread(img_path)  # Create tensor for pytorch
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # correct color channels
    img = 2 * (img / 255.0) - 1 # Image transformation to range (-1,1)
    # Move from (h,w,3)--->(3,h,w) as model input dimension is defined like this
    img = np.moveaxis(img, -1, 0)
    
    #img = torch.tensor([img.astype(np.float32)])  # .cuda() 削除 → CPU上に保持
    img = torch.from_numpy(np.expand_dims(img.astype(np.float32), axis=0))

    n_rooms = len( room_classes )
    n_icons = len( icon_classes )

    with torch.no_grad(): # 推論(≠学習)の為、勾配計算 無効化し memory節約
        #Check if shape of image is odd or even
        size_check = np.array([img.shape[2], img.shape[3]]) % 2
        
        height = img.shape[2] - size_check[0]
        width = img.shape[3] - size_check[1]
        img_size = (height, width)

        rotations = [ ( 0,  0),  #  回転なし → 戻しなし
                      ( 1, -1),  #  90度回転 → -90度戻し
                      ( 2,  2),  # 180度回転 → 180度戻し
                      (-1,  1) ] # -90度回転 → +90度戻し

        pred_count = len(rotations)
        prediction = torch.zeros([pred_count, n_classes, height, width])  # CPU上で生成

        for i, r in enumerate( rotations ):
            forward, back = r
            rot_image = rot(img, 'tensor', forward) # 画像を正回転
            pred = model(rot_image)                 # 予測
            pred = rot(pred, 'tensor', back)        # 結果の見た目を戻す
            # heatmap点の意味を戻す(例:icon向き)
            pred = rot(pred, 'points', back)

            # model出力 pred を指定のheight, widthにresize(補間)
            pred = F.interpolate(pred,
                                 size=(height, width),
                                 mode='bilinear',
                                 align_corners=True)
            prediction[i] = pred[0]

    # それぞれの回転での予測結果を平均
    prediction = torch.mean(prediction, 0, True)
    
    rooms_pred = F.softmax(prediction[0, 21:21+12], 0).cpu().data.numpy()
    rooms_pred = np.argmax(rooms_pred, axis=0)

    icons_pred = F.softmax(prediction[0, 21+12:], 0).cpu().data.numpy()
    icons_pred = np.argmax(icons_pred, axis=0)

    heatmaps, rooms, icons = split_prediction(prediction, img_size, split)
    polygons, types, room_polygons, room_types = get_polygons((heatmaps, rooms, icons),
                                                              0.2,
                                                              [1, 2])

    # 壁polygon → 3D変換の準備
    wall_polygon_numbers = [i for i, j in enumerate(types) if j['type'] == 'wall']
    boxes = []
    for i, j in enumerate(polygons):
        if i in wall_polygon_numbers:
            temp = [np.array([k]) for k in j]
            boxes.append(np.array(temp))

    verts, faces, wall_amount = transform.create_nx4_verts_and_faces(boxes,
                                                                     wall_height,
                                                                     scale)
    # Create top walls verts
    verts = []
    for box in boxes:
        verts.extend([transform.scale_point_to_vector(box, scale, 0)])

    # create faces
    faces = []
    for room in verts:
        temp = tuple(range(len(room)))
        faces.append([temp])

    # 部屋やiconの画像を生成
    pol_room_seg, pol_icon_seg = polygons_to_image(polygons,
                                                   types,
                                                   room_polygons,
                                                   room_types,
                                                   height, width )
    # 画面表示
    plt.figure(figsize=(12, 12))
    ax = plt.subplot(1, 1, 1)
    ax.axis('off')
    rseg = ax.imshow(pol_room_seg, cmap='rooms', vmin=0, vmax=n_rooms - 0.1)
    cbar = plt.colorbar(rseg, ticks=np.arange(n_rooms) + 0.5, fraction=0.046, pad=0.01)
    cbar.ax.set_yticklabels(room_classes, fontsize=20)
    plt.tight_layout()
    plt.show()

    
    plt.figure(figsize=(12, 12))
    ax = plt.subplot(1, 1, 1)
    ax.axis('off')
    iseg = ax.imshow(pol_icon_seg, cmap='icons', vmin=0, vmax=n_icons - 0.1)
    cbar = plt.colorbar(iseg, ticks=np.arange(n_icons) + 0.5, fraction=0.046, pad=0.01)
    cbar.ax.set_yticklabels(icon_classes, fontsize=20)
    plt.tight_layout()
    plt.show()

    # blender dataへ変換
    stl.createFloorPlan(image_path  = img_path,
                        target_path = "floorplan",
                        SR_Check=True )

if __name__ == '__main__':
    main()

実行結果

元画像

どこかの不動産サイトにあったものです

部屋や壁の抽出結果 (rooms)

ドアや窓の抽出結果 (icons)

rooms や icons の座標

stl.createFloorPlan()により、Data/以下に text fileが複数出力されます

  • floor_faces.txt
  • floor_verts.txt
  • rooms_faces.txt
  • rooms_verts.txt
  • top_wall_faces.txt
  • top_wall_verts.txt
  • transform.txt
  • wall_faces.txt
  • wall_verts.txt

例えば、wall_verts.txt の抜粋は以下です。 壁面を縦?に見て出力されていますので、「wall_height = 1」の値があります

[[2.74, 11.71, 1], [2.74, 11.81, 1]
 [2.74, 11.71, 0], [2.74, 11.81, 0] ],