feat: configurations
This commit is contained in:
@@ -32,7 +32,6 @@ class Geolocation:
|
|||||||
# Вычисляем угол поворота
|
# Вычисляем угол поворота
|
||||||
rotation = -np.arctan2(mat[1, 0], mat[0, 0])
|
rotation = -np.arctan2(mat[1, 0], mat[0, 0])
|
||||||
scale = np.hypot(mat[0, 0], mat[0, 1])
|
scale = np.hypot(mat[0, 0], mat[0, 1])
|
||||||
print("Scale: ", scale)
|
|
||||||
|
|
||||||
# Координаты уже отцентрированы, поэтому используем их напрямую
|
# Координаты уже отцентрированы, поэтому используем их напрямую
|
||||||
dx_meters = tx
|
dx_meters = tx
|
||||||
|
|||||||
5
main.py
5
main.py
@@ -34,7 +34,7 @@ def main():
|
|||||||
# points = get_trajectory_points('map.jpg')
|
# points = get_trajectory_points('map.jpg')
|
||||||
|
|
||||||
# Trajectory #1
|
# Trajectory #1
|
||||||
points = [[np.float64(0.5384504359393909), np.float64(0.4084520767967683)], [np.float64(0.4451750568707629), np.float64(0.38213330305374654)], [np.float64(0.49266070439660997), np.float64(0.2789637099811013)], [np.float64(0.36377108968359656), np.float64(0.3263375027185404)], [np.float64(0.3535955937852008), np.float64(0.4337180995900692)]]
|
# points = [[np.float64(0.5384504359393909), np.float64(0.4084520767967683)], [np.float64(0.4451750568707629), np.float64(0.38213330305374654)], [np.float64(0.49266070439660997), np.float64(0.2789637099811013)], [np.float64(0.36377108968359656), np.float64(0.3263375027185404)], [np.float64(0.3535955937852008), np.float64(0.4337180995900692)]]
|
||||||
|
|
||||||
# Trajectory #2
|
# Trajectory #2
|
||||||
# points = [[np.float64(0.29197731306713737), np.float64(0.3452870198135161)], [np.float64(0.33494051797147517), np.float64(0.2010601397017569)], [np.float64(0.39768940934491587), np.float64(0.25369768718780034)], [np.float64(0.4027771572941138), np.float64(0.4158213334448144)], [np.float64(0.2914120077394487), np.float64(0.5547844588079692)]]
|
# points = [[np.float64(0.29197731306713737), np.float64(0.3452870198135161)], [np.float64(0.33494051797147517), np.float64(0.2010601397017569)], [np.float64(0.39768940934491587), np.float64(0.25369768718780034)], [np.float64(0.4027771572941138), np.float64(0.4158213334448144)], [np.float64(0.2914120077394487), np.float64(0.5547844588079692)]]
|
||||||
@@ -93,6 +93,7 @@ def main():
|
|||||||
kp, des = chunk.compute_keypoints()
|
kp, des = chunk.compute_keypoints()
|
||||||
|
|
||||||
plt.subplot(1, len(points), i+1)
|
plt.subplot(1, len(points), i+1)
|
||||||
|
plt.imshow(chunk.image)
|
||||||
kp_coords = np.array([j.pt for j in kp])
|
kp_coords = np.array([j.pt for j in kp])
|
||||||
if len(kp_coords) > 0:
|
if len(kp_coords) > 0:
|
||||||
plt.scatter(kp_coords[:, 0], kp_coords[:, 1], c='red', s=20, alpha=0.7, marker='o')
|
plt.scatter(kp_coords[:, 0], kp_coords[:, 1], c='red', s=20, alpha=0.7, marker='o')
|
||||||
@@ -187,3 +188,5 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
#TODO
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
todo.md
4
todo.md
@@ -18,3 +18,7 @@
|
|||||||
| [+] Уменьшение погрешность за счёт удаления выбросов
|
| [+] Уменьшение погрешность за счёт удаления выбросов
|
||||||
| [+] Исследовать причину погрешности при развороте
|
| [+] Исследовать причину погрешности при развороте
|
||||||
[+] Устранение большой погрешности при повороте
|
[+] Устранение большой погрешности при повороте
|
||||||
|
|
||||||
|
[ ] Переделать ключевые точки -> Optical Flow
|
||||||
|
[ ] Добавить перспективу
|
||||||
|
[ ] Эталоны на Google Maps, полёт тот же
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ from pathlib import Path
|
|||||||
from typing import Literal, Optional, Tuple
|
from typing import Literal, Optional, Tuple
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
FeatureMethod = Literal["orb", "sift", "surf", "akaze", "brisk"]
|
FeatureMethod = Literal["orb", "sift", "akaze", "brisk"]
|
||||||
|
DEFAULT_METHOD = "akaze"
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VisionChunk:
|
class VisionChunk:
|
||||||
image: Image.Image
|
image: Image.Image
|
||||||
feature_method: FeatureMethod = "orb"
|
feature_method: FeatureMethod = DEFAULT_METHOD
|
||||||
|
|
||||||
keypoints: Optional[list] = field(default=None, init=False)
|
keypoints: Optional[list] = field(default=None, init=False)
|
||||||
descriptors: Optional[np.ndarray] = field(default=None, init=False)
|
descriptors: Optional[np.ndarray] = field(default=None, init=False)
|
||||||
@@ -26,7 +27,7 @@ class VisionChunk:
|
|||||||
self._detector = cv2.ORB_create(
|
self._detector = cv2.ORB_create(
|
||||||
nfeatures=1000,
|
nfeatures=1000,
|
||||||
scaleFactor=1.2,
|
scaleFactor=1.2,
|
||||||
nlevels=8,
|
nlevels=32,
|
||||||
edgeThreshold=31,
|
edgeThreshold=31,
|
||||||
firstLevel=0,
|
firstLevel=0,
|
||||||
WTA_K=2,
|
WTA_K=2,
|
||||||
@@ -35,18 +36,11 @@ class VisionChunk:
|
|||||||
)
|
)
|
||||||
elif self.feature_method == "sift":
|
elif self.feature_method == "sift":
|
||||||
self._detector = cv2.SIFT_create(
|
self._detector = cv2.SIFT_create(
|
||||||
nfeatures=1000,
|
nfeatures=1500,
|
||||||
nOctaveLayers=3,
|
nOctaveLayers=2,
|
||||||
contrastThreshold=0.04,
|
contrastThreshold=0.01,
|
||||||
edgeThreshold=10,
|
edgeThreshold=15,
|
||||||
sigma=1.6
|
sigma=3.3
|
||||||
)
|
|
||||||
elif self.feature_method == "surf":
|
|
||||||
self._detector = cv2.xfeatures2d.SURF_create(
|
|
||||||
hessianThreshold=400.0,
|
|
||||||
nOctaveLayers=3,
|
|
||||||
# nOctaveLayers=4,
|
|
||||||
upright=False
|
|
||||||
)
|
)
|
||||||
elif self.feature_method == "akaze":
|
elif self.feature_method == "akaze":
|
||||||
self._detector = cv2.AKAZE_create(
|
self._detector = cv2.AKAZE_create(
|
||||||
@@ -59,8 +53,8 @@ class VisionChunk:
|
|||||||
)
|
)
|
||||||
elif self.feature_method == "brisk":
|
elif self.feature_method == "brisk":
|
||||||
self._detector = cv2.BRISK_create(
|
self._detector = cv2.BRISK_create(
|
||||||
thresh=30,
|
thresh=70,
|
||||||
octaves=3,
|
octaves=7,
|
||||||
patternScale=1.0
|
patternScale=1.0
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -69,15 +63,10 @@ class VisionChunk:
|
|||||||
|
|
||||||
def _get_matcher(self) -> cv2.DescriptorMatcher:
|
def _get_matcher(self) -> cv2.DescriptorMatcher:
|
||||||
if self._matcher is None:
|
if self._matcher is None:
|
||||||
self._matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
|
if self.feature_method == 'sift':
|
||||||
return self._matcher
|
self._matcher = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
|
||||||
if self.feature_method == 'orb':
|
else:
|
||||||
self._matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
|
self._matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
|
||||||
elif self.feature_method == 'sift':
|
|
||||||
FLANN_INDEX_LSH = 6
|
|
||||||
index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=6, key_size=12, multi_probe_level=1)
|
|
||||||
search_params = dict(checks=50)
|
|
||||||
self._matcher = cv2.FlannBasedMatcher(index_params, search_params)
|
|
||||||
return self._matcher
|
return self._matcher
|
||||||
|
|
||||||
def _preprocess(self, img_np: np.ndarray) -> np.ndarray:
|
def _preprocess(self, img_np: np.ndarray) -> np.ndarray:
|
||||||
@@ -90,7 +79,7 @@ class VisionChunk:
|
|||||||
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
|
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
|
||||||
return clahe.apply(gray)
|
return clahe.apply(gray)
|
||||||
|
|
||||||
def compute_keypoints(self, force: bool = False) -> Tuple[list, Optional[np.ndarray]]:
|
def compute_keypoints(self, force: bool = False) -> Tuple[list[cv2.KeyPoint], Optional[np.ndarray]]:
|
||||||
if self.keypoints is not None and self.descriptors is not None and not force:
|
if self.keypoints is not None and self.descriptors is not None and not force:
|
||||||
return self.keypoints, self.descriptors
|
return self.keypoints, self.descriptors
|
||||||
|
|
||||||
@@ -103,11 +92,21 @@ class VisionChunk:
|
|||||||
|
|
||||||
# CLAHE предобработка
|
# CLAHE предобработка
|
||||||
preprocessed = self._preprocess(img_np)
|
preprocessed = self._preprocess(img_np)
|
||||||
kps, desc = detector.detectAndCompute(preprocessed, None)
|
keypoints, descriptors = detector.detectAndCompute(preprocessed, None)
|
||||||
|
|
||||||
self.keypoints = kps
|
# Получаем массив response для всех точек
|
||||||
self.descriptors = desc
|
responses = np.array([kp.response for kp in keypoints])
|
||||||
return kps, desc
|
|
||||||
|
# Находим индексы топ-100
|
||||||
|
top_indices = np.argsort(responses)[-2500:][::-1]
|
||||||
|
|
||||||
|
# Отбираем keypoints и descriptors
|
||||||
|
best_keypoints = [keypoints[i] for i in top_indices]
|
||||||
|
best_descriptors = descriptors[top_indices]
|
||||||
|
|
||||||
|
self.keypoints = best_keypoints
|
||||||
|
self.descriptors = best_descriptors
|
||||||
|
return self.keypoints, self.descriptors
|
||||||
|
|
||||||
def detect_and_match_keypoints(
|
def detect_and_match_keypoints(
|
||||||
self,
|
self,
|
||||||
@@ -193,7 +192,7 @@ class VisionChunk:
|
|||||||
return np.array(self.image)
|
return np.array(self.image)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_image(cls, path: Path | str, feature_method: FeatureMethod = "orb") -> "VisionChunk":
|
def load_image(cls, path: Path | str, feature_method: FeatureMethod = DEFAULT_METHOD) -> "VisionChunk":
|
||||||
path = Path(path)
|
path = Path(path)
|
||||||
image = Image.open(path)
|
image = Image.open(path)
|
||||||
return cls(image=image, feature_method=feature_method)
|
return cls(image=image, feature_method=feature_method)
|
||||||
|
|||||||
Reference in New Issue
Block a user