'How to make OpenCV works with 3D markless object?
spec I use: Windows 10, python 3.7, opencv-contrib-python==4.5.3.56
I am trying to project a 3D object on a markless card, but the 3D object is flying everywhere when the camera opens(main.py runs) as below:
As we can see the fox is flying everywhere within the footage, that is the fox.obj should be projected while showing the card.
Here we have 4 files place in the same folder (main.py, objloader_simple.py, fox.obj, model.jpg)
main.py (click to run)
import argparse
import cv2
import numpy as np
import math
import os
from objloader_simple import *
MIN_MATCHES = 10
DEFAULT_COLOR = (0, 0, 0)
def main():
homography = None
camera_parameters = np.array([[800, 0, 320], [0, 800, 240], [0, 0, 1]])
orb = cv2.ORB_create()
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
dir_name = os.getcwd()
model = cv2.imread(os.path.join(dir_name, 'model.jpg'), 0)
kp_model, des_model = orb.detectAndCompute(model, None)
obj = OBJ(os.path.join(dir_name, 'fox.obj'), swapyz=True)
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
print("Unable to capture video")
return
kp_frame, des_frame = orb.detectAndCompute(frame, None)
matches = bf.match(des_model, des_frame)
matches = sorted(matches, key=lambda x: x.distance)
if len(matches) > MIN_MATCHES:
src_pts = np.float32([kp_model[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp_frame[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
homography, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
if args.rectangle:
h, w = model.shape
pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, homography)
frame = cv2.polylines(frame, [np.int32(dst)], True, 255, 3, cv2.LINE_AA)
if homography is not None:
try:
projection = projection_matrix(camera_parameters, homography)
frame = render(frame, obj, projection, model, False)
except:
pass
if args.matches:
frame = cv2.drawMatches(model, kp_model, frame, kp_frame, matches[:10], 0, flags=2)
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
print("Not enough matches found - %d/%d" % (len(matches), MIN_MATCHES))
cap.release()
cv2.destroyAllWindows()
return 0
def render(img, obj, projection, model, color=False):
vertices = obj.vertices
scale_matrix = np.eye(3) * 3
h, w = model.shape
for face in obj.faces:
face_vertices = face[0]
points = np.array([vertices[vertex - 1] for vertex in face_vertices])
points = np.dot(points, scale_matrix)
points = np.array([[p[0] + w / 2, p[1] + h / 2, p[2]] for p in points])
dst = cv2.perspectiveTransform(points.reshape(-1, 1, 3), projection)
imgpts = np.int32(dst)
if color is False:
cv2.fillConvexPoly(img, imgpts, DEFAULT_COLOR)
else:
color = hex_to_rgb(face[-1])
color = color[::-1] # reverse
cv2.fillConvexPoly(img, imgpts, color)
return img
def projection_matrix(camera_parameters, homography):
homography = homography * (-1)
rot_and_transl = np.dot(np.linalg.inv(camera_parameters), homography)
col_1 = rot_and_transl[:, 0]
col_2 = rot_and_transl[:, 1]
col_3 = rot_and_transl[:, 2]
l = math.sqrt(np.linalg.norm(col_1, 2) * np.linalg.norm(col_2, 2))
rot_1 = col_1 / l
rot_2 = col_2 / l
translation = col_3 / l
c = rot_1 + rot_2
p = np.cross(rot_1, rot_2)
d = np.cross(c, p)
rot_1 = np.dot(c / np.linalg.norm(c, 2) + d / np.linalg.norm(d, 2), 1 / math.sqrt(2))
rot_2 = np.dot(c / np.linalg.norm(c, 2) - d / np.linalg.norm(d, 2), 1 / math.sqrt(2))
rot_3 = np.cross(rot_1, rot_2)
projection = np.stack((rot_1, rot_2, rot_3, translation)).T
return np.dot(camera_parameters, projection)
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
h_len = len(hex_color)
return tuple(int(hex_color[i:i + h_len // 3], 16) for i in range(0, h_len, h_len // 3))
parser = argparse.ArgumentParser(description='Augmented reality application')
parser.add_argument('-r','--rectangle', help = 'draw rectangle delimiting target surface on frame', action = 'store_true')
parser.add_argument('-mk','--model_keypoints', help = 'draw model keypoints', action = 'store_true')
parser.add_argument('-fk','--frame_keypoints', help = 'draw frame keypoints', action = 'store_true')
parser.add_argument('-ma','--matches', help = 'draw matches between keypoints', action = 'store_true')
args = parser.parse_args()
if __name__ == '__main__':
main()
objloader_simple.py (place in the same folder with main.py)
class OBJ:
def __init__(self, filename, swapyz=False):
"""Loads a Wavefront OBJ file. """
self.vertices = []
self.normals = []
self.texcoords = []
self.faces = []
material = None
for line in open(filename, "r"):
if line.startswith('#'): continue
values = line.split()
if not values: continue
if values[0] == 'v':
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.vertices.append(v)
elif values[0] == 'vn':
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.normals.append(v)
elif values[0] == 'vt':
self.texcoords.append(map(float, values[1:3]))
#elif values[0] in ('usemtl', 'usemat'):
#material = values[1]
#elif values[0] == 'mtllib':
#self.mtl = MTL(values[1])
elif values[0] == 'f':
face = []
texcoords = []
norms = []
for v in values[1:]:
w = v.split('/')
face.append(int(w[0]))
if len(w) >= 2 and len(w[1]) > 0:
texcoords.append(int(w[1]))
else:
texcoords.append(0)
if len(w) >= 3 and len(w[2]) > 0:
norms.append(int(w[2]))
else:
norms.append(0)
#self.faces.append((face, norms, texcoords, material))
self.faces.append((face, norms, texcoords))
fox.obj (place in the same folder with main.py) fox.obj is over the characters limitation, so I post download link below for the fox.obj https://free3d.com/3d-model/tibetan-hill-fox-v1--444273.html
model.jpg (place in the same folder with main.py) is the card can be anything we want. Not the key point in this case. So I am not posting here.
How can I alter the code and run the main.py to make it can detect the model.jpg and show the 3D fox normally?
Solution 1:[1]
You can set the homography matrix manually such that it will be at fixed position. But there is a catch you cannot actually rotation and translation. The homography must be somehow be eliminated.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Vin |

