COLMAP已知相機內外參數重建稀疏/稠密模型


COLMAP已知相機內外參數重建稀疏/稠密模型

Reconstruct sparse/dense model from known camera poses

參考官方Faq鏈接:https://colmap.github.io/faq.html#reconstruct-sparse-dense-model-from-known-camera-poses

1. 手動指定所有相機內外參

在目錄下手動新建cameras.txt, images.txt, 和 points3D.txt三個文本文件,目錄示例:

+── %ProjectPath%/created/sparse
│   +── cameras.txt
│   +── images.txt
│   +── points3D.txt

將內參(camera intrinsics) 放入cameras.txt, 外參(camera extrinsics)放入 images.txt , points3D.txt 為空。具體格式如下:

cameras.txt 示例:

# Camera list with one line of data per camera:
# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[fx,fy,cx,cy]
# Number of cameras: 2
1 PINHOLE 1280 720 771.904 771.896 639.993 360.001
2 PINHOLE 1280 720 771.899 771.898 639.999 360.001

COLMAP 設定了 SIMPLE_PINHOLEPINHOLESIMPLE_RADIALRADIALOPENCVFULL_OPENCV SIMPLE_RADIAL_FISHEYERADIAL_FISHEYEOPENCV_FISHEYEFOVTHIN_PRISM_FISHEYE 共11種相機模型,其中最常用的為PINHOLE,即小孔相機模型。一般正常拍攝的照片,不考慮畸變即為該模型。手動參數的情況下官方推薦OPENCV相機模型,相比PINHOLE,考慮了xy軸畸變。各模型區別具體可參考Camera Models 查看。請根據自己的已有相機參數選用合適的相機模型。

images.txt 示例:

# Image list with two lines of data per image:
# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME
# POINTS2D[] as (X, Y, POINT3D_ID)
# Number of images: 2, mean observations per image: 2
1 0.430115 0.411564 0.555504 -0.580543 10468.491287 380.313066 1720.465175 1 image001.jpg
# Make sure every other line is left empty
2 0.309712 0.337960 0.655221 -0.600456 10477.663284 446.4208 -1633.886712 2 image002.jpg

3 0.375916 0.401654 0.609703 -0.570633 10592.122754 263.672534 600.636247 3 image003.jpg

COLMAP 中多張圖片可以使用一個相機模型,見 IMAGE_ID 所對應的CAMERA_ID, images.txt 中,每張圖片由一行圖像外參,一行2D點組成。在手動構建中由於沒有2D點,第二行留空。QW, QX, QY, QZ 為四元數表示的相機旋轉信息,TX, TY, TZ為平移向量。

接下來的命令會依賴目錄機構,因此需先明確各級目錄,本文中的命令參考以下目錄結構:

E:\CODE\COLMAPMANUAL\ProjectPath
├─created
│  └─sparse
│   +── cameras.txt
│   +── images.txt
│   +── points3D.txt
├─dense
├─images
│   +── images001.jpg
│   +── images002.jpg
│   +── ...
└─triangulated
    └─sparse

images/ 文件夾下放置需進行重建的圖片,created/sparse/ 文件夾下放入上一步創建的 cameras.txtimages.txtpoints3D.txt

2. 僅指定相機內參由COLMAP進行稀疏重建

2.1. 抽取圖像特征

ProjectPath 目錄下運行命令:

colmap feature_extractor --database_path database.db --image_path images

目錄下會自動生成一個 database.db 文件。終端輸出示例:

==============================================================================
Feature extraction
==============================================================================
Processed file [1/25]
  Name:            image001.jpg
  Dimensions:      1280 x 720
  Camera:          #1 - SIMPLE_RADIAL
  Focal Length:    1536.00px
  Features:        10300
Processed file [2/25]
...
...
Elapsed time: 0.039 [minutes]

可以看出此時使用的相機模型是SIMPLE_RADIAL,這是沒有關系的,因為此時只是抽取特征,官方文檔的說法是此步之后才需要將 cameras.txt 中的相機內參復制到 database.db 中。

2.1.手動導入相機內參

有兩種導入方式:

  • 運行COLMAPGUI程序,選擇 File->New Project->Database->Open 打開我們剛剛建立的 database.dbImages->Select 選擇存放圖片的目錄。點擊 Save 保存。選擇 Processing->Database management ,點擊 Cameras 查看相機模型。這里就需要一行一行選中已有的相機模型進行修改。選中后點擊 Set model ,先選中相機的model類型,如 PINHOLE ,或 OPENCV,注意不同相機的 params 個數可能不一樣:

  • 使用Python腳本更改 database.db ,官方在colmap源碼 scripts\python\database.py 提供了一個fake的創建database腳本。但我們的目的其實是 update 原來的相機模型,這里我寫了一個python腳本,其讀取cameras.txt 中的數據,更新當前目錄下的 database.db 中的相機模型 。代碼如下:

    # This script is based on an original implementation by True Price.
    # Created by liminghao
    import sys
    import numpy as np
    import sqlite3
    
    IS_PYTHON3 = sys.version_info[0] >= 3
    
    def array_to_blob(array):
        if IS_PYTHON3:
            return array.tostring()
        else:
            return np.getbuffer(array)
    
    def blob_to_array(blob, dtype, shape=(-1,)):
        if IS_PYTHON3:
            return np.fromstring(blob, dtype=dtype).reshape(*shape)
        else:
            return np.frombuffer(blob, dtype=dtype).reshape(*shape)
    
    class COLMAPDatabase(sqlite3.Connection):
    
        @staticmethod
        def connect(database_path):
            return sqlite3.connect(database_path, factory=COLMAPDatabase)
    
        def __init__(self, *args, **kwargs):
            super(COLMAPDatabase, self).__init__(*args, **kwargs)
    
            self.create_tables = lambda: self.executescript(CREATE_ALL)
            self.create_cameras_table = \
                lambda: self.executescript(CREATE_CAMERAS_TABLE)
            self.create_descriptors_table = \
                lambda: self.executescript(CREATE_DESCRIPTORS_TABLE)
            self.create_images_table = \
                lambda: self.executescript(CREATE_IMAGES_TABLE)
            self.create_two_view_geometries_table = \
                lambda: self.executescript(CREATE_TWO_VIEW_GEOMETRIES_TABLE)
            self.create_keypoints_table = \
                lambda: self.executescript(CREATE_KEYPOINTS_TABLE)
            self.create_matches_table = \
                lambda: self.executescript(CREATE_MATCHES_TABLE)
            self.create_name_index = lambda: self.executescript(CREATE_NAME_INDEX)
    
        def update_camera(self, model, width, height, params, camera_id):
            params = np.asarray(params, np.float64)
            cursor = self.execute(
                "UPDATE cameras SET model=?, width=?, height=?, params=?, prior_focal_length=True WHERE camera_id=?",
                (model, width, height, array_to_blob(params),camera_id))
            return cursor.lastrowid
    
    def camTodatabase(txtfile):
        import os
        import argparse
    
        camModelDict = {'SIMPLE_PINHOLE': 0,
                        'PINHOLE': 1,
                        'SIMPLE_RADIAL': 2,
                        'RADIAL': 3,
                        'OPENCV': 4,
                        'FULL_OPENCV': 5,
                        'SIMPLE_RADIAL_FISHEYE': 6,
                        'RADIAL_FISHEYE': 7,
                        'OPENCV_FISHEYE': 8,
                        'FOV': 9,
                        'THIN_PRISM_FISHEYE': 10}
        parser = argparse.ArgumentParser()
        parser.add_argument("--database_path", default="database.db")
        args = parser.parse_args()
        if os.path.exists(args.database_path)==False:
            print("ERROR: database path dosen't exist -- please check database.db.")
            return
        # Open the database.
        db = COLMAPDatabase.connect(args.database_path)
    
        idList=list()
        modelList=list()
        widthList=list()
        heightList=list()
        paramsList=list()
        # Update real cameras from .txt
        with open(txtfile, "r") as cam:
            lines = cam.readlines()
            for i in range(0,len(lines),1):
                if lines[i][0]!='#':
                    strLists = lines[i].split()
                    cameraId=int(strLists[0])
                    cameraModel=camModelDict[strLists[1]] #SelectCameraModel
                    width=int(strLists[2])
                    height=int(strLists[3])
                    paramstr=np.array(strLists[4:12])
                    params = paramstr.astype(np.float64)
                    idList.append(cameraId)
                    modelList.append(cameraModel)
                    widthList.append(width)
                    heightList.append(height)
                    paramsList.append(params)
                    camera_id = db.update_camera(cameraModel, width, height, params, cameraId)
    
        # Commit the data to the file.
        db.commit()
        # Read and check cameras.
        rows = db.execute("SELECT * FROM cameras")
        for i in range(0,len(idList),1):
            camera_id, model, width, height, params, prior = next(rows)
            params = blob_to_array(params, np.float64)
            assert camera_id == idList[i]
            assert model == modelList[i] and width == widthList[i] and height == heightList[i]
            assert np.allclose(params, paramsList[i])
    
        # Close database.db.
        db.close()
    
    if __name__ == "__main__":
        camTodatabase("created/sparse/cameras.txt")
    
    

    注意,在 update_camera 函數中我將 prior_focal_length 設為 True,在數據庫中值為1,表示信任預先給定的焦距,如有其他需求,可查看官方文檔修改。

2.2. 特征匹配

ProjectPath 目錄下運行命令:

colmap exhaustive_matcher --database_path database.db

終端輸出示例:

==============================================================================
Exhaustive feature matching
==============================================================================

Matching block [1/1, 1/1] in 13.361s
Elapsed time: 0.225 [minutes]

2.3. 三角測量

ProjectPath 目錄下運行命令:

colmap point_triangulator --database_path database.db --image_path images --input_path created/sparse --output_path triangulated/sparse

終端輸出示例:

==============================================================================
Loading database
==============================================================================

Loading cameras... 25 in 0.000s
Loading matches... 282 in 0.005s
Loading images... 25 in 0.011s (connected 25)
Building correspondence graph... in 0.048s (ignored 0)

Elapsed time: 0.001 [minutes]
==============================================================================
Triangulating image #1
==============================================================================
  => Image has 0 / 7503 points
  => Triangulated 3807 points
...
...
Bundle adjustment report
------------------------
    Residuals : 148710
   Parameters : 38385
   Iterations : 2
         Time : 0.153904 [s]
 Initial cost : 0.291198 [px]
   Final cost : 0.289322 [px]
  Termination : Convergence

  => Merged observations: 7
  => Completed observations: 0
  => Filtered observations: 11
  => Changed observations: 0.000242
==============================================================================
Extracting colors
==============================================================================

這里注意每張圖片 Triangulated Points 數,如果太少,比如小於500,就說明輸入的相機外參有問題,不同視圖之間找不到共同可以觀察到(Triangulate)的點,重建基本是失敗的。關於 Bundle adjustment 是否收斂的問題,如果不收斂,一定失敗了,如果收斂了,也不一定成功。我們可以仍通過運行COLMAPGUI程序, File->Import model 導入剛剛生成的在 triangulated/sparse 下的.bin格式的稀疏重建模型,觀察重建效果。

3. 命令行執行稠密重建

3.1. 使用1中指定相機的內外參進行稠密重建

ProjectPath 目錄下運行命令:

colmap image_undistorter --image_path images --input_path created/sparse --output_path dense

dense/sparse/ 目錄下的.bin文件的內容與 created/sparse 中我們手動建立的.txt文件內容相同。在 dense/stereo/ 目錄下的 patch-match.cfg 規定了源圖像進行塊匹配的參考圖像,默認格式如下:

image001.jpg
__auto__, 20
image002.jpg
__auto__, 20
...
__auto__, 20
image025.jpg
__auto__, 20

圖像名字下一行規定了稠密重建時對應圖像的參考圖像, __auto__, 20 表示自動優先級前20張,但該選項只有進行了三角測量的稀疏重建,有稀疏3D點雲才可用。因為我們提供的images.txt和points3D.txt中的點都是空的,所以這里需要手動指定每個圖片的參考源。在圖像數量少的情況下,可以使用 __all__ 指定所有圖像為參考圖像。或者用空格分隔的圖像名字指定參考圖像,示例:

image001.jpg
image002.jpg, image003.jpg, image004.jpg
image002.jpg
image001.jpg, image003.jpg, image004.jpg, image007.jpg

之后,同樣由於沒有稀疏3D點雲,我們需要手動指定最小深度 depth_min 和最大深度 depth_max 來執行立體匹配,具體數值根據場景來定:

colmap patch_match_stereo --workspace_path dense --PatchMatchStereo.depth_min 0.0 --PatchMatchStereo.depth_max 20.0

融合3D網格:

colmap stereo_fusion --workspace_path dense --output_path dense/fused.ply

3.2. 利用2中僅指定內參由COLMAP進行的稀疏重建,進行稠密重建

ProjectPath 目錄下運行命令:

colmap image_undistorter --image_path images --input_path triangulated/sparse --output_path dense

繼續運行命令:

colmap patch_match_stereo --workspace_path dense

這一步時間較長,與圖片數量,分辨率和顯卡能力都有關系,25張1280*720的圖片在一張GTX 1070顯卡上大概需要13分鍾。
如果需要生成三角網格,繼續運行命令:

colmap stereo_fusion --workspace_path dense --output_path dense/fused.ply

到此,稠密重建結束。如果想可視化重建的深度圖,法線等,可通過運行COLMAPGUI程序, Reconstruction->Dense reconstruction 進入稠密重建菜單,點擊 Select 選擇 dense 文件夾,就可以在應用程序界面選擇每張圖片對應的深度圖和法線圖。

4. 其它

部分手動得到的相機內外參,以旋轉矩陣+平移向量的方式實現,colmap需要轉化為四元數,這里提供一部分matlab代碼:

%旋轉矩陣轉四元數
% R為旋轉矩陣 Quat為四元數向量
q0=0.5*sqrt(1+R(1,1)+R(2,2)+R(3,3));
q1=(R(3,2)-R(2,3))/(4*q0);
q2=(R(1,3)-R(3,1))/(4*q0);
q3=(R(2,1)-R(1,2))/(4*q0);
Quat=[q0 q1 q2 q3];

已有一組相機內參矩陣 K ,相機外參四元數: Quat ,平移向量: T ,寫入至 cameras.txtimages.txt

cam_txt=fopen(cam_txt_path,'w');
image_txt=fopen(image_txt_path,'w');
fprintf(cam_txt,'#By liminghao\n#CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]\n# Number of cameras: %d \n',numofimage);
fprintf(image_txt,'#By liminghao\n#IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME\n# Number of images: %d \n',numofimage);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for i=1:numofimage
    fx=Ktemp{i}(1,1);
    fy=Ktemp{i}(2,2);
    cx=Ktemp{i}(1,3);
    cy=Ktemp{i}(2,3);
    fprintf(cam_txt,'%d PINHOLE %d %d %f %f %f %f\n',i,Width,Height,fx,fy,cx,cy);
    fprintf(image_txt,'%d ',i);
    for qi=1:1:length(Quatvector{i})             
    	fprintf(image_txt,'%f ',Quatvector{i}(qi));
    end
    for ti=1:1:length(Ttemp{i})             
    	fprintf(image_txt,'%f ',Ttemp{i}(ti));
    end
    fprintf(image_txt,'%d image%03d.jpg\n\n',i,i);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fclose(cam_txt);
fclose(image_txt);

以上代碼邏輯比較簡單,用Python等其他語言也較容易實現,這里要注意的就是以上代碼中設置相機模型為 PINHOLE ,使用者請根據自己情況再行修改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM