diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..515de1d89 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:20.04 + +WORKDIR /app +ENV preset=2 +ENV mode=1 +ENV nolog=0 +ENV reverse=0 +ENV nomt=1 +ENV speed=0 +ENV nogui=1 +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update && \ + apt install -y git +RUN git clone https://github.com/IldarGreat/dso.git +RUN apt-get install -y build-essential \ + libsuitesparse-dev libeigen3-dev libboost-all-dev \ + libopencv-dev + +WORKDIR /app +RUN git clone --recursive https://github.com/stevenlovegrove/Pangolin.git -b v0.6 +WORKDIR /app/Pangolin +RUN apt-get install -y cmake && \ + apt-get clean && \ + apt install -y libglew-dev && \ + apt-get install -y libegl1-mesa-dev + +RUN cmake -B build && \ + cmake --build build + +RUN apt-get install -y zlib1g-dev +WORKDIR /app/dso/thirdparty/ +RUN tar -zxvf libzip-1.1.1.tar.gz +WORKDIR /app/dso/thirdparty/libzip-1.1.1/ +RUN ./configure && \ + make && \ + cp lib/zipconf.h /usr/local/include/zipconf.h + +RUN git submodule update --init + +WORKDIR /app/dso/build +RUN cmake .. && \ + make + +ENTRYPOINT ./bin/dso_dataset files=set/sequence calib=set/camera.txt mode=${mode} preset=${preset} nolog=${nolog} reverse=${reverse} nomt=${nomt} speed=${speed} nogui=${nogui} diff --git a/README.md b/README.md index 50a9f5952..e2234ebae 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,32 @@ For more information see * **A Photometrically Calibrated Benchmark For Monocular Visual Odometry**, *J. Engel, V. Usenko, D. Cremers*, In arXiv:1607.02555, 2016 Get some datasets from [https://vision.in.tum.de/mono-dataset](https://vision.in.tum.de/mono-dataset) . - -### 2. Installation - - git clone https://github.com/JakobEngel/dso.git - -#### 2.1 Required Dependencies +### 2. Installation with docker + +#### 2.1 Get image +##### Build image from Dockerfile. + git clone https://github.com/IldarGreat/dso.git + docker build . -t ildarthegreat/dso +##### Or pull image from dockerhub + docker pull ildarthegreat/dso +##### If you have arm based system + docker pull ildarthegreat/dsoarm64 +#### 2.2 Run container + docker run \ + -v {path}:/app/dso/build/set \ + -e mode=2 \ + ... other environment (see Dockerfile) + ildarthegreat/dso (or ildarthegreat/dsoarm64) +path - it's folder that contains the calib file camera.txt and another folder named sequence that contains a sequence of images
+Example: .../path
+ -camera.txt
+ -sequence + +### 3. Installation from source code + + git clone https://github.com/IldarGreat/dso.git + +#### 3.1 Required Dependencies ##### suitesparse and eigen3 (required). Required. Install with @@ -22,7 +42,7 @@ Required. Install with -#### 2.2 Optional Dependencies +#### 3.2 Optional Dependencies ##### OpenCV (highly recommended). Used to read / write / display images. @@ -37,7 +57,7 @@ Install with sudo apt-get install libopencv-dev -##### Pangolin (highly recommended). +##### Pangolin v0.4! (highly recommended). Used for 3D visualization & the GUI. Pangolin is **only** used in `IOWrapper/Pangolin/*`. You can compile without Pangolin, however then there is not going to be any visualization / GUI capability. @@ -46,6 +66,16 @@ and use it instead of `PangolinDSOViewer` Install from [https://github.com/stevenlovegrove/Pangolin](https://github.com/stevenlovegrove/Pangolin) +Or install like bellow + + git clone --recursive https://github.com/stevenlovegrove/Pangolin.git -b v0.6 + apt-get clean + sudo apt install libglew-dev + sudo apt-get install libegl1-mesa-dev + cd Pangolin + cmake -B build + cmake --build build + ##### ziplib (recommended). Used to read datasets with images as .zip, as e.g. in the TUM monoVO dataset. @@ -64,7 +94,7 @@ to unzip the dataset image archives before loading them). ##### sse2neon (required for ARM builds). After cloning, just run `git submodule update --init` to include this. It translates Intel-native SSE functions to ARM-native NEON functions during the compilation process. -#### 2.3 Build +#### 3.3 Build cd dso mkdir build @@ -76,12 +106,12 @@ this will compile a library `libdso.a`, which can be linked from external projec It will also build a binary `dso_dataset`, to run DSO on datasets. However, for this OpenCV and Pangolin need to be installed. +If you have error with boost, please add #include to IndexThreadReduce - -### 3 Usage +### 4 Usage Run on a dataset from [https://vision.in.tum.de/mono-dataset](https://vision.in.tum.de/mono-dataset) using bin/dso_dataset \ @@ -98,17 +128,17 @@ other camera drivers, to use DSO interactively without ROS. -#### 3.1 Dataset Format. +#### 4.1 Dataset Format. The format assumed is that of [https://vision.in.tum.de/mono-dataset](https://vision.in.tum.de/mono-dataset). However, it should be easy to adapt it to your needs, if required. The binary is run with: -- `files=XXX` where XXX is either a folder or .zip archive containing images. They are sorted *alphabetically*. for .zip to work, need to comiple with ziplib support. +- `files=XXX` where XXX is either a folder or .zip archive containing images. They are sorted *alphabetically*. for .zip to work, need to comiple with ziplib support. You can use video_to_images.py in utils folder to split video into images. For example python video_to_images.py -v "path to video" . It create a folder 'images' desired sequence of images -- `gamma=XXX` where XXX is a gamma calibration file, containing a single row with 256 values, mapping [0..255] to the respective irradiance value, i.e. containing the *discretized inverse response function*. See TUM monoVO dataset for an example. +- `gamma=XXX` where XXX is a gamma calibration file, containing a single row with 256 values, mapping [0..255] to the respective irradiance value, i.e. containing the *discretized inverse response function*. See TUM monoVO dataset for an example or read this https://github.com/tum-vision/online_photometric_calibration - `vignette=XXX` where XXX is a monochrome 16bit or 8bit image containing the vignette as pixelwise attenuation factors. See TUM monoVO dataset for an example. -- `calib=XXX` where XXX is a geometric camera calibration file. See below. +- `calib=XXX` where XXX is a geometric camera calibration file. See below. If you want you can use calibration.py in utils it use chessboard 6*9, to check camera calibration settings. For example python calibration.py -i "path to images of chessboard" @@ -117,14 +147,14 @@ However, it should be easy to adapt it to your needs, if required. The binary is ###### Calibration File for Pre-Rectified Images - Pinhole fx fy cx cy 0 + fx fy cx cy 0 in_width in_height "crop" / "full" / "none" / "fx fy cx cy 0" out_width out_height ###### Calibration File for FOV camera model: - FOV fx fy cx cy omega + fx fy cx cy omega in_width in_height "crop" / "full" / "fx fy cx cy 0" out_width out_height @@ -132,7 +162,7 @@ However, it should be easy to adapt it to your needs, if required. The binary is ###### Calibration File for Radio-Tangential camera model - RadTan fx fy cx cy k1 k2 r1 r2 + fx fy cx cy k1 k2 r1 r2 in_width in_height "crop" / "full" / "fx fy cx cy 0" out_width out_height @@ -140,7 +170,7 @@ However, it should be easy to adapt it to your needs, if required. The binary is ###### Calibration File for Equidistant camera model - EquiDistant fx fy cx cy k1 k2 k3 k4 + fx fy cx cy k1 k2 k3 k4 in_width in_height "crop" / "full" / "fx fy cx cy 0" out_width out_height @@ -183,7 +213,7 @@ outliers along those borders, and corrupt the scale-pyramid). -#### 3.2 Commandline Options +#### 4.2 Commandline Options there are many command line options available, see `main_dso_pangolin.cpp`. some examples include - `mode=X`: - `mode=0` use iff a photometric calibration exists (e.g. TUM monoVO dataset). @@ -210,11 +240,11 @@ there are many command line options available, see `main_dso_pangolin.cpp`. some -#### 3.3 Runtime Options +#### 4.3 Runtime Options Some parameters can be reconfigured from the Pangolin GUI at runtime. Feel free to add more. -#### 3.4 Accessing Data. +#### 4.4 Accessing Data. The easiest way to access the Data (poses, pointclouds, etc.) computed by DSO (in real-time) is to create your own `Output3DWrapper`, and add it to the system, i.e., to `FullSystem.outputWrapper`. The respective member functions will be called on various occations (e.g., when a new KF is created, @@ -233,7 +263,7 @@ using the TUM RGB-D / TUM monoVO format ([timestamp x y z qx qy qz qw] of the ca -#### 3.5 Notes +#### 4.5 Notes - the initializer is very slow, and does not work very reliably. Maybe replace by your own way to get an initialization. - see [https://github.com/JakobEngel/dso_ros](https://github.com/JakobEngel/dso_ros) for a minimal example project on how to use the library with your own input / output procedures. - see `settings.cpp` for a LOT of settings parameters. Most of which you shouldn't touch. @@ -242,7 +272,7 @@ using the TUM RGB-D / TUM monoVO format ([timestamp x y z qx qy qz qw] of the ca -### 4 General Notes for Good Results +### 5 General Notes for Good Results #### Accurate Geometric Calibration - Please have a look at Chapter 4.3 from the DSO paper, in particular Figure 20 (Geometric Noise). Direct approaches suffer a LOT from bad geometric calibrations: Geometric distortions of 1.5 pixel already reduce the accuracy by factor 10. @@ -272,7 +302,7 @@ little rotation) during initialization. Possibly replace by your own initializer. -### 5 License +### 6 License DSO was developed at the Technical University of Munich and Intel. The open-source version is licensed under the GNU General Public License Version 3 (GPLv3). diff --git a/builddso.bash b/builddso.bash new file mode 100644 index 000000000..4d125d0d8 --- /dev/null +++ b/builddso.bash @@ -0,0 +1,20 @@ +sudo apt-get install -y libsuitesparse-dev libeigen3-dev libboost-all-dev libopencv-dev libglew-dev libegl1-mesa-dev cmake zlib1g-dev curl lsb-release +user=ildar +cd /home/$user +mkdir app +cd app +git clone --recursive https://github.com/stevenlovegrove/Pangolin.git -b v0.6 +cd Pangolin +mkdir build +cd build +cmake .. +make + +cd /home/$user/app +git clone https://github.com/IldarGreat/dso.git +cd dso +git submodule update --init +mkdir build +cd build +cmake .. +make diff --git a/src/IOWrapper/OpenCV/ImageRW_OpenCV.cpp b/src/IOWrapper/OpenCV/ImageRW_OpenCV.cpp index 170cb33ce..685eb7288 100644 --- a/src/IOWrapper/OpenCV/ImageRW_OpenCV.cpp +++ b/src/IOWrapper/OpenCV/ImageRW_OpenCV.cpp @@ -34,7 +34,7 @@ namespace IOWrap { MinimalImageB* readImageBW_8U(std::string filename) { - cv::Mat m = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE); + cv::Mat m = cv::imread(filename, cv::IMREAD_GRAYSCALE); if(m.rows*m.cols==0) { printf("cv::imread could not read image %s! this may segfault. \n", filename.c_str()); @@ -52,7 +52,7 @@ MinimalImageB* readImageBW_8U(std::string filename) MinimalImageB3* readImageRGB_8U(std::string filename) { - cv::Mat m = cv::imread(filename, CV_LOAD_IMAGE_COLOR); + cv::Mat m = cv::imread(filename, cv::IMREAD_COLOR); if(m.rows*m.cols==0) { printf("cv::imread could not read image %s! this may segfault. \n", filename.c_str()); @@ -70,7 +70,7 @@ MinimalImageB3* readImageRGB_8U(std::string filename) MinimalImage* readImageBW_16U(std::string filename) { - cv::Mat m = cv::imread(filename, CV_LOAD_IMAGE_UNCHANGED); + cv::Mat m = cv::imread(filename, cv::IMREAD_UNCHANGED); if(m.rows*m.cols==0) { printf("cv::imread could not read image %s! this may segfault. \n", filename.c_str()); @@ -88,7 +88,7 @@ MinimalImage* readImageBW_16U(std::string filename) MinimalImageB* readStreamBW_8U(char* data, int numBytes) { - cv::Mat m = cv::imdecode(cv::Mat(numBytes,1,CV_8U, data), CV_LOAD_IMAGE_GRAYSCALE); + cv::Mat m = cv::imdecode(cv::Mat(numBytes,1,CV_8U, data), cv::IMREAD_GRAYSCALE); if(m.rows*m.cols==0) { printf("cv::imdecode could not read stream (%d bytes)! this may segfault. \n", numBytes); diff --git a/utils/calibration.py b/utils/calibration.py new file mode 100644 index 000000000..09805f4b7 --- /dev/null +++ b/utils/calibration.py @@ -0,0 +1,63 @@ +import cv2 +import numpy as np +import os +import glob +import argparse + +ap = argparse.ArgumentParser() +ap.add_argument("-i", "--images", required=True, help="path to input images") +args = vars(ap.parse_args()) + +# Определение размеров шахматной доски +CHECKERBOARD = (6,9) +criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) +# Создание вектора для хранения векторов трехмерных точек для каждого изображения шахматной доски +objpoints = [] +# Создание вектора для хранения векторов 2D точек для каждого изображения шахматной доски +imgpoints = [] +# Определение мировых координат для 3D точек +objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) +objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) +prev_img_shape = None +# Извлечение пути отдельного изображения, хранящегося в данном каталоге +images = glob.glob(args["images"]+'/*.jpg') +for fname in images: + img = cv2.imread(fname) + gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) + # Найти углы шахматной доски + # Если на изображении найдено нужное количество углов, тогда ret = true + ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) + + """ + Если желаемый номер угла обнаружен, + уточняем координаты пикселей и отображаем + их на изображениях шахматной доски + """ + if ret == True: + objpoints.append(objp) + # уточнение координат пикселей для заданных 2d точек. + corners2 = cv2.cornerSubPix(gray, corners, (11,11),(-1,-1), criteria) + + imgpoints.append(corners2) + # Нарисовать и отобразить углы + img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret) + + cv2.imshow('img',img) + cv2.waitKey(0) +cv2.destroyAllWindows() +h,w = img.shape[:2] +""" +Выполнение калибровки камеры с помощью +Передача значения известных трехмерных точек (объектов) +и соответствующие пиксельные координаты +обнаруженные углы (imgpoints) +""" +ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) +print("Camera matrix : \n") +print(mtx) +print("dist : \n") +print(dist) +print("rvecs : \n") +print(rvecs) +print("tvecs : \n") +print(tvecs) \ No newline at end of file diff --git a/utils/chessboard.png b/utils/chessboard.png new file mode 100644 index 000000000..630a0ba2b Binary files /dev/null and b/utils/chessboard.png differ diff --git a/utils/video_to_images.py b/utils/video_to_images.py new file mode 100644 index 000000000..fae712e49 --- /dev/null +++ b/utils/video_to_images.py @@ -0,0 +1,50 @@ +import cv2 +import numpy as np +import os +import argparse + +ap = argparse.ArgumentParser() +ap.add_argument("-v", "--video", required=True, help="path to input video") +args = vars(ap.parse_args()) + +# Playing video from file: +cap = cv2.VideoCapture(args["video"]) + +try: + if not os.path.exists('images'): + os.makedirs('images') +except OSError: + print ('Error: Creating directory of data') + +currentFrame = 0 +rightFrameName = '0000' + str(currentFrame) +while(True): + # Capture frame-by-frame + ret, frame = cap.read() + if frame is None: + break + grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + # Saves image of the current frame in jpg file + if currentFrame < 10: + rightFrameName = rightFrameName[:-1] + rightFrameName = '0000' + str(currentFrame) + if currentFrame >= 10 and currentFrame < 100 : + rightFrameName = rightFrameName[:-2] + rightFrameName = rightFrameName + str(currentFrame) + if currentFrame >= 100 and currentFrame < 1000: + rightFrameName = rightFrameName[:-3] + rightFrameName = rightFrameName + str(currentFrame) + if currentFrame >= 1000: + rightFrameName = rightFrameName[:-4] + rightFrameName = rightFrameName + str(currentFrame) + name = './images/' + rightFrameName + '.jpg' + print ('Creating...' + name,end="\r") + cv2.imwrite(name, grayFrame) + + # To stop duplicate images + currentFrame += 1 + +# When everything done, release the capture +print('\nDone!') +cap.release() +cv2.destroyAllWindows() \ No newline at end of file