/// <summary> /// Updates a collection of points constituting a pattern for recognition. Zero point is at the center. /// </summary> /// <param name="patternSize">For chessbord, count the inner corners</param> /// <param name="transform">The transform must be scaled to fit the aspect of the pattern</param> /// <param name="patternType">OpenCv supported pattern type</param> /// <param name="patternPointsWorldSpace">Point collection</param> public static void UpdateWorldSpacePatternPoints(Vector2Int patternSize, Matrix4x4 patternToWorldMatrix, PatternType patternType, Vector2 patternBorderSizeUV, ref MatOfPoint3f pointsWorldSpace) { bool isAsym = patternType == PatternType.AsymmetricCircleGrid; // Instantiate array. int cornerCount = patternSize.y * patternSize.x; if (pointsWorldSpace == null || pointsWorldSpace.rows() != cornerCount) { pointsWorldSpace = new MatOfPoint3f(); pointsWorldSpace.alloc(cornerCount); } // Fill. int c = 0; Vector2 size = Vector2.one - patternBorderSizeUV * 2; Vector2 step = new Vector2(size.x / (patternSize.x - 1f + (isAsym ? 0.5f : 0)), size.y / (patternSize.y - 1f)); for (int ny = 0; ny < patternSize.y; ny++) { float y = 1 - patternBorderSizeUV.y - ny * step.y - 0.5f; for (int nx = 0; nx < patternSize.x; nx++, c++) { float x = patternBorderSizeUV.x + nx * step.x - 0.5f; Vector3 point = new Vector3(x, y, 0); if (isAsym && ny % 2 == 1) { point.x += step.x * 0.5f; } point = patternToWorldMatrix.MultiplyPoint3x4(point); pointsWorldSpace.WriteVector3(point, c); } } }
void UpdateCirclePatternSize() { if (_state == State.Initiating || _state == State.BlindCalibration) { _circlePatternSize = defaultCirclesPatternSize; } else { const int circlePatternSizeYMin = 7; const int desiredPixelsPerCirclePatternSegment = 25; // 50px is recommended, but for a 720p camera, this will give too fex dots. float desiredCirclePatternNumAspect = _chessPatternSize.x / (_chessPatternSize.y) / 2f; // 3f / 4f / 2f; float patternDistance = Vector3.Distance(_mainCamera.transform.position, _circlePatternTransform.position); float patternHeight = _chessPatternTransform.localScale.y; float viewHeightAtPatternPosition = Mathf.Tan(_mainCamera.fieldOfView * Mathf.Deg2Rad * 0.5f) * patternDistance * 2; int circlePatternPixelHeight = (int)((patternHeight / viewHeightAtPatternPosition) * _cameraTexture.height); int optimalPatternSizeY = Mathf.Max(circlePatternSizeYMin, Mathf.FloorToInt(circlePatternPixelHeight / (float)desiredPixelsPerCirclePatternSegment)); int optimalPatternSizeX = Mathf.FloorToInt(optimalPatternSizeY * desiredCirclePatternNumAspect); _circlePatternSize = TrackingToolsHelper.GetClosestValidPatternSize(new Vector2Int(optimalPatternSizeX, optimalPatternSizeY), TrackingToolsHelper.PatternType.AsymmetricCircleGrid); } _circlePatternPointCount = _circlePatternSize.x * _circlePatternSize.y; if (_circlePointsProjectorRenderImageMat != null && _circlePointsProjectorRenderImageMat.rows() == _circlePatternPointCount) { return; } if (_circlePointsProjectorRenderImageMat != null) { _circlePointsProjectorRenderImageMat.release(); } if (_circlePointsRealModelMat != null) { _circlePointsRealModelMat.release(); } if (_circlePointsDetectedWorldMat != null) { _circlePointsDetectedWorldMat.release(); } _circlePointsProjectorRenderImageMat.alloc(_circlePatternPointCount); _circlePointsRealModelMat.alloc(_circlePatternPointCount); _circlePointsDetectedWorldMat.alloc(_circlePatternPointCount); // Render pattern to texture. _circlePatternBorderSizeUV = TrackingToolsHelper.RenderPattern(_circlePatternSize, TrackingToolsHelper.PatternType.AsymmetricCircleGrid, 2048, ref _circlePatternTexture, ref _patternRenderMaterial, circlePatternBorder, true); _circlePatternBoardMaterial.mainTexture = _circlePatternTexture; // Update transform to match. float circleTextureAspect = _circlePatternTexture.width / (float)_circlePatternTexture.height; float borderProportion = (_circlePatternSize.y - 1 + 2f) / (_circlePatternSize.y - 1f); // Asymmetric patttern tiles are half the height. _circlePatternTransform.localScale = new Vector3(circleTextureAspect, 1, 0) * _chessPatternTransform.localScale.y * borderProportion; if (_state == State.TrackedCalibration || _state == State.Testing) { _circlePatternTransform.localPosition = -Vector3.right * (_chessCirclePatternCenterOffset / 1000f); } }
/// <summary> /// Creates a collection of points constituting a pattern for recognition. Measured in millimeters. /// In real space, y increases upwards. Since the first point is at upper-left corner, y starts high and decrease. /// </summary> /// <param name="patternSize">For chessbord, count the inner corners</param> /// <param name="tileSize">Measured in millimeters</param> /// <param name="patternType">OpenCv supported pattern type</param> /// <returns>Points in real space (mm)</returns> public static MatOfPoint3f CreateRealModelPatternPoints(Vector2Int patternSize, int tileSize, PatternType patternType) { bool isAsym = patternType == PatternType.AsymmetricCircleGrid; int cornerCount = patternSize.y * patternSize.x; Vector2Int boardSize = patternSize * tileSize; MatOfPoint3f chessCornersModelSpace = new MatOfPoint3f(); chessCornersModelSpace.alloc(cornerCount); int c = 0; _temp3d[2] = 0; for (int y = 0; y < patternSize.y; y++) { for (int x = 0; x < patternSize.x; x++, c++) { _temp3d[0] = x * tileSize + (isAsym && y % 2 == 1 ? tileSize * 0.5 : 0); _temp3d[1] = boardSize.y - y * tileSize * (isAsym ? 0.5f : 1f); chessCornersModelSpace.put(c, 0, _temp3d); } } return(chessCornersModelSpace); }
void Awake() { // Create UI. if (!_containerUI) { _containerUI = new GameObject("CameraPoser").AddComponent <RectTransform>(); _containerUI.transform.SetParent(_canvas.transform); } CanvasGroup wrapperGroup = _containerUI.GetComponent <CanvasGroup>(); if (!wrapperGroup) { wrapperGroup = _containerUI.gameObject.AddComponent <CanvasGroup>(); } wrapperGroup.alpha = _alpha; Image backgroundImage = new GameObject("Background").AddComponent <Image>(); backgroundImage.transform.SetParent(_containerUI.transform); _rawImageUI = new GameObject("CameraImage").AddComponent <RawImage>(); _rawImageUI.transform.SetParent(_containerUI.transform); _rawImageUI.uvRect = _flipTexture ? new UnityEngine.Rect(0, 1, 1, -1) : new UnityEngine.Rect(0, 0, 1, 1); _rawImageRect = _rawImageUI.GetComponent <RectTransform>(); _uiMaterial = new Material(Shader.Find("Hidden/SingleChannelTexture")); _rawImageUI.material = _uiMaterial; _aspectFitterUI = _rawImageUI.gameObject.AddComponent <AspectRatioFitter>(); _aspectFitterUI.aspectMode = AspectRatioFitter.AspectMode.HeightControlsWidth; backgroundImage.color = Color.black; ExpandRectTransform(_containerUI); ExpandRectTransform(backgroundImage.GetComponent <RectTransform>()); ExpandRectTransform(_rawImageRect); _userPointRects = new RectTransform[pointCount]; _userPointImages = new Image[pointCount]; for (int p = 0; p < pointCount; p++) { GameObject pointObject = new GameObject("Point" + p); pointObject.transform.SetParent(_rawImageRect); Image pointImage = pointObject.AddComponent <Image>(); pointImage.color = Color.cyan; RectTransform pointRect = pointObject.GetComponent <RectTransform>(); pointRect.sizeDelta = Vector2.one * 5; SetAnchoredPosition(pointRect, defaultPointPositions[p]); pointRect.anchoredPosition = Vector3.zero; Text pointLabel = new GameObject("Label").AddComponent <Text>(); pointLabel.text = p.ToString(); pointLabel.transform.SetParent(pointRect); pointLabel.rectTransform.anchoredPosition = Vector2.zero; pointLabel.rectTransform.sizeDelta = new Vector2(_fontSize, _fontSize) * 2; pointLabel.font = _font; pointLabel.fontSize = _fontSize; _userPointRects[p] = pointRect; _userPointImages[p] = pointImage; } // Hide. //if( !_interactable ) _containerUI.transform.gameObject.SetActive( false ); // Prepare OpenCV. _noDistCoeffs = new MatOfDouble(new double[] { 0, 0, 0, 0 }); _rVec = new Mat(); _tVec = new Mat(); _anchorPointsImage = new Point[pointCount]; _anchorPointsWorld = new Point3[pointCount]; _anchorPointsImageMat = new MatOfPoint2f(); _anchorPointsWorldMat = new MatOfPoint3f(); _anchorPointsImageMat.alloc(pointCount); _anchorPointsWorldMat.alloc(pointCount); for (int p = 0; p < pointCount; p++) { _anchorPointsImage[p] = new Point(); _anchorPointsWorld[p] = new Point3(); } // Load files. if (Intrinsics.TryLoadFromFile(_intrinsicsFileName, out _intrinsics)) { enabled = false; return; } LoadCircleAnchorPoints(); // Update variables. if (!Application.isEditor) { OnValidate(); } }