Вычисление соотношения сторон изображения перспективы

Недавно я реализовал Perspective Transform в OpenCV для своего приложения на Android . Почти все работает без проблем, но один аспект требует гораздо больше работы.

Проблема в том, что я не знаю, как считать правильное соотношение сторон целевого изображения Perspective Transform (его не нужно устанавливать вручную), чтобы он мог рассчитывать соотношение сторон изображения к размеру реального Вещь / изображение, несмотря на угол камеры . Обратите внимание, что начальные координаты не образуют трапецию, она образует четырехугольник.

Если у меня есть фотография книги, взятой примерно с 45 градусов, и я хочу, чтобы соотношение сторон изображения изображения было примерно таким же, как и соотношение сторон этой книги. Трудно сделать 2D-фотографию, но приложение CamScanner делает это отлично. Я сделал очень простой способ подсчитать размер моего целевого изображения (без каких-либо ожиданий, чтобы он работал так, как я хочу), но он делает изображение с углом 45 градусов примерно на 20% короче, а при уменьшении угла высота изображения уменьшается Значительно, в то время как CamScanner делает это отлично, несмотря на угол:

Введите описание изображения здесь

Здесь CamScanner поддерживает соотношение сторон целевого изображения (второго), то же самое, что и в книге, оно было довольно точно даже при ~ 20 градусах.

Между тем, мой код выглядит так (при подсчете размеров целевого изображения у меня нет намерения работать, как я задаю в этом вопросе):

public static Mat PerspectiveTransform(Point[] cropCoordinates, float ratioW, float ratioH, Bitmap croppedImage) { if (cropCoordinates.length != 4) return null; double width1, width2, height1, height2, avgw, avgh; Mat src = new Mat(); List<Point> startCoords = new ArrayList<>(); List<Point> resultCoords = new ArrayList<>(); Utils.bitmapToMat(croppedImage, src); for (int i = 0; i < 4; i++) { if (cropCoordinates[i].y < 0 ) new Point(cropCoordinates[i].x, 0); startCoords.add(new Point(cropCoordinates[i].x * ratioW, cropCoordinates[i].y * ratioH)); } width1 = Math.sqrt(Math.pow(startCoords.get(2).x - startCoords.get(3).x,2) + Math.pow(startCoords.get(2).y - startCoords.get(3).y,2)); width2 = Math.sqrt(Math.pow(startCoords.get(1).x - startCoords.get(0).x,2) + Math.pow(startCoords.get(1).y - startCoords.get(0).y,2)); height1 = Math.sqrt(Math.pow(startCoords.get(1).x - startCoords.get(2).x, 2) + Math.pow(startCoords.get(1).y - startCoords.get(2).y, 2)); height2 = Math.sqrt(Math.pow(startCoords.get(0).x - startCoords.get(3).x, 2) + Math.pow(startCoords.get(0).y - startCoords.get(3).y, 2)); avgw = (width1 + width2) / 2; avgh = (height1 + height2) / 2; resultCoords.add(new Point(0, 0)); resultCoords.add(new Point(avgw-1, 0)); resultCoords.add(new Point(avgw-1, avgh-1)); resultCoords.add(new Point(0, avgh-1)); Mat start = Converters.vector_Point2f_to_Mat(startCoords); Mat result = Converters.vector_Point2d_to_Mat(resultCoords); start.convertTo(start, CvType.CV_32FC2); result.convertTo(result,CvType.CV_32FC2); Mat mat = new Mat(); Mat perspective = Imgproc.getPerspectiveTransform(start, result); Imgproc.warpPerspective(src, mat, perspective, new Size(avgw, avgh)); return mat; } 

И из-за того же угла мой метод производит этот результат:

Введите описание изображения здесь

Я хочу знать, как это можно сделать? Мне интересно, как им удалось подсчитать длину объекта, просто имея координаты из четырех углов. Кроме того, если это возможно, предоставьте некоторые кодовые / математические объяснения или статьи аналогичного / того же.

Заранее спасибо.

Это появилось несколько раз, прежде чем на SO, но я никогда не видел полного ответа, так что здесь. Реализация, представленная здесь, основана на этой статье, которая выводит полные уравнения: http://research.microsoft.com/en-us/um/people/zhang/papers/tr03-39.pdf

По существу, это показывает, что, предполагая модель камеры с отверстиями, можно рассчитать соотношение сторон для проецируемого прямоугольника (но не масштаб, неудивительно). По существу, можно решить фокусное расстояние, затем получить соотношение сторон. Вот пример реализации в python с использованием OpenCV. Обратите внимание, что вам нужно иметь 4 обнаруженных угла в правильном порядке или не работать (обратите внимание на порядок, это зигзаг). Сообщенные коэффициенты ошибок находятся в диапазоне 3-5%.

 import math import cv2 import scipy.spatial.distance import numpy as np img = cv2.imread('img.png') (rows,cols,_) = img.shape #image center u0 = (cols)/2.0 v0 = (rows)/2.0 #detected corners on the original image p = [] p.append((67,74)) p.append((270,64)) p.append((10,344)) p.append((343,331)) #widths and heights of the projected image w1 = scipy.spatial.distance.euclidean(p[0],p[1]) w2 = scipy.spatial.distance.euclidean(p[2],p[3]) h1 = scipy.spatial.distance.euclidean(p[0],p[2]) h2 = scipy.spatial.distance.euclidean(p[1],p[3]) w = max(w1,w2) h = max(h1,h2) #visible aspect ratio ar_vis = float(w)/float(h) #make numpy arrays and append 1 for linear algebra m1 = np.array((p[0][0],p[0][1],1)).astype('float32') m2 = np.array((p[1][0],p[1][1],1)).astype('float32') m3 = np.array((p[2][0],p[2][1],1)).astype('float32') m4 = np.array((p[3][0],p[3][1],1)).astype('float32') #calculate the focal disrance k2 = np.dot(np.cross(m1,m4),m3) / np.dot(np.cross(m2,m4),m3) k3 = np.dot(np.cross(m1,m4),m2) / np.dot(np.cross(m3,m4),m2) n2 = k2 * m2 - m1 n3 = k3 * m3 - m1 n21 = n2[0] n22 = n2[1] n23 = n2[2] n31 = n3[0] n32 = n3[1] n33 = n3[2] f = math.sqrt(- (1.0/(n23*n33)) * ((n21*n31 - (n21*n33 + n23*n31)*u0 + n23*n33*u0*u0) + (n22*n32 - (n22*n33+n23*n32)*v0 + n23*n33*v0*v0))) A = np.array([[f,0,u0],[0,f,v0],[0,0,1]]).astype('float32') At = np.transpose(A) Ati = np.linalg.inv(At) Ai = np.linalg.inv(A) #calculate the real aspect ratio ar_real = math.sqrt(np.dot(np.dot(np.dot(n2,Ati),Ai),n2)/np.dot(np.dot(np.dot(n3,Ati),Ai),n3)) if ar_real < ar_vis: W = int(w) H = int(W / ar_real) else: H = int(h) W = int(ar_real * H) pts1 = np.array(p).astype('float32') pts2 = np.float32([[0,0],[W,0],[0,H],[W,H]]) #project the image with the new w/h M = cv2.getPerspectiveTransform(pts1,pts2) dst = cv2.warpPerspective(img,M,(W,H)) cv2.imshow('img',img) cv2.imshow('dst',dst) cv2.imwrite('orig.png',img) cv2.imwrite('proj.png',dst) cv2.waitKey(0) 

Оригинал:

Введите описание изображения здесь

Проецируется (разрешение очень низкое, так как я обрезал изображение с экрана, но соотношение сторон кажется правильным):

Введите описание изображения здесь