add calibration code
@ -6,7 +6,7 @@ import os
|
|||||||
class FileStorage(object):
|
class FileStorage(object):
|
||||||
def __init__(self, filename, isWrite=False):
|
def __init__(self, filename, isWrite=False):
|
||||||
version = cv2.__version__
|
version = cv2.__version__
|
||||||
self.version = int(version.split('.')[0])
|
self.version = '.'.join(version.split('.')[:2])
|
||||||
if isWrite:
|
if isWrite:
|
||||||
self.fs = cv2.FileStorage(filename, cv2.FILE_STORAGE_WRITE)
|
self.fs = cv2.FileStorage(filename, cv2.FILE_STORAGE_WRITE)
|
||||||
else:
|
else:
|
||||||
@ -19,7 +19,8 @@ class FileStorage(object):
|
|||||||
if dt == 'mat':
|
if dt == 'mat':
|
||||||
cv2.FileStorage.write(self.fs, key, value)
|
cv2.FileStorage.write(self.fs, key, value)
|
||||||
elif dt == 'list':
|
elif dt == 'list':
|
||||||
if self.version == 4: # 4.4
|
# import ipdb;ipdb.set_trace()
|
||||||
|
if self.version == '4.4': # 4.4
|
||||||
# self.fs.write(key, '[')
|
# self.fs.write(key, '[')
|
||||||
# for elem in value:
|
# for elem in value:
|
||||||
# self.fs.write('none', elem)
|
# self.fs.write('none', elem)
|
||||||
@ -67,39 +68,98 @@ def _FindChessboardCorners(img, patternSize = (9, 6)):
|
|||||||
corners = cv2.cornerSubPix(img, corners, (11, 11), (-1, -1), criteria)
|
corners = cv2.cornerSubPix(img, corners, (11, 11), (-1, -1), criteria)
|
||||||
return True, corners
|
return True, corners
|
||||||
|
|
||||||
def FindChessboardCorners(image_names, patternSize=(9, 6), gridSize=0.1, debug=False):
|
def FindChessboardCorners(image_names, patternSize=(9, 6), gridSize=0.1, debug=False, remove=False, resize_rate = 1):
|
||||||
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
|
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
|
||||||
object_points = np.zeros((patternSize[1]*patternSize[0], 3), np.float32)
|
object_points = np.zeros((patternSize[1]*patternSize[0], 3), np.float32)
|
||||||
object_points[:,:2] = np.mgrid[0:patternSize[0], 0:patternSize[1]].T.reshape(-1,2)
|
object_points[:,:2] = np.mgrid[0:patternSize[0], 0:patternSize[1]].T.reshape(-1,2)
|
||||||
object_points = object_points * gridSize
|
object_points = object_points * gridSize
|
||||||
# Arrays to store object points and image points from all the images.
|
# Arrays to store object points and image points from all the images.
|
||||||
objpoints = [] # 3d point in real world space
|
infos = []
|
||||||
imgpoints = [] # 2d points in image plane.
|
for fname in tqdm(image_names, desc='detecting chessboard'):
|
||||||
images = []
|
assert os.path.exists(fname), fname
|
||||||
for fname in tqdm(image_names):
|
tmpname = fname+'.corners.txt'
|
||||||
img = cv2.imread(fname)
|
img = cv2.imread(fname)
|
||||||
|
img = cv2.resize(img, None, fx=resize_rate, fy=resize_rate)
|
||||||
|
if debug:
|
||||||
|
if img.shape[0] > 1000:
|
||||||
|
show = cv2.resize(img, None, fx=0.5, fy=0.5)
|
||||||
|
else:
|
||||||
|
show = img
|
||||||
|
cv2.imshow('img', show)
|
||||||
|
cv2.waitKey(10)
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists(tmpname) and not debug:
|
||||||
|
ret = True
|
||||||
|
tmp = np.loadtxt(tmpname)
|
||||||
|
if len(tmp.shape) < 2:
|
||||||
|
ret = False
|
||||||
|
else:
|
||||||
|
corners = tmp.reshape((-1, 1, 2))
|
||||||
|
corners = np.ascontiguousarray(corners.astype(np.float32))
|
||||||
|
else:
|
||||||
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
|
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
|
||||||
# Find the chess board corners
|
# Find the chess board corners
|
||||||
ret, corners = _FindChessboardCorners(gray, patternSize)
|
ret, corners = _FindChessboardCorners(gray, patternSize)
|
||||||
|
if not ret:
|
||||||
|
ret, corners = _FindChessboardCorners(gray, patternSize, False)
|
||||||
|
print('Not found in adaptive mode, but retry = {}'.format(ret))
|
||||||
# If found, add object points, image points (after refining them)
|
# If found, add object points, image points (after refining them)
|
||||||
if ret:
|
if ret:
|
||||||
objpoints.append(object_points)
|
np.savetxt(tmpname, corners[:, 0])
|
||||||
imgpoints.append(corners)
|
|
||||||
# Draw and display the corners
|
|
||||||
images.append(img)
|
|
||||||
if debug:
|
|
||||||
img = cv2.drawChessboardCorners(img, patternSize, corners, ret)
|
|
||||||
cv2.imshow('img',img)
|
|
||||||
cv2.waitKey(10)
|
|
||||||
else:
|
else:
|
||||||
|
np.savetxt(tmpname, np.empty((0, 2), dtype=np.float32))
|
||||||
|
if ret:
|
||||||
|
infos.append({
|
||||||
|
'point_object': object_points,
|
||||||
|
'point_image': corners,
|
||||||
|
'image': img,
|
||||||
|
'image_name': fname
|
||||||
|
})
|
||||||
|
if debug:
|
||||||
|
show = img.copy()
|
||||||
|
show = cv2.drawChessboardCorners(show, patternSize, corners, ret)
|
||||||
|
if show.shape[0] > 1000:
|
||||||
|
show = cv2.resize(show, None, fx=0.5, fy=0.5)
|
||||||
|
cv2.imshow('img', show)
|
||||||
|
cv2.waitKey(10)
|
||||||
|
elif remove:
|
||||||
os.remove(fname)
|
os.remove(fname)
|
||||||
return imgpoints, objpoints, images
|
os.remove(tmpname)
|
||||||
|
return infos
|
||||||
|
|
||||||
|
|
||||||
def safe_mkdir(path):
|
def safe_mkdir(path):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|
||||||
|
def read_intri(intri_name):
|
||||||
|
assert os.path.exists(intri_name), intri_name
|
||||||
|
intri = FileStorage(intri_name)
|
||||||
|
camnames = intri.read('names', dt='list')
|
||||||
|
from collections import OrderedDict
|
||||||
|
cameras = OrderedDict()
|
||||||
|
for key in camnames:
|
||||||
|
cam = {}
|
||||||
|
cam['K'] = intri.read('K_{}'.format(key))
|
||||||
|
cam['invK'] = np.linalg.inv(cam['K'])
|
||||||
|
cam['dist'] = intri.read('dist_{}'.format(key))
|
||||||
|
cameras[key] = cam
|
||||||
|
return cameras
|
||||||
|
|
||||||
|
def write_extri(camera, path, base='extri.yml'):
|
||||||
|
extri_name = os.path.join(path, base)
|
||||||
|
extri = FileStorage(extri_name, True)
|
||||||
|
results = {}
|
||||||
|
camnames = [key_.split('.')[0] for key_ in camera.keys()]
|
||||||
|
extri.write('names', camnames, 'list')
|
||||||
|
for key_, val in camera.items():
|
||||||
|
key = key_.split('.')[0]
|
||||||
|
extri.write('R_{}'.format(key), val['Rvec'])
|
||||||
|
extri.write('Rot_{}'.format(key), val['R'])
|
||||||
|
extri.write('T_{}'.format(key), val['T'])
|
||||||
|
return 0
|
||||||
|
|
||||||
def read_camera(intri_name, extri_name, cam_names=[0,1,2,3]):
|
def read_camera(intri_name, extri_name, cam_names=[0,1,2,3]):
|
||||||
assert os.path.exists(intri_name), intri_name
|
assert os.path.exists(intri_name), intri_name
|
||||||
assert os.path.exists(extri_name), extri_name
|
assert os.path.exists(extri_name), extri_name
|
||||||
|
BIN
data/examples/calibration/extri_images/1.jpg
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
data/examples/calibration/extri_images/2.jpg
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
data/examples/calibration/extri_images/3.jpg
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
data/examples/calibration/extri_images/4.jpg
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
data/examples/calibration/extri_images/5.jpg
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
data/examples/calibration/extri_images/6.jpg
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
data/examples/calibration/extri_images/7.jpg
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
data/examples/calibration/extri_images/8.jpg
Normal file
After Width: | Height: | Size: 108 KiB |
99
data/examples/calibration/intri.yml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
%YAML:1.0
|
||||||
|
---
|
||||||
|
names:
|
||||||
|
- "1"
|
||||||
|
- "2"
|
||||||
|
- "3"
|
||||||
|
- "4"
|
||||||
|
- "5"
|
||||||
|
- "6"
|
||||||
|
- "7"
|
||||||
|
- "8"
|
||||||
|
K_1: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.0927224982003611e+02, 0., 9.6062674039097942e+02, 0.,
|
||||||
|
9.2444288845626772e+02, 5.4293008226872905e+02, 0., 0., 1. ]
|
||||||
|
dist_1: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_2: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.0927224982003611e+02, 0., 9.6062674039097942e+02, 0.,
|
||||||
|
9.2444288845626772e+02, 5.4293008226872905e+02, 0., 0., 1. ]
|
||||||
|
dist_2: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_3: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.0927224982003611e+02, 0., 9.6062674039097942e+02, 0.,
|
||||||
|
9.2444288845626772e+02, 5.4293008226872905e+02, 0., 0., 1. ]
|
||||||
|
dist_3: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_4: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.1665997341043840e+02, 0., 9.5547887626827935e+02, 0.,
|
||||||
|
9.8855809176036394e+02, 5.5038850358905097e+02, 0., 0., 1. ]
|
||||||
|
dist_4: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_5: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.0156851427245795e+02, 0., 9.6223657355953253e+02, 0.,
|
||||||
|
8.8178917785495423e+02, 5.6788041632010697e+02, 0., 0., 1. ]
|
||||||
|
dist_5: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_6: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.0088200265010209e+02, 0., 9.5815011666263433e+02, 0.,
|
||||||
|
9.3840029880200143e+02, 5.5017825887724632e+02, 0., 0., 1. ]
|
||||||
|
dist_6: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_7: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 8.9861671187902732e+02, 0., 9.7219362635132836e+02, 0.,
|
||||||
|
9.0366361957942956e+02, 5.4668133454126132e+02, 0., 0., 1. ]
|
||||||
|
dist_7: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
||||||
|
K_8: !!opencv-matrix
|
||||||
|
rows: 3
|
||||||
|
cols: 3
|
||||||
|
dt: d
|
||||||
|
data: [ 9.0204703596907223e+02, 0., 9.7263731333736860e+02, 0.,
|
||||||
|
8.7830208920235123e+02, 5.4022734518354935e+02, 0., 0., 1. ]
|
||||||
|
dist_8: !!opencv-matrix
|
||||||
|
rows: 1
|
||||||
|
cols: 5
|
||||||
|
dt: d
|
||||||
|
data: [ 0., 0., 0., 0., 0. ]
|
39
scripts/calibration/Readme.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
* @Date: 2021-03-02 16:14:48
|
||||||
|
* @Author: Qing Shuai
|
||||||
|
* @LastEditors: Qing Shuai
|
||||||
|
* @LastEditTime: 2021-03-02 17:09:02
|
||||||
|
* @FilePath: /EasyMocap/scripts/calibration/Readme.md
|
||||||
|
-->
|
||||||
|
# Camera Calibration
|
||||||
|
|
||||||
|
## 0. Prepare your chessboard
|
||||||
|
|
||||||
|
## 1. Distortion and Intrinsic Parameter Calibration
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## 2. Extrinsic Parameter Calibration
|
||||||
|
Prepare your images as following:
|
||||||
|
```bash
|
||||||
|
./data/examples/calibration
|
||||||
|
├── extri_images
|
||||||
|
│ ├── 1.jpg
|
||||||
|
│ ├── 2.jpg
|
||||||
|
│ ├── 3.jpg
|
||||||
|
│ ├── 4.jpg
|
||||||
|
│ ├── 5.jpg
|
||||||
|
│ ├── 6.jpg
|
||||||
|
│ ├── 7.jpg
|
||||||
|
│ └── 8.jpg
|
||||||
|
└── intri.yml
|
||||||
|
```
|
||||||
|
The basename of the images must be same as the name of cameras in `intri.yml`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/calibration/calib_extri.py -i ./data/examples/calibration/extri_images -o ./data/examples/calibration --debug
|
||||||
|
```
|
||||||
|
To specify your chessboard, add the option `--pattern`, `--grid`
|
||||||
|
```bash
|
||||||
|
python3 scripts/calibration/calib_extri.py -i ./data/examples/calibration/extri_images -o ./data/examples/calibration --debug --pattern 9,6 --grid 0.1
|
||||||
|
```
|
||||||
|
More details can be found in the code.
|
57
scripts/calibration/calib_extri.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
'''
|
||||||
|
@ Date: 2021-03-02 16:13:03
|
||||||
|
@ Author: Qing Shuai
|
||||||
|
@ LastEditors: Qing Shuai
|
||||||
|
@ LastEditTime: 2021-03-02 17:06:41
|
||||||
|
@ FilePath: /EasyMocap/scripts/calibration/calib_extri.py
|
||||||
|
'''
|
||||||
|
import os
|
||||||
|
from glob import glob
|
||||||
|
from os.path import join
|
||||||
|
import cv2
|
||||||
|
import sys
|
||||||
|
code_path = join(os.path.dirname(__file__), '..', '..', 'code')
|
||||||
|
sys.path.append(code_path)
|
||||||
|
from mytools.camera_utils import read_intri, write_extri, FindChessboardCorners
|
||||||
|
|
||||||
|
def calib_extri_pipeline(path, out, resize_rate, debug, args):
|
||||||
|
assert os.path.exists(path), path
|
||||||
|
intri = read_intri(join(out, 'intri.yml'))
|
||||||
|
cameras = [i.split('.')[0] for i in sorted(os.listdir(path))]
|
||||||
|
if cameras[0].isdigit():
|
||||||
|
cameras.sort(key=lambda x:int(x))
|
||||||
|
total = 0
|
||||||
|
extri = {}
|
||||||
|
for cam in cameras:
|
||||||
|
image_names = glob(join(path, '{}.jpg'.format(cam)))
|
||||||
|
assert len(image_names) >= 1, '{}/{} has no images'.format(path, cameras)
|
||||||
|
infos = FindChessboardCorners([image_names[0]],
|
||||||
|
patternSize=args.pattern, gridSize=args.grid,
|
||||||
|
debug=debug, remove=False, resize_rate=resize_rate)
|
||||||
|
if len(infos) < 1:
|
||||||
|
continue
|
||||||
|
info = infos[0]
|
||||||
|
ret, rvecs, tvecs = cv2.solvePnP(info['point_object'], info['point_image'], intri[cam]['K'], intri[cam]['dist'])
|
||||||
|
extri[cam] = {}
|
||||||
|
extri[cam]['Rvec'] = rvecs
|
||||||
|
extri[cam]['R'] = cv2.Rodrigues(rvecs)[0]
|
||||||
|
extri[cam]['T'] = tvecs
|
||||||
|
extri[cam]['center'] = -extri[cam]['R'].T @ tvecs
|
||||||
|
write_extri(extri, out, 'extri.yml')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-i', '--path', type=str,dest='path',
|
||||||
|
help='the directory contains the extrinsic images')
|
||||||
|
parser.add_argument('-o', '--out', type=str,
|
||||||
|
help='output path')
|
||||||
|
parser.add_argument('--pattern', type=lambda x: (int(x.split(',')[0]), int(x.split(',')[1])),
|
||||||
|
help='The pattern of the chessboard', default=(9, 6))
|
||||||
|
parser.add_argument('--grid', type=float, default=0.1,
|
||||||
|
help='The length of the grid size (unit: meter)')
|
||||||
|
parser.add_argument('--rate', type=float, default=1,
|
||||||
|
help='scale the original image')
|
||||||
|
parser.add_argument('--debug', action='store_true')
|
||||||
|
args = parser.parse_args()
|
||||||
|
calib_extri_pipeline(args.path, args.out, args.rate, args.debug, args)
|
7
scripts/calibration/calib_intri.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'''
|
||||||
|
@ Date: 2021-03-02 16:12:59
|
||||||
|
@ Author: Qing Shuai
|
||||||
|
@ LastEditors: Qing Shuai
|
||||||
|
@ LastEditTime: 2021-03-02 16:12:59
|
||||||
|
@ FilePath: /EasyMocap/scripts/calibration/calib_intri.py
|
||||||
|
'''
|