kopia lustrzana https://github.com/OpenDroneMap/ODM
Merge pull request #1493 from pierotofy/auto
Faster band alignment, minor fixes, thermal improvementspull/1496/head
commit
cc7fb2efa5
|
@ -25,13 +25,11 @@ def dn_to_radiance(photo, image):
|
||||||
image = image.astype("float32")
|
image = image.astype("float32")
|
||||||
if len(image.shape) != 3:
|
if len(image.shape) != 3:
|
||||||
raise ValueError("Image should have shape length of 3 (got: %s)" % len(image.shape))
|
raise ValueError("Image should have shape length of 3 (got: %s)" % len(image.shape))
|
||||||
|
|
||||||
# Handle thermal bands (experimental)
|
|
||||||
if photo.band_name == 'LWIR':
|
|
||||||
image -= (273.15 * 100.0) # Convert Kelvin to Celsius
|
|
||||||
image *= 0.01
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
# Thermal (this should never happen, but just in case..)
|
||||||
|
if photo.is_thermal():
|
||||||
|
return image
|
||||||
|
|
||||||
# All others
|
# All others
|
||||||
a1, a2, a3 = photo.get_radiometric_calibration()
|
a1, a2, a3 = photo.get_radiometric_calibration()
|
||||||
dark_level = photo.get_dark_level()
|
dark_level = photo.get_dark_level()
|
||||||
|
@ -381,12 +379,24 @@ def compute_homography(image_filename, align_image_filename):
|
||||||
|
|
||||||
return h, (align_image_gray.shape[1], align_image_gray.shape[0])
|
return h, (align_image_gray.shape[1], align_image_gray.shape[0])
|
||||||
|
|
||||||
algo = 'feat'
|
warp_matrix = None
|
||||||
result = compute_using(find_features_homography)
|
dimension = None
|
||||||
|
algo = None
|
||||||
|
|
||||||
if result[0] is None:
|
if max_dim > 320:
|
||||||
|
algo = 'feat'
|
||||||
|
result = compute_using(find_features_homography)
|
||||||
|
|
||||||
|
if result[0] is None:
|
||||||
|
algo = 'ecc'
|
||||||
|
log.ODM_INFO("Can't use features matching, will use ECC (this might take a bit)")
|
||||||
|
result = compute_using(find_ecc_homography)
|
||||||
|
if result[0] is None:
|
||||||
|
algo = None
|
||||||
|
|
||||||
|
else: # ECC only for low resolution images
|
||||||
algo = 'ecc'
|
algo = 'ecc'
|
||||||
log.ODM_INFO("Can't use features matching, will use ECC (this might take a bit)")
|
log.ODM_INFO("Using ECC (this might take a bit)")
|
||||||
result = compute_using(find_ecc_homography)
|
result = compute_using(find_ecc_homography)
|
||||||
if result[0] is None:
|
if result[0] is None:
|
||||||
algo = None
|
algo = None
|
||||||
|
@ -396,7 +406,7 @@ def compute_homography(image_filename, align_image_filename):
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.ODM_WARNING("Compute homography: %s" % str(e))
|
log.ODM_WARNING("Compute homography: %s" % str(e))
|
||||||
return None, None, (None, None)
|
return None, (None, None), None
|
||||||
|
|
||||||
def find_ecc_homography(image_gray, align_image_gray, number_of_iterations=1000, termination_eps=1e-8, start_eps=1e-4):
|
def find_ecc_homography(image_gray, align_image_gray, number_of_iterations=1000, termination_eps=1e-8, start_eps=1e-4):
|
||||||
pyramid_levels = 0
|
pyramid_levels = 0
|
||||||
|
@ -413,10 +423,14 @@ def find_ecc_homography(image_gray, align_image_gray, number_of_iterations=1000,
|
||||||
if align_image_gray.shape[0] != image_gray.shape[0]:
|
if align_image_gray.shape[0] != image_gray.shape[0]:
|
||||||
align_image_gray = to_8bit(align_image_gray)
|
align_image_gray = to_8bit(align_image_gray)
|
||||||
image_gray = to_8bit(image_gray)
|
image_gray = to_8bit(image_gray)
|
||||||
|
|
||||||
|
fx = align_image_gray.shape[1]/image_gray.shape[1]
|
||||||
|
fy = align_image_gray.shape[0]/image_gray.shape[0]
|
||||||
|
|
||||||
image_gray = cv2.resize(image_gray, None,
|
image_gray = cv2.resize(image_gray, None,
|
||||||
fx=align_image_gray.shape[1]/image_gray.shape[1],
|
fx=fx,
|
||||||
fy=align_image_gray.shape[0]/image_gray.shape[0],
|
fy=fy,
|
||||||
interpolation=cv2.INTER_AREA)
|
interpolation=(cv2.INTER_AREA if (fx < 1.0 and fy < 1.0) else cv2.INTER_LANCZOS4))
|
||||||
|
|
||||||
# Build pyramids
|
# Build pyramids
|
||||||
image_gray_pyr = [image_gray]
|
image_gray_pyr = [image_gray]
|
||||||
|
@ -430,8 +444,9 @@ def find_ecc_homography(image_gray, align_image_gray, number_of_iterations=1000,
|
||||||
align_image_pyr.insert(0, cv2.resize(align_image_pyr[0], None, fx=1/2, fy=1/2,
|
align_image_pyr.insert(0, cv2.resize(align_image_pyr[0], None, fx=1/2, fy=1/2,
|
||||||
interpolation=cv2.INTER_AREA))
|
interpolation=cv2.INTER_AREA))
|
||||||
|
|
||||||
# Define the motion model
|
# Define the motion model, scale the initial warp matrix to smallest level
|
||||||
warp_matrix = np.eye(3, 3, dtype=np.float32)
|
warp_matrix = np.eye(3, 3, dtype=np.float32)
|
||||||
|
warp_matrix = warp_matrix * np.array([[1,1,2],[1,1,2],[0.5,0.5,1]], dtype=np.float32)**(1-(pyramid_levels+1))
|
||||||
|
|
||||||
for level in range(pyramid_levels+1):
|
for level in range(pyramid_levels+1):
|
||||||
ig = gradient(gaussian(image_gray_pyr[level]))
|
ig = gradient(gaussian(image_gray_pyr[level]))
|
||||||
|
@ -453,6 +468,7 @@ def find_ecc_homography(image_gray, align_image_gray, number_of_iterations=1000,
|
||||||
if level != pyramid_levels:
|
if level != pyramid_levels:
|
||||||
log.ODM_INFO("Could not compute ECC warp_matrix at pyramid level %s, resetting matrix" % level)
|
log.ODM_INFO("Could not compute ECC warp_matrix at pyramid level %s, resetting matrix" % level)
|
||||||
warp_matrix = np.eye(3, 3, dtype=np.float32)
|
warp_matrix = np.eye(3, 3, dtype=np.float32)
|
||||||
|
warp_matrix = warp_matrix * np.array([[1,1,2],[1,1,2],[0.5,0.5,1]], dtype=np.float32)**(1-(pyramid_levels+1))
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
@ -462,29 +478,33 @@ def find_ecc_homography(image_gray, align_image_gray, number_of_iterations=1000,
|
||||||
return warp_matrix
|
return warp_matrix
|
||||||
|
|
||||||
|
|
||||||
def find_features_homography(image_gray, align_image_gray, feature_retention=0.25):
|
def find_features_homography(image_gray, align_image_gray, feature_retention=0.7, min_match_count=10):
|
||||||
|
|
||||||
# Detect SIFT features and compute descriptors.
|
# Detect SIFT features and compute descriptors.
|
||||||
detector = cv2.SIFT_create(edgeThreshold=10, contrastThreshold=0.1)
|
detector = cv2.SIFT_create(edgeThreshold=10, contrastThreshold=0.1)
|
||||||
kp_image, desc_image = detector.detectAndCompute(image_gray, None)
|
kp_image, desc_image = detector.detectAndCompute(image_gray, None)
|
||||||
kp_align_image, desc_align_image = detector.detectAndCompute(align_image_gray, None)
|
kp_align_image, desc_align_image = detector.detectAndCompute(align_image_gray, None)
|
||||||
|
|
||||||
# Match
|
# Match
|
||||||
bf = cv2.BFMatcher(cv2.NORM_L1,crossCheck=True)
|
FLANN_INDEX_KDTREE = 1
|
||||||
|
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
|
||||||
|
search_params = dict(checks=50)
|
||||||
|
|
||||||
|
flann = cv2.FlannBasedMatcher(index_params, search_params)
|
||||||
try:
|
try:
|
||||||
matches = bf.match(desc_image, desc_align_image)
|
matches = flann.knnMatch(desc_image, desc_align_image, k=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.ODM_INFO("Cannot match features")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Sort by score
|
# Filter good matches following Lowe's ratio test
|
||||||
matches.sort(key=lambda x: x.distance, reverse=False)
|
good_matches = []
|
||||||
|
for m, n in matches:
|
||||||
|
if m.distance < feature_retention * n.distance:
|
||||||
|
good_matches.append(m)
|
||||||
|
|
||||||
# Remove bad matches
|
matches = good_matches
|
||||||
num_good_matches = int(len(matches) * feature_retention)
|
|
||||||
matches = matches[:num_good_matches]
|
|
||||||
|
|
||||||
if len(matches) < 4:
|
if len(matches) < min_match_count:
|
||||||
log.ODM_INFO("Insufficient features: %s" % len(matches))
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
|
|
|
@ -44,9 +44,22 @@ def generate_png(orthophoto_file, output_file=None, outsize=None):
|
||||||
|
|
||||||
# See if we need to select top three bands
|
# See if we need to select top three bands
|
||||||
bandparam = ""
|
bandparam = ""
|
||||||
|
|
||||||
gtif = gdal.Open(orthophoto_file)
|
gtif = gdal.Open(orthophoto_file)
|
||||||
if gtif.RasterCount > 4:
|
if gtif.RasterCount > 4:
|
||||||
bandparam = "-b 1 -b 2 -b 3 -a_nodata 0"
|
bands = []
|
||||||
|
for idx in range(1, gtif.RasterCount+1):
|
||||||
|
bands.append(gtif.GetRasterBand(idx).GetColorInterpretation())
|
||||||
|
bands = dict(zip(bands, range(1, len(bands)+1)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
red = bands.get(gdal.GCI_RedBand)
|
||||||
|
green = bands.get(gdal.GCI_GreenBand)
|
||||||
|
blue = bands.get(gdal.GCI_BlueBand)
|
||||||
|
bandparam = "-b %s -b %s -b %s -a_nodata 0" % (red, green, blue)
|
||||||
|
except:
|
||||||
|
bandparam = "-b 1 -b 2 -b 3 -a_nodata 0"
|
||||||
|
gtif = None
|
||||||
|
|
||||||
osparam = ""
|
osparam = ""
|
||||||
if outsize is not None:
|
if outsize is not None:
|
||||||
|
|
|
@ -339,6 +339,7 @@ class ODM_Photo:
|
||||||
|
|
||||||
self.set_attr_from_xmp_tag('capture_uuid', xtags, [
|
self.set_attr_from_xmp_tag('capture_uuid', xtags, [
|
||||||
'@drone-dji:CaptureUUID', # DJI
|
'@drone-dji:CaptureUUID', # DJI
|
||||||
|
'MicaSense:CaptureId', # MicaSense Altum
|
||||||
'@Camera:ImageUniqueID', # sentera 6x
|
'@Camera:ImageUniqueID', # sentera 6x
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue