diff --git a/cpp/FOCV_Function.cpp b/cpp/FOCV_Function.cpp index 5d30a66..0d6b872 100644 --- a/cpp/FOCV_Function.cpp +++ b/cpp/FOCV_Function.cpp @@ -953,13 +953,18 @@ jsi::Object FOCV_Function::invoke(jsi::Runtime& runtime, const jsi::Value* argum } break; case hashString("drawContours", 12): { auto img = args.asMatPtr(1); - auto contours = args.asMatVectorPtr(2); auto contourIdx = args.asNumber(3); auto color = args.asScalarPtr(4); auto thickness = args.asNumber(5); auto line_type = args.asNumber(6); - - cv::drawContours(*img, *contours, contourIdx, *color, thickness, line_type); + + if (args.isMatVector(2)) { + auto contours = args.asMatVectorPtr(2); + cv::drawContours(*img, *contours, contourIdx, *color, thickness, line_type); + } else { + auto contours = args.asPointVectorOfVectorsPtr(2); + cv::drawContours(*img, *contours, contourIdx, *color, thickness, line_type); + } } break; case hashString("drawMarker", 10): { auto img = args.asMatPtr(1); @@ -1000,11 +1005,16 @@ jsi::Object FOCV_Function::invoke(jsi::Runtime& runtime, const jsi::Value* argum } break; case hashString("fillPoly", 8): { auto img = args.asMatPtr(1); - auto pts = args.asMatVectorPtr(2); auto color = args.asScalarPtr(3); auto line_type = args.asNumber(4); - - cv::fillPoly(*img, *pts, *color, line_type); + + if (args.isMatVector(2)) { + auto pts = args.asMatVectorPtr(2); + cv::fillPoly(*img, *pts, *color, line_type); + } else { + auto pts = args.asPointVectorOfVectorsPtr(2); + cv::fillPoly(*img, *pts, *color, line_type); + } } break; case hashString("line", 4): { auto img = args.asMatPtr(1); @@ -1018,13 +1028,18 @@ jsi::Object FOCV_Function::invoke(jsi::Runtime& runtime, const jsi::Value* argum } break; case hashString("polylines", 9): { auto img = args.asMatPtr(1); - auto pts = args.asMatVectorPtr(2); auto isClosed = args.asBool(3); auto color = args.asScalarPtr(4); auto thickness = args.asNumber(5); auto line_type = args.asNumber(6); - - cv::polylines(*img, *pts, isClosed, *color, thickness, line_type); + + if (args.isMatVector(2)) { + auto pts = args.asMatVectorPtr(2); + cv::polylines(*img, *pts, isClosed, *color, thickness, line_type); + } else { + auto pts = args.asPointVectorOfVectorsPtr(2); + cv::polylines(*img, *pts, isClosed, *color, thickness, line_type); + } } break; case hashString("rectangle", 9): { auto img = args.asMatPtr(1); @@ -1062,12 +1077,40 @@ jsi::Object FOCV_Function::invoke(jsi::Runtime& runtime, const jsi::Value* argum } break; case hashString("goodFeaturesToTrack", 19): { auto image = args.asMatPtr(1); - auto corners = args.asMatPtr(2); auto maxCorners = args.asNumber(3); auto qualityLevel = args.asNumber(4); auto minDistance = args.asNumber(5); - - cv::goodFeaturesToTrack(*image, *corners, maxCorners, qualityLevel, minDistance); + auto blockSize = count > 6 ? args.asNumber(6) : 3; + auto useHarrisDetector = count > 7 ? args.asBool(7) : false; + auto k = count > 8 ? args.asNumber(8) : 0.04; + + if (args.isPoint2fVector(2)) { + auto corners = args.asPoint2fVectorPtr(2); + cv::goodFeaturesToTrack( + *image, + *corners, + maxCorners, + qualityLevel, + minDistance, + cv::noArray(), + blockSize, + useHarrisDetector, + k + ); + } else { + auto corners = args.asMatPtr(2); + cv::goodFeaturesToTrack( + *image, + *corners, + maxCorners, + qualityLevel, + minDistance, + cv::noArray(), + blockSize, + useHarrisDetector, + k + ); + } } break; case hashString("HoughCircles", 12): { auto image = args.asMatPtr(1); @@ -1494,6 +1537,17 @@ jsi::Object FOCV_Function::invoke(jsi::Runtime& runtime, const jsi::Value* argum } } break; + case hashString("fitEllipse", 10): { + cv::RotatedRect rect; + + if (args.isMat(1)) { + rect = cv::fitEllipse(*args.asMatPtr(1)); + } else { + rect = cv::fitEllipse(*args.asPointVectorPtr(1)); + } + + return FOCV_JsiObject::wrap(runtime, "rotated_rect", std::make_shared(rect)); + } break; case hashString("fitLine", 7): { auto points = args.asMatPtr(1); auto line = args.asMatPtr(2); @@ -1684,12 +1738,64 @@ jsi::Object FOCV_Function::invoke(jsi::Runtime& runtime, const jsi::Value* argum return FOCV_JsiObject::wrap(runtime, "mat", std::make_shared(H)); } break; + case hashString("calcOpticalFlowPyrLK", 19): { + auto prevImg = args.asMatPtr(1); + auto nextImg = args.asMatPtr(2); + auto prevPts = args.asPoint2fVectorPtr(3); + auto nextPts = args.asPoint2fVectorPtr(4); + auto status = args.asMatPtr(5); + auto err = args.asMatPtr(6); + auto winSize = args.asSizePtr(7); + auto maxLevel = args.asNumber(8); + auto criteria = args.asTermCriteriaPtr(9); + + cv::calcOpticalFlowPyrLK( + *prevImg, + *nextImg, + *prevPts, + *nextPts, + *status, + *err, + *winSize, + maxLevel, + *criteria + ); + } break; + + case hashString("estimateAffinePartial2D", 22): { + auto from = args.asPoint2fVectorPtr(1); + auto to = args.asPoint2fVectorPtr(2); + auto inliers = args.asMatPtr(3); + auto method = count > 4 ? static_cast(args.asNumber(4)) : cv::RANSAC; + auto ransacReprojThreshold = count > 5 ? args.asNumber(5) : 3.0; + auto maxIters = count > 6 ? static_cast(args.asNumber(6)) : 2000; + auto confidence = count > 7 ? args.asNumber(7) : 0.99; + auto refineIters = count > 8 ? static_cast(args.asNumber(8)) : 10; + + cv::Mat transform = cv::estimateAffinePartial2D( + *from, + *to, + *inliers, + method, + ransacReprojThreshold, + maxIters, + confidence, + refineIters + ); + + return FOCV_JsiObject::wrap(runtime, "mat", std::make_shared(transform)); + } break; + // ================== END FEATURE MATCHING FUNCTIONS ================== } } catch (cv::Exception& e) { std::string message(e.what()); std::cout << "Fast OpenCV Invoke Error: " << message << "\n"; throw std::runtime_error("Fast OpenCV Error: " + message); + } catch (std::exception& e) { + std::string message(e.what()); + std::cout << "Fast OpenCV Invoke Error: " << message << "\n"; + throw std::runtime_error("Fast OpenCV Error: " + message); } return value; diff --git a/docs/content/apidetails.md b/docs/content/apidetails.md index ad78c3e..ac876ce 100644 --- a/docs/content/apidetails.md +++ b/docs/content/apidetails.md @@ -234,11 +234,11 @@ Represents a rotated rectangle. **Properties:** - `type`: `ObjectType.RotatedRect` -- `x`: `number` -- `y`: `number` -- `width`: `number` -- `height`: `number` -- `angle`: `number` +- `x`: `number` - center x coordinate +- `y`: `number` - center y coordinate +- `width`: `number` - full width of the rotated rectangle +- `height`: `number` - full height of the rotated rectangle +- `angle`: `number` - rotation angle in degrees **Methods:** - `release(): void` diff --git a/docs/content/availablefunctions.md b/docs/content/availablefunctions.md index 2ddbb6d..d137f99 100644 --- a/docs/content/availablefunctions.md +++ b/docs/content/availablefunctions.md @@ -1561,10 +1561,41 @@ Determines strong corners on an image ```js OpenCV.goodFeaturesToTrack(image: Mat, - corners: Mat, + corners: Mat | Point2fVector, maxCorners: number, qualityLevel: number, - minDistance: number + minDistance: number, + blockSize?: number, + useHarrisDetector?: boolean, + k?: number +): void; +``` + +### calcOpticalFlowPyrLK + +Calculates an optical flow for a sparse feature set using the iterative Lucas-Kanade method with pyramids + +- name Function name. +- prevImg First 8-bit input image +- nextImg Second input image of the same size and type as prevImg +- prevPts Vector of 2D points for which the flow needs to be found +- nextPts Output vector of 2D points containing the calculated new positions of input features in the second image +- status Output status vector (1 if the flow for the corresponding feature has been found, otherwise 0) +- err Output vector of errors +- winSize Size of the search window at each pyramid level +- maxLevel 0-based maximal pyramid level number +- criteria Parameter specifying the termination criteria of the iterative search algorithm + +```js +OpenCV.calcOpticalFlowPyrLK(prevImg: Mat, + nextImg: Mat, + prevPts: Point2fVector, + nextPts: Point2fVector, + status: Mat, + err: Mat, + winSize: Size, + maxLevel: number, + criteria: TermCriteria ): void; ``` @@ -1752,6 +1783,34 @@ OpenCV.findHomographyFromMatches( ): Mat; ``` +### estimateAffinePartial2D + +Computes an optimal limited affine transform (4 degrees of freedom) between two 2D point sets. + +- from First input 2D point set +- to Second input 2D point set +- inliers Output inlier mask +- method Robust method (`cv::RANSAC` = 8 by default) +- ransacReprojThreshold Maximum reprojection error in pixels for inliers +- maxIters Maximum robust-method iterations +- confidence Confidence level between 0 and 1 +- refineIters Maximum number of refinement iterations + +Returns a 2x3 affine transform `Mat`, or an empty `Mat` on failure. + +```js +OpenCV.estimateAffinePartial2D( + from: Point2fVector, + to: Point2fVector, + inliers: Mat, + method?: number, + ransacReprojThreshold?: number, + maxIters?: number, + confidence?: number, + refineIters?: number +): Mat; +``` + ## Imgproc – Image Filtering ### bilateralFilter @@ -2370,6 +2429,25 @@ OpenCV.findContoursWithHierarchy(image: Mat, ): void; ``` +### fitEllipse + +Fits an ellipse around a set of 2D points. + +- points Input 2D point set, stored in a Mat or PointVector. It should contain at least 5 points. +@returns the rotated rectangle in which the ellipse is inscribed. In this library, `x` and `y` are the ellipse center coordinates, while `width` and `height` are the full axis lengths. + +```js +OpenCV.fitEllipse(points: Mat | PointVector): RotatedRect; +``` + +```js +const ellipse = OpenCV.fitEllipse(contour); + +console.log(ellipse.x, ellipse.y); // center +console.log(ellipse.width, ellipse.height); // full axis lengths +console.log(ellipse.angle); // degrees +``` + ### fitLine Fits a line to a 2D or 3D point set. diff --git a/docs/content/examples/_meta.js b/docs/content/examples/_meta.js index e082ef9..a070007 100644 --- a/docs/content/examples/_meta.js +++ b/docs/content/examples/_meta.js @@ -1,4 +1,5 @@ export default { realtimedetection: 'Real-time detection', blur: 'Blur image on separated thread', + fitellipse: 'Fit ellipse to a contour', }; diff --git a/docs/content/examples/fitellipse.md b/docs/content/examples/fitellipse.md new file mode 100644 index 0000000..7749118 --- /dev/null +++ b/docs/content/examples/fitellipse.md @@ -0,0 +1,170 @@ +# Fit an ellipse to a contour + +This example shows the smallest useful `fitEllipse` flow in this repo: build a contour, fit the ellipse, draw the result, and inspect the returned `RotatedRect`. + +### Why this example uses synthetic points + +The goal here is to demonstrate the `fitEllipse` API itself, not contour extraction. A synthetic contour keeps the result deterministic and makes the returned geometry easy to verify. + +In real image-processing pipelines you would usually pass a contour returned by `findContours`. + +### Returned shape + +`OpenCV.fitEllipse(...)` returns a `RotatedRect` in this library: + +- `x`, `y` are the ellipse center coordinates +- `width`, `height` are the full axis lengths +- `angle` is the rotation angle in degrees + +When drawing that ellipse back with `OpenCV.ellipse(...)`, remember that the drawing API expects half-axis sizes, so the example divides `width` and `height` by `2`. + +### Code + +```js +import { useState } from 'react'; +import { Button, Image, ScrollView, StyleSheet, Text, View } from 'react-native'; +import { + LineTypes, + Mat, + OpenCV, + Point, + PointVector, + Scalar, + Size, +} from 'react-native-fast-opencv'; + +const CANVAS_SIZE = 320; + +const samplePoints = [ + [63, 177], + [76, 211], + [103, 239], + [143, 254], + [188, 251], + [227, 229], + [254, 193], + [264, 150], + [256, 108], + [228, 74], + [189, 53], + [144, 49], + [103, 65], + [75, 95], + [61, 136], +]; + +function buildEllipseExample() { + const background = new Uint8Array(CANVAS_SIZE * CANVAS_SIZE * 3).fill(255); + const image = Mat.createFromBuffer( + 'uint8', + CANVAS_SIZE, + CANVAS_SIZE, + 3, + background + ); + const contour = PointVector.create(); + + for (const [x, y] of samplePoints) { + const point = Point.create(x, y); + contour.push(point); + + OpenCV.circle( + image, + point, + 4, + Scalar.create(70, 70, 70), + LineTypes.FILLED, + LineTypes.LINE_AA + ); + } + + const fittedEllipse = OpenCV.fitEllipse(contour); + const center = Point.create( + Math.round(fittedEllipse.x), + Math.round(fittedEllipse.y) + ); + const axes = Size.create( + Math.round(fittedEllipse.width / 2), + Math.round(fittedEllipse.height / 2) + ); + + OpenCV.ellipse( + image, + center, + axes, + fittedEllipse.angle, + 0, + 360, + Scalar.create(40, 110, 255), + 4, + LineTypes.LINE_AA + ); + + return { + image: image.toBase64(), + ellipse: fittedEllipse, + }; +} + +export function FitEllipseExample() { + const [example, setExample] = useState(() => buildEllipseExample()); + + return ( + +