J'essaie de faire une transformation de perspective d'un ensemble de points afin d'obtenir un redressement effet:
http://nuigroup.com/?ACT=28&fid=27&aid=1892_H6eNAaign4Mrnn30Au8d
J'utilise l'image ci-dessous pour les tests, et le rectangle vert affiche la zone d'intérêt.
Je me demandais s'il était possible d'obtenir l'effet que j'espérais utiliser une simple combinaison de cv::getPerspectiveTransform
Et cv::warpPerspective
. Je partage le code source que j'ai écrit jusqu'à présent, mais cela ne fonctionne pas. Voici l'image résultante:
Il y a donc un vector<cv::Point>
Qui définit la région d'intérêt , mais les points ne sont pas stockés dans un ordre particulier à l'intérieur du vecteur, et c'est quelque chose que je ne peux pas changer dans la procédure de détection. Quoi qu'il en soit, plus tard , les points du vecteur sont utilisés pour définir un RotatedRect
, qui à son tour est utilisé pour assembler cv::Point2f src_vertices[4];
, l'une des variables requises par cv::getPerspectiveTransform()
.
Ma compréhension des sommets et de leur organisation pourrait être l'un des problèmes . Je pense également que l'utilisation d'un RotatedRect
n'est pas la meilleure idée pour stocker les points d'origine du ROI, car le les coordonnées changeront un peu pour tenir dans le rectangle pivoté, et ce n'est pas très cool .
#include <cv.h>
#include <highgui.h>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
cv::Mat src = cv::imread(argv[1], 1);
// After some magical procedure, these are points detect that represent
// the corners of the paper in the picture:
// [408, 69] [72, 2186] [1584, 2426] [1912, 291]
vector<Point> not_a_rect_shape;
not_a_rect_shape.Push_back(Point(408, 69));
not_a_rect_shape.Push_back(Point(72, 2186));
not_a_rect_shape.Push_back(Point(1584, 2426));
not_a_rect_shape.Push_back(Point(1912, 291));
// For debugging purposes, draw green lines connecting those points
// and save it on disk
const Point* point = ¬_a_rect_shape[0];
int n = (int)not_a_rect_shape.size();
Mat draw = src.clone();
polylines(draw, &point, &n, 1, true, Scalar(0, 255, 0), 3, CV_AA);
imwrite("draw.jpg", draw);
// Assemble a rotated rectangle out of that info
RotatedRect box = minAreaRect(cv::Mat(not_a_rect_shape));
std::cout << "Rotated box set to (" << box.boundingRect().x << "," << box.boundingRect().y << ") " << box.size.width << "x" << box.size.height << std::endl;
// Does the order of the points matter? I assume they do NOT.
// But if it does, is there an easy way to identify and order
// them as topLeft, topRight, bottomRight, bottomLeft?
cv::Point2f src_vertices[4];
src_vertices[0] = not_a_rect_shape[0];
src_vertices[1] = not_a_rect_shape[1];
src_vertices[2] = not_a_rect_shape[2];
src_vertices[3] = not_a_rect_shape[3];
Point2f dst_vertices[4];
dst_vertices[0] = Point(0, 0);
dst_vertices[1] = Point(0, box.boundingRect().width-1);
dst_vertices[2] = Point(0, box.boundingRect().height-1);
dst_vertices[3] = Point(box.boundingRect().width-1, box.boundingRect().height-1);
Mat warpMatrix = getPerspectiveTransform(src_vertices, dst_vertices);
cv::Mat rotated;
warpPerspective(src, rotated, warpMatrix, rotated.size(), INTER_LINEAR, BORDER_CONSTANT);
imwrite("rotated.jpg", rotated);
return 0;
}
Quelqu'un peut-il m'aider à résoudre ce problème?
Donc, le premier problème est l'ordre des coins. Ils doivent être dans le même ordre dans les deux vecteurs. Donc, si dans le premier vecteur votre commande est: (en haut à gauche, en bas à gauche, en bas à droite, en haut à droite), elles DOIVENT être dans le même ordre dans l'autre vecteur.
Deuxièmement, pour que l'image résultante ne contienne que l'objet d'intérêt, vous devez définir sa largeur et sa hauteur pour qu'elles soient identiques à la largeur et la hauteur du rectangle résultant. Ne vous inquiétez pas, les images src et dst dans warpPerspective peuvent être de tailles différentes.
Troisièmement, un souci de performance. Bien que votre méthode soit absolument précise, car vous ne faites que des transformations affines (rotation, redimensionnement, redressement), mathématiquement, vous pouvez utiliser le correspondant affine de vos fonctions. Ils sont beaucoup plus rapides.
getAffineTransform ()
warpAffine ().
Remarque importante: la transformation getAffine a besoin et n'attend que 3 points, et la matrice de résultat est de 2 par 3, au lieu de 3 par 3.
Comment faire pour que l'image du résultat ait une taille différente de celle de l'entrée:
cv::warpPerspective(src, dst, dst.size(), ... );
utilisation
cv::Mat rotated;
cv::Size size(box.boundingRect().width, box.boundingRect().height);
cv::warpPerspective(src, dst, size, ... );
Vous voici donc, et votre affectation de programmation est terminée.
void main()
{
cv::Mat src = cv::imread("r8fmh.jpg", 1);
// After some magical procedure, these are points detect that represent
// the corners of the paper in the picture:
// [408, 69] [72, 2186] [1584, 2426] [1912, 291]
vector<Point> not_a_rect_shape;
not_a_rect_shape.Push_back(Point(408, 69));
not_a_rect_shape.Push_back(Point(72, 2186));
not_a_rect_shape.Push_back(Point(1584, 2426));
not_a_rect_shape.Push_back(Point(1912, 291));
// For debugging purposes, draw green lines connecting those points
// and save it on disk
const Point* point = ¬_a_rect_shape[0];
int n = (int)not_a_rect_shape.size();
Mat draw = src.clone();
polylines(draw, &point, &n, 1, true, Scalar(0, 255, 0), 3, CV_AA);
imwrite("draw.jpg", draw);
// Assemble a rotated rectangle out of that info
RotatedRect box = minAreaRect(cv::Mat(not_a_rect_shape));
std::cout << "Rotated box set to (" << box.boundingRect().x << "," << box.boundingRect().y << ") " << box.size.width << "x" << box.size.height << std::endl;
Point2f pts[4];
box.points(pts);
// Does the order of the points matter? I assume they do NOT.
// But if it does, is there an easy way to identify and order
// them as topLeft, topRight, bottomRight, bottomLeft?
cv::Point2f src_vertices[3];
src_vertices[0] = pts[0];
src_vertices[1] = pts[1];
src_vertices[2] = pts[3];
//src_vertices[3] = not_a_rect_shape[3];
Point2f dst_vertices[3];
dst_vertices[0] = Point(0, 0);
dst_vertices[1] = Point(box.boundingRect().width-1, 0);
dst_vertices[2] = Point(0, box.boundingRect().height-1);
/* Mat warpMatrix = getPerspectiveTransform(src_vertices, dst_vertices);
cv::Mat rotated;
cv::Size size(box.boundingRect().width, box.boundingRect().height);
warpPerspective(src, rotated, warpMatrix, size, INTER_LINEAR, BORDER_CONSTANT);*/
Mat warpAffineMatrix = getAffineTransform(src_vertices, dst_vertices);
cv::Mat rotated;
cv::Size size(box.boundingRect().width, box.boundingRect().height);
warpAffine(src, rotated, warpAffineMatrix, size, INTER_LINEAR, BORDER_CONSTANT);
imwrite("rotated.jpg", rotated);
}
Le problème était l'ordre dans lequel les points étaient déclarés à l'intérieur du vecteur, puis il y avait aussi un autre problème lié à cela dans la définition de dst_vertices
.
L'ordre des points importe à getPerspectiveTransform()
et doit être spécifié dans l'ordre suivant:
1st-------2nd
| |
| |
| |
3rd-------4th
Par conséquent, les points d'origine devaient être réorganisés pour cela:
vector<Point> not_a_rect_shape;
not_a_rect_shape.Push_back(Point(408, 69));
not_a_rect_shape.Push_back(Point(1912, 291));
not_a_rect_shape.Push_back(Point(72, 2186));
not_a_rect_shape.Push_back(Point(1584, 2426));
et la destination:
Point2f dst_vertices[4];
dst_vertices[0] = Point(0, 0);
dst_vertices[1] = Point(box.boundingRect().width-1, 0); // Bug was: had mistakenly switched these 2 parameters
dst_vertices[2] = Point(0, box.boundingRect().height-1);
dst_vertices[3] = Point(box.boundingRect().width-1, box.boundingRect().height-1);
Après cela, un recadrage doit être effectué parce que l'image résultante n'est pas seulement la zone dans le rectangle vert comme je le pensais:
Je ne sais pas s'il s'agit d'un bogue d'OpenCV ou s'il me manque quelque chose, mais le problème principal a été résolu.
Lorsque vous travaillez avec un quadrilatère, OpenCV n'est pas vraiment votre ami. RotatedRect
vous donnera des résultats incorrects. Vous aurez également besoin d'une projection en perspective au lieu d'une projection affine comme celles mentionnées ici.
Fondamentalement, ce qui doit être fait est:
J'ai implémenté une classe Quadrangle
qui s'occupe de la conversion de contour en quadrilatère et la transformera également dans la bonne perspective.
Voir une implémentation fonctionnelle ici: Java OpenCV redressant un contour
MISE À JOUR: RÉSOLU
J'ai presque ce travail. Si proche d'être utilisable. Il déforme correctement mais je semble avoir un problème d'échelle ou de traduction. J'ai mis le point d'ancrage à zéro et j'ai également expérimenté le changement de mode d'échelle (aspectFill, scale to fit, etc ...).
Configurez les points de redressement (le rouge les rend difficiles à voir):
Appliquez la transformation calculée:
Maintenant, il fait un bond. Cela semble assez bon sauf que ce n'est pas centré sur l'écran. En ajoutant un mouvement de panoramique à la vue de l'image, je peux la faire glisser et vérifier qu'elle s'aligne:
Ce n'est pas aussi simple que de traduire par -0,5, -0,5 car l'image d'origine devient un polygone qui s'étend très très loin (potentiellement), donc son rectangle de délimitation est beaucoup plus grand que le cadre de l'écran.
Quelqu'un voit-il ce que je peux faire pour que cela soit terminé? Je voudrais le faire s'engager et le partager ici. C'est un sujet populaire mais je n'ai pas trouvé de solution aussi simple que copier/coller.
Le code source complet est ici:
git clone https://github.com/zakkhoyt/Quadrilateral.git
démonstration de git checkout
Cependant, je vais coller les parties pertinentes ici. Cette première méthode est la mienne et c'est là que j'obtiens les points de redressement.
- (IBAction)buttonAction:(id)sender {
Quadrilateral quadFrom;
float scale = 1.0;
quadFrom.topLeft.x = self.topLeftView.center.x / scale;
quadFrom.topLeft.y = self.topLeftView.center.y / scale;
quadFrom.topRight.x = self.topRightView.center.x / scale;
quadFrom.topRight.y = self.topRightView.center.y / scale;
quadFrom.bottomLeft.x = self.bottomLeftView.center.x / scale;
quadFrom.bottomLeft.y = self.bottomLeftView.center.y / scale;
quadFrom.bottomRight.x = self.bottomRightView.center.x / scale;
quadFrom.bottomRight.y = self.bottomRightView.center.y / scale;
Quadrilateral quadTo;
quadTo.topLeft.x = self.view.bounds.Origin.x;
quadTo.topLeft.y = self.view.bounds.Origin.y;
quadTo.topRight.x = self.view.bounds.Origin.x + self.view.bounds.size.width;
quadTo.topRight.y = self.view.bounds.Origin.y;
quadTo.bottomLeft.x = self.view.bounds.Origin.x;
quadTo.bottomLeft.y = self.view.bounds.Origin.y + self.view.bounds.size.height;
quadTo.bottomRight.x = self.view.bounds.Origin.x + self.view.bounds.size.width;
quadTo.bottomRight.y = self.view.bounds.Origin.y + self.view.bounds.size.height;
CATransform3D t = [self transformQuadrilateral:quadFrom toQuadrilateral:quadTo];
// t = CATransform3DScale(t, 0.5, 0.5, 1.0);
self.imageView.layer.anchorPoint = CGPointZero;
[UIView animateWithDuration:1.0 animations:^{
self.imageView.layer.transform = t;
}];
}
#pragma mark OpenCV stuff...
-(CATransform3D)transformQuadrilateral:(Quadrilateral)Origin toQuadrilateral:(Quadrilateral)destination {
CvPoint2D32f *cvsrc = [self openCVMatrixWithQuadrilateral:Origin];
CvMat *src_mat = cvCreateMat( 4, 2, CV_32FC1 );
cvSetData(src_mat, cvsrc, sizeof(CvPoint2D32f));
CvPoint2D32f *cvdst = [self openCVMatrixWithQuadrilateral:destination];
CvMat *dst_mat = cvCreateMat( 4, 2, CV_32FC1 );
cvSetData(dst_mat, cvdst, sizeof(CvPoint2D32f));
CvMat *H = cvCreateMat(3,3,CV_32FC1);
cvFindHomography(src_mat, dst_mat, H);
cvReleaseMat(&src_mat);
cvReleaseMat(&dst_mat);
CATransform3D transform = [self transform3DWithCMatrix:H->data.fl];
cvReleaseMat(&H);
return transform;
}
- (CvPoint2D32f*)openCVMatrixWithQuadrilateral:(Quadrilateral)Origin {
CvPoint2D32f *cvsrc = (CvPoint2D32f *)malloc(4*sizeof(CvPoint2D32f));
cvsrc[0].x = Origin.topLeft.x;
cvsrc[0].y = Origin.topLeft.y;
cvsrc[1].x = Origin.topRight.x;
cvsrc[1].y = Origin.topRight.y;
cvsrc[2].x = Origin.bottomRight.x;
cvsrc[2].y = Origin.bottomRight.y;
cvsrc[3].x = Origin.bottomLeft.x;
cvsrc[3].y = Origin.bottomLeft.y;
return cvsrc;
}
-(CATransform3D)transform3DWithCMatrix:(float *)matrix {
CATransform3D transform = CATransform3DIdentity;
transform.m11 = matrix[0];
transform.m21 = matrix[1];
transform.m41 = matrix[2];
transform.m12 = matrix[3];
transform.m22 = matrix[4];
transform.m42 = matrix[5];
transform.m14 = matrix[6];
transform.m24 = matrix[7];
transform.m44 = matrix[8];
return transform;
}
Mise à jour: je l'ai fait fonctionner correctement. Les coordonnées devaient être Origine au centre, pas à gauche. J'ai appliqué xOffset et yOffset et alto. Code de démonstration à l'emplacement mentionné ci-dessus (branche "démo")
J'ai eu le même genre de problème et l'ai résolu en utilisant la fonction d'extraction d'homographie d'OpenCV.
Vous pouvez voir comment j'ai fait dans cette question: Transformer une image rectangulaire en un quadrilatère en utilisant un CATransform3D
Très inspiré par la réponse de @ VaporwareWolf, implémentée en C # à l'aide de Xamarin MonoTouch pour iOS. La principale différence est que j'utilise GetPerspectiveTransform au lieu de FindHomography et TopLeft plutôt que ScaleToFit pour le mode de contenu:
void SetupWarpedImage(UIImage sourceImage, Quad sourceQuad, RectangleF destRectangle)
{
var imageContainerView = new UIView(destRectangle)
{
ClipsToBounds = true,
ContentMode = UIViewContentMode.TopLeft
};
InsertSubview(imageContainerView, 0);
var imageView = new UIImageView(imageContainerView.Bounds)
{
ContentMode = UIViewContentMode.TopLeft,
Image = sourceImage
};
var offset = new PointF(-imageView.Bounds.Width / 2, -imageView.Bounds.Height / 2);
var dest = imageView.Bounds;
dest.Offset(offset);
var destQuad = dest.ToQuad();
var transformMatrix = Quad.GeneratePerspectiveTransformMatrixFromQuad(sourceQuad, destQuad);
CATransform3D transform = transformMatrix.ToCATransform3D();
imageView.Layer.AnchorPoint = new PointF(0f, 0f);
imageView.Layer.Transform = transform;
imageContainerView.Add(imageView);
}