mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 07:43:03 +04:00
Compare commits
61 Commits
8d69623051
...
5.x
| Author | SHA1 | Date | |
|---|---|---|---|
| a5e4c04903 | |||
| 8e4e45eac3 | |||
| b94079a214 | |||
| 62732ab966 | |||
| 0a7288695f | |||
| f3a8f654c3 | |||
| 51f7547bf1 | |||
| 57bcf14e78 | |||
| c2e0e4edb9 | |||
| 940810b589 | |||
| 0bc2636564 | |||
| 2c67582020 | |||
| ad8056f9b0 | |||
| 46331314a5 | |||
| 49b8f7c03f | |||
| 36d8bc4a44 | |||
| e1f1495faa | |||
| 7a25b1986d | |||
| 53d308c2a5 | |||
| 4ccee4a65f | |||
| 6e70e5d2a9 | |||
| 4ba2752f36 | |||
| 9fcdbaa8b5 | |||
| 18b39f1f34 | |||
| 73c580723c | |||
| fe482bd575 | |||
| 859d550029 | |||
| b83e561526 | |||
| 17d63dddf6 | |||
| b671e1e56f | |||
| 0d88e1933a | |||
| e247376ee2 | |||
| b41cda2b93 | |||
| c3d4337abb | |||
| aacac3d349 | |||
| 864f9594ad | |||
| a1084efef2 | |||
| 4bace2fc28 | |||
| 9c487708f9 | |||
| 8308fbf9ba | |||
| 325e30db1d | |||
| 757c40c81a | |||
| 1af832250b | |||
| c219bb783d | |||
| eccd1c43c4 | |||
| c42778a0e8 | |||
| 6b04d456b3 | |||
| 7cc9947809 | |||
| dcc3daf76b | |||
| 319a03a2c9 | |||
| fd4491fcb1 | |||
| ae392712b1 | |||
| ccbc31a0f1 | |||
| 4ef179b4bf | |||
| f552e8ac82 | |||
| 747a71fdfe | |||
| bf5224cfa8 | |||
| a02eb6ad0f | |||
| 65c93e673d | |||
| 08077a00a1 | |||
| 72b3fea7dd |
@@ -53,7 +53,7 @@ std::string diagnosticKeys =
|
||||
"{ model m | | Path to the model file. }"
|
||||
"{ config c | | Path to the model configuration file. }"
|
||||
"{ framework f | | [Optional] Name of the model framework. }"
|
||||
"{ engine e | auto | [Optional] Graph negine selector: auto or classic or new}"
|
||||
"{ engine e | auto | [Optional] DNN engine selector: auto (default), opencv or ort}"
|
||||
"{ input0_name | | [Optional] Name of input0. Use with input0_shape}"
|
||||
"{ input0_shape | | [Optional] Shape of input0. Use with input0_name}"
|
||||
"{ input1_name | | [Optional] Name of input1. Use with input1_shape}"
|
||||
@@ -104,13 +104,13 @@ int main( int argc, const char** argv )
|
||||
std::string eng_name = argParser.get<std::string>("engine");
|
||||
if(eng_name == "auto")
|
||||
engine = dnn::ENGINE_AUTO;
|
||||
else if(eng_name == "classic")
|
||||
engine = dnn::ENGINE_CLASSIC;
|
||||
else if(eng_name == "new")
|
||||
engine = dnn::ENGINE_NEW;
|
||||
else if(eng_name == "opencv")
|
||||
engine = dnn::ENGINE_OPENCV;
|
||||
else if(eng_name == "ort")
|
||||
engine = dnn::ENGINE_ORT;
|
||||
else
|
||||
{
|
||||
std::cerr << "Unknown DNN graph engine \"" << eng_name << "\"\n";
|
||||
std::cerr << "Unknown DNN graph engine \"" << eng_name << "\" (use 'auto', 'opencv' or 'ort')\n";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,6 +832,8 @@ macro(ocv_glob_module_sources)
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/hal/*.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/utils/*.hpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/utils/*.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/slam/*.hpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/slam/*.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/legacy/*.h"
|
||||
)
|
||||
file(GLOB lib_hdrs_detail
|
||||
|
||||
@@ -678,9 +678,8 @@ def _redirect_orphan_duplicates(app, out_dir: pathlib.Path) -> None:
|
||||
_DNN_ENGINE_LINKS = {
|
||||
"readNet": "dnn.html#readnet",
|
||||
"readNetFromONNX": "dnn.html#readnetfromonnx",
|
||||
"ENGINE_NEW": "dnn.html#enginetype",
|
||||
"ENGINE_CLASSIC": "dnn.html#enginetype",
|
||||
"ENGINE_AUTO": "dnn.html#enginetype",
|
||||
"ENGINE_OPENCV": "dnn.html#enginetype",
|
||||
"ENGINE_ORT": "dnn.html#enginetype",
|
||||
"EngineType": "dnn.html#enginetype",
|
||||
"DNN_BACKEND_CUDA": "dnn.html#backend",
|
||||
|
||||
@@ -1256,7 +1256,7 @@ A minimum of 2 motions with non parallel rotation axes are necessary to determin
|
||||
So at least 3 different poses are required, but it is strongly recommended to use many more poses.
|
||||
|
||||
*/
|
||||
CV_EXPORTS void calibrateHandEye( InputArrayOfArrays R_gripper2base, InputArrayOfArrays t_gripper2base,
|
||||
CV_EXPORTS_W void calibrateHandEye( InputArrayOfArrays R_gripper2base, InputArrayOfArrays t_gripper2base,
|
||||
InputArrayOfArrays R_target2cam, InputArrayOfArrays t_target2cam,
|
||||
OutputArray R_cam2gripper, OutputArray t_cam2gripper,
|
||||
HandEyeCalibrationMethod method=CALIB_HAND_EYE_TSAI );
|
||||
@@ -1399,7 +1399,7 @@ This problem is also known as solving the \f$\mathbf{A}\mathbf{X}=\mathbf{Z}\mat
|
||||
At least 3 measurements are required (input vectors size must be greater or equal to 3).
|
||||
|
||||
*/
|
||||
CV_EXPORTS void calibrateRobotWorldHandEye( InputArrayOfArrays R_world2cam, InputArrayOfArrays t_world2cam,
|
||||
CV_EXPORTS_W void calibrateRobotWorldHandEye( InputArrayOfArrays R_world2cam, InputArrayOfArrays t_world2cam,
|
||||
InputArrayOfArrays R_base2gripper, InputArrayOfArrays t_base2gripper,
|
||||
OutputArray R_base2world, OutputArray t_base2world,
|
||||
OutputArray R_gripper2cam, OutputArray t_gripper2cam,
|
||||
|
||||
@@ -92,5 +92,85 @@ class calibration_test(NewOpenCVTests):
|
||||
self.assertTrue(isinstance(dist, (float, np.floating)))
|
||||
self.assertGreaterEqual(dist, 0.0)
|
||||
|
||||
@staticmethod
|
||||
def _random_rt():
|
||||
# random rotation (axis-angle) + random translation
|
||||
axis = np.random.uniform(-1, 1, 3)
|
||||
axis /= np.linalg.norm(axis)
|
||||
angle = np.random.uniform(0.2, 1.0) * (1 if np.random.rand() > 0.5 else -1)
|
||||
rvec = (axis * angle).astype(np.float64)
|
||||
R, _ = cv.Rodrigues(rvec)
|
||||
t = np.random.uniform(-100, 100, 3).astype(np.float64)
|
||||
return R, t
|
||||
|
||||
@staticmethod
|
||||
def _to_T(R, t):
|
||||
T = np.eye(4)
|
||||
T[:3, :3] = R
|
||||
T[:3, 3] = t
|
||||
return T
|
||||
|
||||
def test_calibrateHandEye(self):
|
||||
# Ground truth camera-to-gripper transform (what calibrateHandEye should recover)
|
||||
R_cam2gripper_gt, t_cam2gripper_gt = self._random_rt()
|
||||
X = self._to_T(R_cam2gripper_gt, t_cam2gripper_gt)
|
||||
|
||||
# Arbitrary fixed target-to-base transform (the calibration board doesn't move)
|
||||
R_target2base, t_target2base = self._random_rt()
|
||||
Wt = self._to_T(R_target2base, t_target2base)
|
||||
|
||||
n_poses = 10
|
||||
Rs_gripper2base, ts_gripper2base = [], []
|
||||
Rs_target2cam, ts_target2cam = [], []
|
||||
for _ in range(n_poses):
|
||||
R_g2b, t_g2b = self._random_rt()
|
||||
G = self._to_T(R_g2b, t_g2b)
|
||||
# Consistent synthetic data: Wt == G @ X @ T_target2cam for every pose
|
||||
T_target2cam = np.linalg.inv(X) @ np.linalg.inv(G) @ Wt
|
||||
|
||||
Rs_gripper2base.append(R_g2b)
|
||||
ts_gripper2base.append(t_g2b)
|
||||
Rs_target2cam.append(T_target2cam[:3, :3])
|
||||
ts_target2cam.append(T_target2cam[:3, 3])
|
||||
|
||||
R_cam2gripper, t_cam2gripper = cv.calibrateHandEye(
|
||||
Rs_gripper2base, ts_gripper2base, Rs_target2cam, ts_target2cam)
|
||||
|
||||
self.assertLess(cv.norm(R_cam2gripper - R_cam2gripper_gt, cv.NORM_L1), 1e-3)
|
||||
self.assertLess(cv.norm(t_cam2gripper.flatten() - t_cam2gripper_gt, cv.NORM_L1), 1e-3)
|
||||
|
||||
def test_calibrateRobotWorldHandEye(self):
|
||||
# Ground truth outputs (what calibrateRobotWorldHandEye should recover)
|
||||
R_base2world_gt, t_base2world_gt = self._random_rt()
|
||||
X = self._to_T(R_base2world_gt, t_base2world_gt)
|
||||
|
||||
R_gripper2cam_gt, t_gripper2cam_gt = self._random_rt()
|
||||
Z = self._to_T(R_gripper2cam_gt, t_gripper2cam_gt)
|
||||
|
||||
n_poses = 10
|
||||
Rs_world2cam, ts_world2cam = [], []
|
||||
Rs_base2gripper, ts_base2gripper = [], []
|
||||
for _ in range(n_poses):
|
||||
R_b2g, t_b2g = self._random_rt()
|
||||
B = self._to_T(R_b2g, t_b2g)
|
||||
# Consistent synthetic data: A_i @ X == Z @ B_i => A_i = Z @ B_i @ inv(X)
|
||||
A = Z @ B @ np.linalg.inv(X)
|
||||
|
||||
Rs_world2cam.append(A[:3, :3])
|
||||
# NOTE: unlike calibrateHandEye, calibrateRobotWorldHandEye's Shah/Li
|
||||
# solvers require translations as explicit (3,1) column vectors, not
|
||||
# flat (3,) arrays -- passing (3,) raises a gemm shape-assertion error.
|
||||
ts_world2cam.append(A[:3, 3].reshape(3, 1))
|
||||
Rs_base2gripper.append(R_b2g)
|
||||
ts_base2gripper.append(t_b2g.reshape(3, 1))
|
||||
|
||||
R_base2world, t_base2world, R_gripper2cam, t_gripper2cam = cv.calibrateRobotWorldHandEye(
|
||||
Rs_world2cam, ts_world2cam, Rs_base2gripper, ts_base2gripper)
|
||||
|
||||
self.assertLess(cv.norm(R_base2world - R_base2world_gt, cv.NORM_L1), 1e-3)
|
||||
self.assertLess(cv.norm(t_base2world.flatten() - t_base2world_gt, cv.NORM_L1), 1e-3)
|
||||
self.assertLess(cv.norm(R_gripper2cam - R_gripper2cam_gt, cv.NORM_L1), 1e-3)
|
||||
self.assertLess(cv.norm(t_gripper2cam.flatten() - t_gripper2cam_gt, cv.NORM_L1), 1e-3)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
|
||||
@@ -1335,6 +1335,16 @@ private:
|
||||
CV_EXPORTS_W void printCudaDeviceInfo(int device);
|
||||
CV_EXPORTS_W void printShortCudaDeviceInfo(int device);
|
||||
|
||||
/** @brief Returns the MatAllocator that backs cv::UMat with CUDA device memory.
|
||||
|
||||
Bind it to a UMat before allocation to place the buffer on the current CUDA device:
|
||||
@code
|
||||
UMat u; u.allocator = cv::cuda::getCudaAllocator(); u.create(rows, cols, CV_32F);
|
||||
@endcode
|
||||
Returns NULL when OpenCV is built without CUDA. Mirrors cv::ocl::getOpenCLAllocator().
|
||||
*/
|
||||
CV_EXPORTS MatAllocator* getCudaAllocator();
|
||||
|
||||
//! @} cudacore_init
|
||||
|
||||
}} // namespace cv { namespace cuda {
|
||||
|
||||
@@ -521,10 +521,11 @@ Cv64suf;
|
||||
CV_16U - 2 bytes
|
||||
...
|
||||
*/
|
||||
// '& 15' == '- 16' for depth in [16,31] here, but avoids a spurious MSVC C4293 on this ternary.
|
||||
#define CV_ELEM_SIZE1(type) \
|
||||
((int)((CV_MAT_DEPTH(type) < 16 \
|
||||
? (0x1114881228442211ULL >> (CV_MAT_DEPTH(type) * 4)) \
|
||||
: (0x0000000000000001ULL >> ((CV_MAT_DEPTH(type) - 16) * 4))) & 15))
|
||||
? (0x1114881228442211ULL >> ((CV_MAT_DEPTH(type) & 15) * 4)) \
|
||||
: (0x0000000000000001ULL >> ((CV_MAT_DEPTH(type) & 15) * 4))) & 15))
|
||||
|
||||
#define CV_ELEM_SIZE(type) (CV_MAT_CN(type)*CV_ELEM_SIZE1(type))
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -472,6 +472,244 @@ private:
|
||||
Buffer texCoord_;
|
||||
};
|
||||
|
||||
/** @brief Struct used to describe a vertex array attribute.
|
||||
*/
|
||||
struct CV_EXPORTS Attribute
|
||||
{
|
||||
/** @brief The target defines how you intend to use the buffer object.
|
||||
*/
|
||||
enum Type
|
||||
{
|
||||
BYTE = 0x1400, //!< 8-bit integer.
|
||||
UNSIGNED_BYTE = 0x1401, //!< 8-bit unsigned integer.
|
||||
SHORT = 0x1402, //!< 16-bit integer.
|
||||
UNSIGNED_SHORT = 0x1403, //!< 16-bit unsigned integer.
|
||||
INT = 0x1404, //!< 32-bit integer.
|
||||
UNSIGNED_INT = 0x1405, //!< 32-bit unsigned integer.
|
||||
FLOAT = 0x1406, //!< 32-bit floating point.
|
||||
};
|
||||
|
||||
//! The buffer this attribute takes data from.
|
||||
Buffer buffer_;
|
||||
//! Number of bytes between each element described by this attribute.
|
||||
size_t stride_;
|
||||
//! Position of the first element in the buffer in bytes.
|
||||
size_t offset_;
|
||||
//! Number of components of the type (eg.: float = 1 component, vec3 = 3 components).
|
||||
int size_;
|
||||
//! Type of the vertex attribute. See cv::ogl::Attribute::Type.
|
||||
Type type_;
|
||||
//! Is the vertex attribute an integer?
|
||||
bool integer_;
|
||||
//! If the vertex attribute isn't an integer, should it be normalized?
|
||||
bool normalized_;
|
||||
//! Location of the attribute in the program. See cv::ogl::Program::getAttributeLocation.
|
||||
unsigned int shader_loc_;
|
||||
};
|
||||
|
||||
/** @brief Smart pointer for OpenGL vertex arrays with reference counting.
|
||||
*/
|
||||
class CV_EXPORTS VertexArray
|
||||
{
|
||||
public:
|
||||
/** @brief The constructors.
|
||||
|
||||
Creates empty ogl::VertexArray object, creates ogl::VertexArray object from a list of attributes.
|
||||
*/
|
||||
VertexArray();
|
||||
|
||||
/** @overload
|
||||
@param attributes List of attributes.
|
||||
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
VertexArray(std::initializer_list<Attribute> attributes, bool autoRelease = false);
|
||||
|
||||
/** @brief Creates ogl::VertexArray object from a list of attributes.
|
||||
|
||||
@param attributes List of attributes.
|
||||
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
void create(std::initializer_list<Attribute> attributes, bool autoRelease = false);
|
||||
|
||||
/** @brief Decrements the reference counter and destroys the vertex array object if needed.
|
||||
|
||||
The function will call setAutoRelease(true) .
|
||||
*/
|
||||
void release();
|
||||
|
||||
/** @brief Sets auto release mode.
|
||||
|
||||
The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was
|
||||
bound to a window it could be released at any time (user can close a window). If object's destructor
|
||||
is called after destruction of the context it will cause an error. Thus ogl::VertexArray doesn't destroy
|
||||
OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context).
|
||||
This function can force ogl::VertexArray destructor to destroy OpenGL object.
|
||||
@param flag Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
void setAutoRelease(bool flag);
|
||||
|
||||
/** @brief Binds OpenGL vertex array.
|
||||
*/
|
||||
void bind() const;
|
||||
|
||||
/** @brief Unbind any vertex array.
|
||||
*/
|
||||
static void unbind();
|
||||
|
||||
//! get OpenGL object id
|
||||
unsigned int vaId() const;
|
||||
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
Ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
/** @brief Smart pointer for OpenGL shader with reference counting.
|
||||
*/
|
||||
class CV_EXPORTS Shader
|
||||
{
|
||||
public:
|
||||
/** @brief The target defines how you intend to use the buffer object.
|
||||
*/
|
||||
enum ShaderType
|
||||
{
|
||||
FRAGMENT = 0x8B30, //!< The shader will be a fragment shader
|
||||
VERTEX = 0x8B31, //!< The shader will be a vertex shader
|
||||
};
|
||||
|
||||
/** @brief The constructors.
|
||||
|
||||
Creates empty ogl::Shader object, creates ogl::Shader object from source code.
|
||||
*/
|
||||
Shader();
|
||||
|
||||
/** @overload
|
||||
@param src Shader source code.
|
||||
@param type Shader type. See cv::ogl::Shader::ShaderType.
|
||||
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
Shader(const char* src, ShaderType type, bool autoRelease = false);
|
||||
|
||||
/** @brief Creates ogl::Shader object from source code.
|
||||
|
||||
@param src Shader source code.
|
||||
@param type Shader type. See cv::ogl::Shader::ShaderType.
|
||||
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
void create(const char* src, ShaderType type, bool autoRelease = false);
|
||||
|
||||
/** @brief Decrements the reference counter and destroys the shader object if needed.
|
||||
|
||||
The function will call setAutoRelease(true) .
|
||||
*/
|
||||
void release();
|
||||
|
||||
/** @brief Sets auto release mode.
|
||||
|
||||
The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was
|
||||
bound to a window it could be released at any time (user can close a window). If object's destructor
|
||||
is called after destruction of the context it will cause an error. Thus ogl::Shader doesn't destroy
|
||||
OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context).
|
||||
This function can force ogl::Shader destructor to destroy OpenGL object.
|
||||
@param flag Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
void setAutoRelease(bool flag);
|
||||
|
||||
ShaderType type() const;
|
||||
|
||||
//! get OpenGL object id
|
||||
unsigned int shaderId() const;
|
||||
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
Ptr<Impl> impl_;
|
||||
ShaderType type_;
|
||||
};
|
||||
|
||||
/** @brief Smart pointer for OpenGL program with reference counting.
|
||||
*/
|
||||
class CV_EXPORTS Program
|
||||
{
|
||||
public:
|
||||
/** @brief The constructors.
|
||||
|
||||
Creates empty ogl::Program object, creates ogl::Program object from a group of shader objects.
|
||||
*/
|
||||
Program();
|
||||
|
||||
/** @overload
|
||||
@param vert Vertex shader. See cv::ogl::Shader.
|
||||
@param frag Fragment shader. See cv::ogl::Shader.
|
||||
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
Program(const Shader& vert, const Shader& frag, bool autoRelease = false);
|
||||
|
||||
/** @brief Creates ogl::Program object from a group of shader objects.
|
||||
|
||||
@param vert Vertex shader. See cv::ogl::Shader.
|
||||
@param frag Fragment shader. See cv::ogl::Shader.
|
||||
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
void create(const Shader& vert, const Shader& frag, bool autoRelease = false);
|
||||
|
||||
/** @brief Decrements the reference counter and destroys the program object if needed.
|
||||
|
||||
The function will call setAutoRelease(true) .
|
||||
*/
|
||||
void release();
|
||||
|
||||
/** @brief Sets auto release mode.
|
||||
|
||||
The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was
|
||||
bound to a window it could be released at any time (user can close a window). If object's destructor
|
||||
is called after destruction of the context it will cause an error. Thus ogl::Program doesn't destroy
|
||||
OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context).
|
||||
This function can force ogl::Program destructor to destroy OpenGL object.
|
||||
@param flag Auto release mode (if true, release will be called in object's destructor).
|
||||
*/
|
||||
void setAutoRelease(bool flag);
|
||||
|
||||
/** @brief Binds OpenGL shader program.
|
||||
*/
|
||||
void bind() const;
|
||||
|
||||
/** @brief Unbind any shader program.
|
||||
*/
|
||||
static void unbind();
|
||||
|
||||
/** @brief Returns the shader location of an attribute from its name.
|
||||
@param name Attribute name.
|
||||
*/
|
||||
int getAttributeLocation(const char* name) const;
|
||||
|
||||
/** @brief Returns the shader location of an uniform from its name.
|
||||
@param name Uniform name.
|
||||
*/
|
||||
int getUniformLocation(const char* name) const;
|
||||
|
||||
/** @brief Sets a uniform vector value.
|
||||
@param location Uniform location.
|
||||
@param vec Vector value.
|
||||
*/
|
||||
static void setUniformVec3(int location, Vec3f vec);
|
||||
|
||||
/** @brief Sets a uniform matrix value.
|
||||
@param location Uniform location.
|
||||
@param mat Matrix value.
|
||||
*/
|
||||
static void setUniformMat4x4(int location, Matx44f mat);
|
||||
|
||||
//! get OpenGL object id
|
||||
unsigned int programId() const;
|
||||
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
Ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
/////////////////// Render Functions ///////////////////
|
||||
|
||||
//! render mode
|
||||
@@ -488,6 +726,18 @@ enum RenderModes {
|
||||
POLYGON = 0x0009
|
||||
};
|
||||
|
||||
//! index type
|
||||
enum IndexType {
|
||||
UNSIGNED_BYTE = 0x1401,
|
||||
UNSIGNED_SHORT = 0x1403,
|
||||
UNSIGNED_INT = 0x1405,
|
||||
};
|
||||
|
||||
//! capability
|
||||
enum Capability {
|
||||
DEPTH_TEST = 0x0B71,
|
||||
};
|
||||
|
||||
/** @brief Render OpenGL texture or primitives.
|
||||
@param tex Texture to draw.
|
||||
@param wndRect Region of window, where to draw a texture (normalized coordinates).
|
||||
@@ -512,6 +762,41 @@ CV_EXPORTS void render(const Arrays& arr, int mode = POINTS, Scalar color = Scal
|
||||
*/
|
||||
CV_EXPORTS void render(const Arrays& arr, InputArray indices, int mode = POINTS, Scalar color = Scalar::all(255));
|
||||
|
||||
/** @brief Clears the current target to a single color.
|
||||
@param color New color.
|
||||
*/
|
||||
CV_EXPORTS void clearColor(Scalar color = Scalar::all(0));
|
||||
|
||||
/** @brief Clears the current target depth to a single value.
|
||||
@param depth New depth.
|
||||
*/
|
||||
CV_EXPORTS void clearDepth(float depth);
|
||||
|
||||
/** @brief Renders primitives from array data.
|
||||
@param first Index of the first vertex to draw.
|
||||
@param count Number of vertices to draw.
|
||||
@param mode Rendering mode. See cv::ogl::RenderModes.
|
||||
*/
|
||||
CV_EXPORTS void drawArrays(int first, int count, int mode);
|
||||
|
||||
/** @brief Renders primitives from array data with index buffer set.
|
||||
@param first Byte offset of the first index in the bound element buffer.
|
||||
@param count Number of indices to draw.
|
||||
@param type Index type. See cv::ogl::IndexType.
|
||||
@param mode Rendering mode. See cv::ogl::RenderModes.
|
||||
*/
|
||||
CV_EXPORTS void drawElements(int first, int count, int type, int mode);
|
||||
|
||||
/** @brief Enable server-side GL capabilities.
|
||||
@param cap The capability to enable.
|
||||
*/
|
||||
CV_EXPORTS void enable(int cap);
|
||||
|
||||
/** @brief Disable server-side GL capabilities.
|
||||
@param cap The capability to disable.
|
||||
*/
|
||||
CV_EXPORTS void disable(int cap);
|
||||
|
||||
/////////////////// CL-GL Interoperability Functions ///////////////////
|
||||
|
||||
namespace ocl {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
// CUDA-backed MatAllocator for UMat (Phase P1): synchronous host<->device transfers.
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
namespace cv { namespace cuda {
|
||||
|
||||
#ifndef HAVE_CUDA
|
||||
|
||||
MatAllocator* getCudaAllocator()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
class CudaUMatAllocator CV_FINAL : public MatAllocator
|
||||
{
|
||||
public:
|
||||
UMatData* allocate(int dims, const int* sizes, int type, void* data0,
|
||||
size_t* step, AccessFlag /*flags*/, UMatUsageFlags /*usageFlags*/) const CV_OVERRIDE
|
||||
{
|
||||
const size_t elemSize = CV_ELEM_SIZE(type);
|
||||
size_t total = elemSize;
|
||||
for (int i = 0; i < dims; i++)
|
||||
total *= (size_t)sizes[i];
|
||||
|
||||
if (step)
|
||||
{
|
||||
step[dims - 1] = elemSize;
|
||||
for (int i = dims - 2; i >= 0; i--)
|
||||
step[i] = step[i + 1] * (size_t)sizes[i + 1];
|
||||
}
|
||||
|
||||
UMatData* u = new UMatData(this);
|
||||
u->size = total;
|
||||
|
||||
if (total > 0)
|
||||
cudaSafeCall(cudaMalloc(&u->handle, total));
|
||||
|
||||
if (data0)
|
||||
{
|
||||
u->data = u->origdata = static_cast<uchar*>(data0);
|
||||
u->flags |= UMatData::USER_ALLOCATED;
|
||||
u->markDeviceCopyObsolete(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
u->markHostCopyObsolete(true);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
bool allocate(UMatData* u, AccessFlag /*accessFlags*/, UMatUsageFlags /*usageFlags*/) const CV_OVERRIDE
|
||||
{
|
||||
if (!u)
|
||||
return false;
|
||||
if (!u->handle && u->size > 0)
|
||||
{
|
||||
cudaSafeCall(cudaMalloc(&u->handle, u->size));
|
||||
if (u->data)
|
||||
u->markDeviceCopyObsolete(true);
|
||||
else
|
||||
u->markHostCopyObsolete(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void deallocate(UMatData* u) const CV_OVERRIDE
|
||||
{
|
||||
if (!u)
|
||||
return;
|
||||
if (u->handle)
|
||||
cudaSafeCall(cudaFree(u->handle));
|
||||
if (u->data && !(u->flags & UMatData::USER_ALLOCATED))
|
||||
fastFree(u->data);
|
||||
delete u;
|
||||
}
|
||||
|
||||
void map(UMatData* u, AccessFlag accessFlags) const CV_OVERRIDE
|
||||
{
|
||||
if (!u)
|
||||
return;
|
||||
if (!u->data && u->size > 0)
|
||||
u->data = u->origdata = static_cast<uchar*>(fastMalloc(u->size));
|
||||
if (u->hostCopyObsolete() && u->handle && u->data)
|
||||
{
|
||||
cudaSafeCall(cudaMemcpy(u->data, u->handle, u->size, cudaMemcpyDeviceToHost));
|
||||
u->markHostCopyObsolete(false);
|
||||
}
|
||||
if (!!(accessFlags & ACCESS_WRITE))
|
||||
u->markDeviceCopyObsolete(true);
|
||||
}
|
||||
|
||||
void unmap(UMatData* u) const CV_OVERRIDE
|
||||
{
|
||||
if (!u)
|
||||
return;
|
||||
if (u->deviceCopyObsolete() && u->handle && u->data)
|
||||
{
|
||||
cudaSafeCall(cudaMemcpy(u->handle, u->data, u->size, cudaMemcpyHostToDevice));
|
||||
u->markDeviceCopyObsolete(false);
|
||||
}
|
||||
}
|
||||
|
||||
void download(UMatData* u, void* dstptr, int dims, const size_t sz[],
|
||||
const size_t srcofs[], const size_t srcstep[],
|
||||
const size_t dststep[]) const CV_OVERRIDE
|
||||
{
|
||||
if (!u || !u->handle || !dstptr)
|
||||
return;
|
||||
copyPlanes((uchar*)u->handle, srcofs, srcstep, (uchar*)dstptr, 0, dststep,
|
||||
dims, sz, cudaMemcpyDeviceToHost);
|
||||
}
|
||||
|
||||
void upload(UMatData* u, const void* srcptr, int dims, const size_t sz[],
|
||||
const size_t dstofs[], const size_t dststep[],
|
||||
const size_t srcstep[]) const CV_OVERRIDE
|
||||
{
|
||||
if (!u || !srcptr)
|
||||
return;
|
||||
if (!u->handle && u->size > 0)
|
||||
cudaSafeCall(cudaMalloc(&u->handle, u->size));
|
||||
copyPlanes((uchar*)srcptr, NULL, srcstep, (uchar*)u->handle, dstofs, dststep,
|
||||
dims, sz, cudaMemcpyHostToDevice);
|
||||
u->markHostCopyObsolete(true);
|
||||
u->markDeviceCopyObsolete(false);
|
||||
}
|
||||
|
||||
// Same-world D2D only; UMat::copyTo calls copy() solely when both share this allocator.
|
||||
void copy(UMatData* src, UMatData* dst, int dims, const size_t sz[],
|
||||
const size_t srcofs[], const size_t srcstep[],
|
||||
const size_t dstofs[], const size_t dststep[], bool /*sync*/) const CV_OVERRIDE
|
||||
{
|
||||
if (!src || !dst || !src->handle || !dst->handle)
|
||||
return;
|
||||
copyPlanes((uchar*)src->handle, srcofs, srcstep, (uchar*)dst->handle, dstofs, dststep,
|
||||
dims, sz, cudaMemcpyDeviceToDevice);
|
||||
dst->markHostCopyObsolete(true);
|
||||
dst->markDeviceCopyObsolete(false);
|
||||
}
|
||||
|
||||
private:
|
||||
// sz/steps follow the MatAllocator convention: the last dim carries element bytes.
|
||||
static void copyPlanes(uchar* srcbase, const size_t srcofs[], const size_t srcstep[],
|
||||
uchar* dstbase, const size_t dstofs[], const size_t dststep[],
|
||||
int dims, const size_t sz[], cudaMemcpyKind kind)
|
||||
{
|
||||
int isz[CV_MAX_DIM];
|
||||
uchar* srcptr = srcbase;
|
||||
uchar* dstptr = dstbase;
|
||||
for (int i = 0; i < dims; i++)
|
||||
{
|
||||
CV_Assert(sz[i] <= (size_t)INT_MAX);
|
||||
if (sz[i] == 0)
|
||||
return;
|
||||
if (srcofs)
|
||||
srcptr += srcofs[i] * (i <= dims - 2 ? srcstep[i] : 1);
|
||||
if (dstofs)
|
||||
dstptr += dstofs[i] * (i <= dims - 2 ? dststep[i] : 1);
|
||||
isz[i] = (int)sz[i];
|
||||
}
|
||||
|
||||
Mat src(dims, isz, CV_8U, srcptr, srcstep);
|
||||
Mat dst(dims, isz, CV_8U, dstptr, dststep);
|
||||
|
||||
const Mat* arrays[] = { &src, &dst };
|
||||
uchar* ptrs[2];
|
||||
NAryMatIterator it(arrays, ptrs, 2);
|
||||
const size_t planesz = it.size;
|
||||
for (size_t j = 0; j < it.nplanes; j++, ++it)
|
||||
cudaSafeCall(cudaMemcpy(ptrs[1], ptrs[0], planesz, kind));
|
||||
}
|
||||
};
|
||||
|
||||
MatAllocator* getCudaAllocator()
|
||||
{
|
||||
CV_SINGLETON_LAZY_INIT(CudaUMatAllocator, new CudaUMatAllocator())
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}} // namespace cv { namespace cuda {
|
||||
@@ -379,6 +379,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste
|
||||
else if (typeid(fptype) == typeid(double))
|
||||
OCV_LAPACK_FUNC(dgels)(mode, &_m, &_n, &_k, (double*)tmpA, &ldtmpA, (double*)b, &_m, (double*)&work1, &lwork, _info);
|
||||
|
||||
CV_ANNOTATE_MEMORY_IS_INITIALIZED(info, sizeof(int));
|
||||
if (*info < 0)
|
||||
return CV_HAL_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
@@ -403,6 +404,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste
|
||||
else if (typeid(fptype) == typeid(double))
|
||||
OCV_LAPACK_FUNC(dgels)(mode, &_m, &_n, &_k, (double*)tmpA, &ldtmpA, (double*)tmpB, &_m, (double*)&work1, &lwork, _info);
|
||||
|
||||
CV_ANNOTATE_MEMORY_IS_INITIALIZED(info, sizeof(int));
|
||||
if (*info < 0)
|
||||
return CV_HAL_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
@@ -425,6 +427,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste
|
||||
else if (typeid(fptype) == typeid(double))
|
||||
dgeqrf_(&_m, &_n, (double*)tmpA, &ldtmpA, (double*)dst, (double*)&work1, &lwork, _info);
|
||||
|
||||
CV_ANNOTATE_MEMORY_IS_INITIALIZED(info, sizeof(int));
|
||||
if (*info < 0)
|
||||
return CV_HAL_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
|
||||
+632
-2
@@ -316,6 +316,8 @@ public:
|
||||
void copyFrom(GLsizeiptr size, const GLvoid* data);
|
||||
void copyTo(GLsizeiptr size, GLvoid* data) const;
|
||||
|
||||
void resize(GLsizeiptr size, GLenum target);
|
||||
|
||||
void* mapHost(GLenum access);
|
||||
void unmapHost();
|
||||
|
||||
@@ -407,6 +409,19 @@ void cv::ogl::Buffer::Impl::copyFrom(GLsizeiptr size, const GLvoid* data)
|
||||
CV_CheckGlError();
|
||||
}
|
||||
|
||||
void cv::ogl::Buffer::Impl::resize(GLsizeiptr size, GLenum target)
|
||||
{
|
||||
gl::BindBuffer(target, bufId_);
|
||||
CV_CheckGlError();
|
||||
|
||||
// keep the same buffer id so bound vertex arrays stay valid
|
||||
gl::BufferData(target, size, 0, gl::DYNAMIC_DRAW);
|
||||
CV_CheckGlError();
|
||||
|
||||
gl::BindBuffer(target, 0);
|
||||
CV_CheckGlError();
|
||||
}
|
||||
|
||||
void cv::ogl::Buffer::Impl::copyTo(GLsizeiptr size, GLvoid* data) const
|
||||
{
|
||||
gl::BindBuffer(gl::COPY_READ_BUFFER, bufId_);
|
||||
@@ -547,8 +562,15 @@ void cv::ogl::Buffer::create(int arows, int acols, int atype, Target target, boo
|
||||
#else
|
||||
if (rows_ != arows || cols_ != acols || type_ != atype)
|
||||
{
|
||||
const GLsizeiptr asize = arows * acols * CV_ELEM_SIZE(atype);
|
||||
impl_.reset(new Impl(asize, 0, target, autoRelease));
|
||||
const GLsizeiptr asize = (GLsizeiptr)arows * acols * CV_ELEM_SIZE(atype);
|
||||
// resize in place when sole owner (keeps the GL id); else allocate fresh (copy-on-write)
|
||||
if (impl_->bufId() != 0 && impl_.use_count() == 1)
|
||||
{
|
||||
impl_->resize(asize, target);
|
||||
impl_->setAutoRelease(autoRelease);
|
||||
}
|
||||
else
|
||||
impl_.reset(new Impl(asize, 0, target, autoRelease));
|
||||
rows_ = arows;
|
||||
cols_ = acols;
|
||||
type_ = atype;
|
||||
@@ -1411,6 +1433,551 @@ void cv::ogl::Arrays::bind() const
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// ogl::VertexArray
|
||||
|
||||
#ifndef HAVE_OPENGL
|
||||
|
||||
class cv::ogl::VertexArray::Impl
|
||||
{
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class cv::ogl::VertexArray::Impl
|
||||
{
|
||||
public:
|
||||
static const Ptr<Impl>& empty();
|
||||
|
||||
Impl(GLuint vaId, bool autoRelease);
|
||||
Impl(std::initializer_list<Attribute> attributes, bool autoRelease);
|
||||
~Impl();
|
||||
|
||||
void bind();
|
||||
|
||||
void setAutoRelease(bool flag) { autoRelease_ = flag; }
|
||||
|
||||
GLuint vaId() const { return vaId_; }
|
||||
|
||||
private:
|
||||
Impl();
|
||||
|
||||
GLuint vaId_;
|
||||
bool autoRelease_;
|
||||
};
|
||||
|
||||
const Ptr<cv::ogl::VertexArray::Impl>& cv::ogl::VertexArray::Impl::empty()
|
||||
{
|
||||
static Ptr<Impl> p(new Impl);
|
||||
return p;
|
||||
}
|
||||
|
||||
cv::ogl::VertexArray::Impl::Impl() : vaId_(0), autoRelease_(false)
|
||||
{
|
||||
}
|
||||
|
||||
cv::ogl::VertexArray::Impl::Impl(GLuint vaId, bool autoRelease) : vaId_(vaId), autoRelease_(autoRelease)
|
||||
{
|
||||
CV_Assert( gl::IsVertexArray(vaId) == gl::TRUE_ );
|
||||
}
|
||||
|
||||
cv::ogl::VertexArray::Impl::Impl(std::initializer_list<Attribute> attributes, bool autoRelease) : vaId_(0), autoRelease_(autoRelease)
|
||||
{
|
||||
gl::GenVertexArrays(1, &vaId_);
|
||||
CV_CheckGlError();
|
||||
|
||||
CV_Assert( vaId_ != 0 );
|
||||
gl::BindVertexArray(vaId_);
|
||||
CV_CheckGlError();
|
||||
|
||||
for (auto& attribute : attributes)
|
||||
{
|
||||
attribute.buffer_.bind(Buffer::ARRAY_BUFFER);
|
||||
if (attribute.integer_)
|
||||
{
|
||||
gl::VertexAttribIPointer(
|
||||
attribute.shader_loc_,
|
||||
attribute.size_,
|
||||
attribute.type_,
|
||||
static_cast<GLsizei>(attribute.stride_),
|
||||
reinterpret_cast<const void*>(attribute.offset_)
|
||||
);
|
||||
CV_CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
gl::VertexAttribPointer(
|
||||
attribute.shader_loc_,
|
||||
attribute.size_,
|
||||
attribute.type_,
|
||||
attribute.normalized_,
|
||||
static_cast<GLsizei>(attribute.stride_),
|
||||
reinterpret_cast<const void*>(attribute.offset_)
|
||||
);
|
||||
CV_CheckGlError();
|
||||
}
|
||||
|
||||
gl::EnableVertexAttribArray(attribute.shader_loc_);
|
||||
CV_CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
cv::ogl::VertexArray::Impl::~Impl()
|
||||
{
|
||||
if (autoRelease_ && vaId_)
|
||||
gl::DeleteVertexArrays(1, &vaId_);
|
||||
}
|
||||
|
||||
void cv::ogl::VertexArray::Impl::bind()
|
||||
{
|
||||
gl::BindVertexArray(vaId_);
|
||||
CV_CheckGlError();
|
||||
}
|
||||
|
||||
#endif // HAVE_OPENGL
|
||||
|
||||
cv::ogl::VertexArray::VertexArray()
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_ = Impl::empty();
|
||||
#endif
|
||||
}
|
||||
|
||||
cv::ogl::VertexArray::VertexArray(std::initializer_list<Attribute> attributes, bool autoRelease)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(attributes); CV_UNUSED(autoRelease);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_.reset(new Impl(attributes, autoRelease));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::VertexArray::create(std::initializer_list<Attribute> attributes, bool autoRelease)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(attributes); CV_UNUSED(autoRelease);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_.reset(new Impl(attributes, autoRelease));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::VertexArray::release()
|
||||
{
|
||||
#ifdef HAVE_OPENGL
|
||||
if (impl_)
|
||||
impl_->setAutoRelease(true);
|
||||
impl_ = Impl::empty();
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::VertexArray::setAutoRelease(bool flag)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(flag);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_->setAutoRelease(flag);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::VertexArray::bind() const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_->bind();
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::VertexArray::unbind()
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::BindVertexArray(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int cv::ogl::VertexArray::vaId() const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
return impl_->vaId();
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// ogl::Shader
|
||||
|
||||
#ifndef HAVE_OPENGL
|
||||
|
||||
class cv::ogl::Shader::Impl
|
||||
{
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class cv::ogl::Shader::Impl
|
||||
{
|
||||
public:
|
||||
static const Ptr<Impl>& empty();
|
||||
|
||||
Impl(GLuint shaderId, bool autoRelease);
|
||||
Impl(const char* src, GLenum type, bool autoRelease);
|
||||
~Impl();
|
||||
|
||||
void setAutoRelease(bool flag) { autoRelease_ = flag; }
|
||||
|
||||
GLuint shaderId() const { return shaderId_; }
|
||||
|
||||
private:
|
||||
Impl();
|
||||
|
||||
GLuint shaderId_;
|
||||
bool autoRelease_;
|
||||
};
|
||||
|
||||
const Ptr<cv::ogl::Shader::Impl>& cv::ogl::Shader::Impl::empty()
|
||||
{
|
||||
static Ptr<Impl> p(new Impl);
|
||||
return p;
|
||||
}
|
||||
|
||||
cv::ogl::Shader::Impl::Impl() : shaderId_(0), autoRelease_(false)
|
||||
{
|
||||
}
|
||||
|
||||
cv::ogl::Shader::Impl::Impl(GLuint shaderId, bool autoRelease) : shaderId_(shaderId), autoRelease_(autoRelease)
|
||||
{
|
||||
CV_Assert( gl::IsShader(shaderId) == gl::TRUE_ );
|
||||
}
|
||||
|
||||
cv::ogl::Shader::Impl::Impl(const char* src, GLenum type, bool autoRelease) : shaderId_(0), autoRelease_(autoRelease)
|
||||
{
|
||||
shaderId_ = gl::CreateShader(type);
|
||||
CV_CheckGlError();
|
||||
|
||||
CV_Assert( shaderId_ != 0 );
|
||||
|
||||
GLint status;
|
||||
gl::ShaderSource(shaderId_, 1, &src, 0);
|
||||
gl::CompileShader(shaderId_);
|
||||
gl::GetShaderiv(shaderId_, gl::COMPILE_STATUS, &status);
|
||||
if (!status) {
|
||||
String info_log;
|
||||
gl::GetShaderiv(shaderId_, gl::INFO_LOG_LENGTH, &status);
|
||||
info_log.resize(status);
|
||||
gl::GetShaderInfoLog(shaderId_, GLsizei(info_log.size()) + 1, nullptr, &info_log[0]);
|
||||
CV_Error(Error::OpenGlApiCallError, info_log);
|
||||
}
|
||||
|
||||
CV_CheckGlError();
|
||||
}
|
||||
|
||||
cv::ogl::Shader::Impl::~Impl()
|
||||
{
|
||||
if (autoRelease_ && shaderId_)
|
||||
gl::DeleteShader(shaderId_);
|
||||
}
|
||||
|
||||
#endif // HAVE_OPENGL
|
||||
|
||||
cv::ogl::Shader::Shader() : type_(ShaderType::VERTEX)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_ = Impl::empty();
|
||||
#endif
|
||||
}
|
||||
|
||||
cv::ogl::Shader::Shader(const char* src, ShaderType type, bool autoRelease) : type_(type)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(src); CV_UNUSED(type); CV_UNUSED(autoRelease);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_.reset(new Impl(src, type, autoRelease));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Shader::create(const char* src, ShaderType type, bool autoRelease)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(src); CV_UNUSED(type); CV_UNUSED(autoRelease);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_.reset(new Impl(src, type, autoRelease));
|
||||
type_ = type;
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Shader::release()
|
||||
{
|
||||
#ifdef HAVE_OPENGL
|
||||
if (impl_)
|
||||
impl_->setAutoRelease(true);
|
||||
impl_ = Impl::empty();
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Shader::setAutoRelease(bool flag)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(flag);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_->setAutoRelease(flag);
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int cv::ogl::Shader::shaderId() const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
return impl_->shaderId();
|
||||
#endif
|
||||
}
|
||||
|
||||
cv::ogl::Shader::ShaderType cv::ogl::Shader::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// ogl::Program
|
||||
|
||||
#ifndef HAVE_OPENGL
|
||||
|
||||
class cv::ogl::Program::Impl
|
||||
{
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class cv::ogl::Program::Impl
|
||||
{
|
||||
public:
|
||||
static const Ptr<Impl>& empty();
|
||||
|
||||
Impl(GLuint programId, bool autoRelease);
|
||||
Impl(const Shader& vert, const Shader& frag, bool autoRelease);
|
||||
~Impl();
|
||||
|
||||
void bind();
|
||||
|
||||
int getAttributeLocation(const char* name) const;
|
||||
int getUniformLocation(const char* name) const;
|
||||
|
||||
void setAutoRelease(bool flag) { autoRelease_ = flag; }
|
||||
|
||||
GLuint programId() const { return programId_; }
|
||||
|
||||
private:
|
||||
Impl();
|
||||
|
||||
Shader vert_;
|
||||
Shader frag_;
|
||||
GLuint programId_;
|
||||
bool autoRelease_;
|
||||
};
|
||||
|
||||
const Ptr<cv::ogl::Program::Impl>& cv::ogl::Program::Impl::empty()
|
||||
{
|
||||
static Ptr<Impl> p(new Impl);
|
||||
return p;
|
||||
}
|
||||
|
||||
cv::ogl::Program::Impl::Impl() : programId_(0), autoRelease_(false)
|
||||
{
|
||||
}
|
||||
|
||||
cv::ogl::Program::Impl::Impl(GLuint programId, bool autoRelease) : programId_(programId), autoRelease_(autoRelease)
|
||||
{
|
||||
CV_Assert( gl::IsProgram(programId) == gl::TRUE_ );
|
||||
}
|
||||
|
||||
cv::ogl::Program::Impl::Impl(const Shader& vert, const Shader& frag, bool autoRelease) : programId_(0), autoRelease_(autoRelease)
|
||||
{
|
||||
programId_ = gl::CreateProgram();
|
||||
CV_CheckGlError();
|
||||
|
||||
CV_Assert( programId_ != 0 );
|
||||
|
||||
gl::AttachShader(programId_, vert.shaderId());
|
||||
CV_CheckGlError();
|
||||
|
||||
gl::AttachShader(programId_, frag.shaderId());
|
||||
CV_CheckGlError();
|
||||
|
||||
GLint status;
|
||||
gl::LinkProgram(programId_);
|
||||
gl::GetProgramiv(programId_, gl::LINK_STATUS, &status);
|
||||
if (!status) {
|
||||
String info_log;
|
||||
gl::GetProgramiv(programId_, gl::INFO_LOG_LENGTH, &status);
|
||||
info_log.resize(status);
|
||||
gl::GetProgramInfoLog(programId_, GLsizei(info_log.size()) + 1, nullptr, &info_log[0]);
|
||||
CV_Error(Error::OpenGlApiCallError, info_log);
|
||||
}
|
||||
|
||||
CV_CheckGlError();
|
||||
|
||||
vert_ = vert;
|
||||
frag_ = frag;
|
||||
}
|
||||
|
||||
cv::ogl::Program::Impl::~Impl()
|
||||
{
|
||||
if (autoRelease_ && programId_)
|
||||
gl::DeleteProgram(programId_);
|
||||
}
|
||||
|
||||
void cv::ogl::Program::Impl::bind()
|
||||
{
|
||||
gl::UseProgram(programId_);
|
||||
CV_CheckGlError();
|
||||
}
|
||||
|
||||
int cv::ogl::Program::Impl::getAttributeLocation(const char* name) const
|
||||
{
|
||||
// -1 if absent (e.g. optimized out); caller decides.
|
||||
int location = gl::GetAttribLocation(programId_, name);
|
||||
CV_CheckGlError();
|
||||
return location;
|
||||
}
|
||||
|
||||
int cv::ogl::Program::Impl::getUniformLocation(const char* name) const
|
||||
{
|
||||
// -1 if absent (e.g. optimized out); caller decides.
|
||||
int location = gl::GetUniformLocation(programId_, name);
|
||||
CV_CheckGlError();
|
||||
return location;
|
||||
}
|
||||
|
||||
#endif // HAVE_OPENGL
|
||||
|
||||
cv::ogl::Program::Program()
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_ = Impl::empty();
|
||||
#endif
|
||||
}
|
||||
|
||||
cv::ogl::Program::Program(const Shader& vert, const Shader& frag, bool autoRelease)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(vert); CV_UNUSED(frag); CV_UNUSED(autoRelease);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_.reset(new Impl(vert, frag, autoRelease));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::create(const Shader& vert, const Shader& frag, bool autoRelease)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(vert); CV_UNUSED(frag); CV_UNUSED(autoRelease);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_.reset(new Impl(vert, frag, autoRelease));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::release()
|
||||
{
|
||||
#ifdef HAVE_OPENGL
|
||||
if (impl_)
|
||||
impl_->setAutoRelease(true);
|
||||
impl_ = Impl::empty();
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::setAutoRelease(bool flag)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(flag);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_->setAutoRelease(flag);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::bind() const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
impl_->bind();
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::unbind()
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::UseProgram(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
int cv::ogl::Program::getAttributeLocation(const char* name) const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(name);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
return impl_->getAttributeLocation(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
int cv::ogl::Program::getUniformLocation(const char* name) const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(name);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
return impl_->getUniformLocation(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::setUniformVec3(int loc, Vec3f vec)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(loc); CV_UNUSED(vec);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::Uniform3f(loc, vec(0), vec(1), vec(2));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::Program::setUniformMat4x4(int loc, Matx44f mat)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(loc); CV_UNUSED(mat);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::UniformMatrix4fv(loc, 1, true, mat.val);
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int cv::ogl::Program::programId() const
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
throw_no_ogl();
|
||||
#else
|
||||
return impl_->programId();
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Rendering
|
||||
|
||||
@@ -1577,6 +2144,69 @@ void cv::ogl::render(const ogl::Arrays& arr, InputArray indices, int mode, Scala
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::clearColor(Scalar color)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(color);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::ClearColor(static_cast<GLfloat>(color[0] / 255.0), static_cast<GLfloat>(color[1] / 255.0),
|
||||
static_cast<GLfloat>(color[2] / 255.0), static_cast<GLfloat>(color[3] / 255.0));
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
#endif // HAVE_OPENGL
|
||||
}
|
||||
|
||||
void cv::ogl::clearDepth(float depth)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(depth);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::ClearDepth(depth);
|
||||
gl::Clear(gl::DEPTH_BUFFER_BIT);
|
||||
#endif // HAVE_OPENGL
|
||||
}
|
||||
|
||||
void cv::ogl::drawArrays(int first, int count, int mode)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(first); CV_UNUSED(count); CV_UNUSED(mode);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::DrawArrays(mode, first, count);
|
||||
#endif // HAVE_OPENGL
|
||||
}
|
||||
|
||||
void cv::ogl::drawElements(int first, int count, int type, int mode)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(first); CV_UNUSED(count); CV_UNUSED(type); CV_UNUSED(mode);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::DrawElements(mode, count, type, reinterpret_cast<const void*>(static_cast<size_t>(first)));
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::enable(int cap)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(cap);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::Enable(cap);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cv::ogl::disable(int cap)
|
||||
{
|
||||
#ifndef HAVE_OPENGL
|
||||
CV_UNUSED(cap);
|
||||
throw_no_ogl();
|
||||
#else
|
||||
gl::Disable(cap);
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// CL-GL Interoperability
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
|
||||
#include <opencv2/core/cuda.hpp>
|
||||
#include <opencv2/core/ocl.hpp>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static void skipIfNoCudaDevice()
|
||||
{
|
||||
if (cv::cuda::getCudaEnabledDeviceCount() <= 0)
|
||||
throw SkipTestException("No CUDA device available");
|
||||
}
|
||||
|
||||
static UMat makeCudaUMat()
|
||||
{
|
||||
UMat u;
|
||||
u.allocator = cv::cuda::getCudaAllocator();
|
||||
return u;
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, roundtrip_contiguous)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
ASSERT_TRUE(cv::cuda::getCudaAllocator() != NULL);
|
||||
|
||||
Mat src(64, 48, CV_32FC1);
|
||||
randu(src, 0, 1);
|
||||
|
||||
UMat u = makeCudaUMat();
|
||||
src.copyTo(u);
|
||||
ASSERT_TRUE(u.u != NULL && u.u->handle != NULL);
|
||||
|
||||
Mat dst;
|
||||
u.copyTo(dst);
|
||||
|
||||
EXPECT_EQ(src.size(), dst.size());
|
||||
EXPECT_EQ(src.type(), dst.type());
|
||||
EXPECT_EQ(0.0, cvtest::norm(src, dst, NORM_INF));
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, roundtrip_multichannel)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
|
||||
Mat src(37, 53, CV_8UC3);
|
||||
randu(src, 0, 255);
|
||||
|
||||
UMat u = makeCudaUMat();
|
||||
src.copyTo(u);
|
||||
|
||||
Mat dst;
|
||||
u.copyTo(dst);
|
||||
EXPECT_EQ(0.0, cvtest::norm(src, dst, NORM_INF));
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, roundtrip_roi_source)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
|
||||
Mat full(100, 100, CV_32FC1);
|
||||
randu(full, 0, 1);
|
||||
Mat roi = full(Rect(10, 12, 50, 40));
|
||||
ASSERT_FALSE(roi.isContinuous());
|
||||
|
||||
UMat u = makeCudaUMat();
|
||||
roi.copyTo(u);
|
||||
|
||||
Mat dst;
|
||||
u.copyTo(dst);
|
||||
EXPECT_EQ(roi.size(), dst.size());
|
||||
EXPECT_EQ(0.0, cvtest::norm(roi, dst, NORM_INF));
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, download_into_roi)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
|
||||
Mat src(40, 50, CV_32FC1);
|
||||
randu(src, 0, 1);
|
||||
|
||||
UMat u = makeCudaUMat();
|
||||
src.copyTo(u);
|
||||
|
||||
Mat canvas(80, 90, CV_32FC1, Scalar(0));
|
||||
Mat dstRoi = canvas(Rect(5, 7, 50, 40));
|
||||
ASSERT_FALSE(dstRoi.isContinuous());
|
||||
u.copyTo(dstRoi);
|
||||
|
||||
EXPECT_EQ(0.0, cvtest::norm(src, dstRoi, NORM_INF));
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, device_to_device_copy)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
|
||||
Mat src(70, 90, CV_32FC1);
|
||||
randu(src, 0, 1);
|
||||
|
||||
UMat a = makeCudaUMat();
|
||||
src.copyTo(a);
|
||||
|
||||
UMat b = makeCudaUMat();
|
||||
a.copyTo(b);
|
||||
|
||||
Mat dst;
|
||||
b.copyTo(dst);
|
||||
EXPECT_EQ(0.0, cvtest::norm(src, dst, NORM_INF));
|
||||
}
|
||||
|
||||
// Proves the allocator's handle is a real, kernel-usable CUDA buffer via GpuMat interop.
|
||||
TEST(Core_CudaUMat, kernel_interop_via_gpumat)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
|
||||
Mat src(32, 40, CV_32FC1, Scalar(1.0f));
|
||||
|
||||
UMat u = makeCudaUMat();
|
||||
src.copyTo(u);
|
||||
ASSERT_TRUE(u.u != NULL && u.u->handle != NULL);
|
||||
|
||||
cv::cuda::GpuMat g(u.rows, u.cols, u.type(), u.u->handle,
|
||||
(size_t)u.cols * CV_ELEM_SIZE(u.type()));
|
||||
|
||||
Mat viaGpu;
|
||||
g.download(viaGpu);
|
||||
EXPECT_EQ(0.0, cvtest::norm(src, viaGpu, NORM_INF));
|
||||
|
||||
g.setTo(Scalar(7.0f));
|
||||
u.u->markHostCopyObsolete(true); // device was mutated behind UMat's back
|
||||
Mat afterKernel;
|
||||
u.copyTo(afterKernel);
|
||||
EXPECT_EQ(0.0, cvtest::norm(Mat(src.size(), src.type(), Scalar(7.0f)), afterKernel, NORM_INF));
|
||||
}
|
||||
|
||||
// P1 gives a UMat storage on CUDA, not GPU execution. With OpenCL on, cv:: ops feed the
|
||||
// CUDA pointer to the driver as a cl_mem and segfault; the tests below force OpenCL off so
|
||||
// ops resolve through the host getMat() fallback, and pin why the OpenCL path is unusable.
|
||||
|
||||
struct NoOpenCL
|
||||
{
|
||||
bool prev;
|
||||
NoOpenCL() : prev(cv::ocl::useOpenCL()) { cv::ocl::setUseOpenCL(false); }
|
||||
~NoOpenCL() { cv::ocl::setUseOpenCL(prev); }
|
||||
};
|
||||
|
||||
// A CUDA UMat fails the OpenCL kernel path's isCLBuffer precondition (carries the CUDA allocator).
|
||||
TEST(Core_CudaUMat, is_not_opencl_buffer)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
UMat u = makeCudaUMat();
|
||||
Mat(8, 8, CV_32FC1, Scalar(1)).copyTo(u);
|
||||
ASSERT_TRUE(u.u != NULL);
|
||||
EXPECT_EQ(cv::cuda::getCudaAllocator(), u.u->currAllocator);
|
||||
#ifdef HAVE_OPENCL
|
||||
EXPECT_NE((MatAllocator*)cv::ocl::getOpenCLAllocator(), u.u->currAllocator);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, op_arithmetic_host_fallback)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
NoOpenCL guard;
|
||||
|
||||
Mat a(50, 40, CV_32FC1), b(50, 40, CV_32FC1);
|
||||
randu(a, 0, 10); randu(b, 0, 10);
|
||||
|
||||
UMat ua = makeCudaUMat(), ub = makeCudaUMat();
|
||||
a.copyTo(ua); b.copyTo(ub);
|
||||
|
||||
UMat usum = makeCudaUMat(), udiff = makeCudaUMat(), uprod = makeCudaUMat();
|
||||
cv::add(ua, ub, usum);
|
||||
cv::subtract(ua, ub, udiff);
|
||||
cv::multiply(ua, ub, uprod);
|
||||
|
||||
Mat sum, diff, prod;
|
||||
usum.copyTo(sum); udiff.copyTo(diff); uprod.copyTo(prod);
|
||||
EXPECT_LE(cvtest::norm(sum, a + b, NORM_INF), 1e-5);
|
||||
EXPECT_LE(cvtest::norm(diff, a - b, NORM_INF), 1e-5);
|
||||
EXPECT_LE(cvtest::norm(prod, a.mul(b), NORM_INF), 1e-4);
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, op_convertTo_host_fallback)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
NoOpenCL guard;
|
||||
|
||||
Mat a(30, 45, CV_32FC1);
|
||||
randu(a, 0, 100);
|
||||
|
||||
UMat ua = makeCudaUMat();
|
||||
a.copyTo(ua);
|
||||
UMat u8 = makeCudaUMat();
|
||||
ua.convertTo(u8, CV_8U, 2.0, 1.0);
|
||||
|
||||
Mat got, expected;
|
||||
u8.copyTo(got);
|
||||
a.convertTo(expected, CV_8U, 2.0, 1.0);
|
||||
EXPECT_EQ(0.0, cvtest::norm(got, expected, NORM_INF));
|
||||
}
|
||||
|
||||
TEST(Core_CudaUMat, op_addWeighted_host_fallback)
|
||||
{
|
||||
skipIfNoCudaDevice();
|
||||
NoOpenCL guard;
|
||||
|
||||
Mat a(40, 40, CV_32FC1), b(40, 40, CV_32FC1);
|
||||
randu(a, 0, 5); randu(b, 0, 5);
|
||||
|
||||
UMat ua = makeCudaUMat(), ub = makeCudaUMat();
|
||||
a.copyTo(ua); b.copyTo(ub);
|
||||
|
||||
UMat ur = makeCudaUMat();
|
||||
cv::addWeighted(ua, 2.0, ub, 1.0, 0.0, ur);
|
||||
|
||||
Mat got, expected;
|
||||
ur.copyTo(got);
|
||||
cv::addWeighted(a, 2.0, b, 1.0, 0.0, expected);
|
||||
EXPECT_LE(cvtest::norm(got, expected, NORM_INF), 1e-4);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // HAVE_CUDA
|
||||
@@ -6,31 +6,27 @@ OpenCV 5 introduces a selectable inference backend for the DNN module, referred
|
||||
|
||||
The engine is specified at model-load time and cannot be changed after the model has been loaded, as each engine uses a different internal graph representation.
|
||||
|
||||
### ENGINE_NEW
|
||||
|
||||
`ENGINE_NEW` is a ground-up rewrite of the DNN inference graph, introduced in OpenCV 5. It is built around a typed operation graph with shape inference, constant folding, and operator fusion, covering approximately 75-80% of the ONNX operator specification. Models that failed to load under OpenCV 4.x due to dynamic shapes or unsupported operators will typically load and run correctly under this engine.
|
||||
|
||||
The engine performs automatic attention fusion: the `MatMul` → `Softmax` → `MatMul` subgraph common to transformer architectures is recognised and collapsed into a single fused operation at load time, with no changes required to the model or calling code.
|
||||
|
||||
`ENGINE_NEW` also introduces native support for Large Language Models and Vision-Language Models. Built-in tokenizers, attention layers, decoding blocks, and KV-caching allow models such as Qwen, Gemma, and PaliGemma to run end-to-end through the standard `Net` API, with no external runtime required.
|
||||
|
||||
In OpenCV 5.0, `ENGINE_NEW` runs on CPU only. Support for CUDA and other non-CPU backends is planned for a subsequent release. Users requiring GPU acceleration should use `ENGINE_CLASSIC` or `ENGINE_ORT`.
|
||||
|
||||
### ENGINE_CLASSIC
|
||||
|
||||
`ENGINE_CLASSIC` is the inference engine carried over from OpenCV 4.x. It supports the full set of DNN backends and hardware targets, including `DNN_BACKEND_CUDA` for NVIDIA GPUs, `DNN_BACKEND_OPENVINO` for Intel hardware, and FP16 inference targets. Its ONNX operator coverage is approximately 22% of the specification. Models with dynamic shapes or transformer-style subgraphs will generally not load under this engine.
|
||||
|
||||
@note The Darknet and Caffe parsers have been removed in OpenCV 5; ONNX is the recommended format for all engines. TFLite models are still supported and currently executed via `ENGINE_CLASSIC`.
|
||||
|
||||
### ENGINE_AUTO
|
||||
|
||||
`ENGINE_AUTO` is the default value for the engine parameter on all `readNet*()` functions. When active, OpenCV first attempts to load the model with `ENGINE_NEW`. If the model cannot be loaded - for example, because it uses an operator not yet implemented in the new engine - the load is automatically retried with `ENGINE_CLASSIC`.
|
||||
`ENGINE_AUTO` is the default value for the engine parameter on all `readNet*()` functions. It lets OpenCV pick the engine: it currently resolves to `ENGINE_OPENCV`. The resolution is intentionally an implementation detail and may change as more engines are added, so code that does not need a specific engine should leave the default in place.
|
||||
|
||||
Because `ENGINE_AUTO` is the default, existing code that does not pass an engine argument requires no modification.
|
||||
|
||||
### ENGINE_OPENCV
|
||||
|
||||
`ENGINE_OPENCV` is OpenCV's built-in DNN engine, a ground-up rewrite of the inference graph introduced in OpenCV 5. It is built around a typed operation graph with shape inference, constant folding, and operator fusion, covering approximately 75-80% of the ONNX operator specification. Models that failed to load under OpenCV 4.x due to dynamic shapes or unsupported operators will typically load and run correctly under this engine.
|
||||
|
||||
The engine performs automatic attention fusion: the `MatMul` → `Softmax` → `MatMul` subgraph common to transformer architectures is recognised and collapsed into a single fused operation at load time, with no changes required to the model or calling code.
|
||||
|
||||
`ENGINE_OPENCV` also introduces native support for Large Language Models and Vision-Language Models. Built-in tokenizers, attention layers, decoding blocks, and KV-caching allow models such as Qwen, Gemma, and PaliGemma to run end-to-end through the standard `Net` API, with no external runtime required.
|
||||
|
||||
In OpenCV 5.0, `ENGINE_OPENCV` runs on CPU only. Support for CUDA and other non-CPU backends is planned for a subsequent release. Users requiring GPU acceleration should use `ENGINE_ORT`.
|
||||
|
||||
@note The Darknet and Caffe parsers have been removed in OpenCV 5; ONNX is the recommended format. TFLite and TensorFlow models are still supported and are executed via `ENGINE_OPENCV`.
|
||||
|
||||
### ENGINE_ORT
|
||||
|
||||
`ENGINE_ORT` routes inference through a bundled ONNX Runtime (ORT) wrapper. OpenCV uses its own ONNX parser to construct the ORT graph internally, so only the ORT library is required at runtime, not the standalone onnx package.
|
||||
`ENGINE_ORT` routes inference through a bundled ONNX Runtime (ORT) wrapper. OpenCV uses its own ONNX parser to construct the ORT graph internally, so only the ORT library is required at runtime, not the standalone onnx package. It applies to ONNX models only.
|
||||
|
||||
`ENGINE_ORT` must be enabled at compile time:
|
||||
|
||||
@@ -42,7 +38,7 @@ cmake -DWITH_ONNXRUNTIME=ON ..
|
||||
cmake -DWITH_ONNXRUNTIME=ON -DDOWNLOAD_ONNXRUNTIME_GPU=ON ..
|
||||
```
|
||||
|
||||
ORT execution providers are supported, including CUDA for NVIDIA hardware. This makes `ENGINE_ORT` the recommended choice for GPU-accelerated inference on models that `ENGINE_CLASSIC` cannot load, while native GPU support for `ENGINE_NEW` is still in development.
|
||||
ORT execution providers are supported, including CUDA for NVIDIA hardware. This makes `ENGINE_ORT` the recommended choice for GPU-accelerated inference while native GPU support for `ENGINE_OPENCV` is still in development.
|
||||
|
||||
### Selecting an Engine
|
||||
|
||||
@@ -50,18 +46,13 @@ The engine parameter is accepted by the `readNet*()` family of functions.
|
||||
|
||||
@add_toggle_cpp
|
||||
@code{.cpp}
|
||||
// ENGINE_AUTO is the default
|
||||
// ENGINE_AUTO is the default (resolves to ENGINE_OPENCV)
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx");
|
||||
|
||||
// Force the new engine
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_NEW);
|
||||
// Explicitly select OpenCV's built-in engine
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_OPENCV);
|
||||
|
||||
// Force the classic engine with CUDA backend
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_CLASSIC);
|
||||
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
|
||||
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
|
||||
|
||||
// Use ONNX Runtime
|
||||
// Use ONNX Runtime (ONNX models only, requires WITH_ONNXRUNTIME=ON)
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_ORT);
|
||||
@endcode
|
||||
@end_toggle
|
||||
@@ -70,18 +61,13 @@ cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_ORT);
|
||||
@code{.py}
|
||||
import cv2
|
||||
|
||||
# ENGINE_AUTO is the default
|
||||
# ENGINE_AUTO is the default (resolves to ENGINE_OPENCV)
|
||||
net = cv2.dnn.readNetFromONNX("model.onnx")
|
||||
|
||||
# Force the new engine
|
||||
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_NEW)
|
||||
# Explicitly select OpenCV's built-in engine
|
||||
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_OPENCV)
|
||||
|
||||
# Force the classic engine with CUDA backend
|
||||
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_CLASSIC)
|
||||
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
|
||||
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
|
||||
|
||||
# Use ONNX Runtime
|
||||
# Use ONNX Runtime (ONNX models only, requires WITH_ONNXRUNTIME=ON)
|
||||
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_ORT)
|
||||
@endcode
|
||||
@end_toggle
|
||||
@@ -90,7 +76,7 @@ The engine cannot be changed after a model has been loaded. To use a different e
|
||||
|
||||
### Engine Selection via Environment Variable
|
||||
|
||||
The engine can be overridden at the process level using the `OPENCV_FORCE_DNN_ENGINE` environment variable. The integer values correspond directly to the `EngineType` enum: 1 for `ENGINE_CLASSIC`, 2 for `ENGINE_NEW`, 3 for `ENGINE_AUTO`, and 4 for `ENGINE_ORT`.
|
||||
The engine can be overridden at the process level using the `OPENCV_FORCE_DNN_ENGINE` environment variable. The integer values correspond directly to the `EngineType` enum: 0 for `ENGINE_AUTO`, 1 for `ENGINE_OPENCV`, and 2 for `ENGINE_ORT`. Leaving the variable unset (or set to `ENGINE_AUTO`) does not force any engine, so the value passed to `readNet*()` is honored.
|
||||
|
||||
```bash
|
||||
# Linux / macOS
|
||||
|
||||
@@ -858,6 +858,28 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<LoopLayer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS ScanLayer : public Layer
|
||||
{
|
||||
public:
|
||||
/** Number of trailing inputs that are scanned (the rest are loop-carried state). */
|
||||
virtual int numScanInputs() const = 0;
|
||||
/** Per-scan-input axis to iterate over (empty => 0 for all). */
|
||||
virtual const std::vector<int>& scanInputAxes() const = 0;
|
||||
/** Per-scan-output axis to stack along (empty => 0 for all). */
|
||||
virtual const std::vector<int>& scanOutputAxes() const = 0;
|
||||
/** Per-scan-input direction, 1 = reverse (empty => forward for all). */
|
||||
virtual const std::vector<int>& scanInputDirections() const = 0;
|
||||
/** Per-scan-output direction, 1 = reverse (empty => forward for all). */
|
||||
virtual const std::vector<int>& scanOutputDirections() const = 0;
|
||||
/** ONNX-declared rank of each body scan output (-1 if unknown). A declared rank of
|
||||
* 0 (scalar) marks an output that OpenCV stores as [1] but must stack into a rank-1
|
||||
* tensor, not [T, 1]. Empty => unknown for all. */
|
||||
virtual const std::vector<int>& scanOutputRanks() const = 0;
|
||||
|
||||
/** Factory: creates a ScanLayer implementation. */
|
||||
static Ptr<ScanLayer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS ConcatLayer : public Layer
|
||||
{
|
||||
public:
|
||||
@@ -1101,6 +1123,8 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
{
|
||||
public:
|
||||
static Ptr<Layer> create(const LayerParams& params);
|
||||
// Set the per-channel slope when it arrives as a second input, not a blob.
|
||||
virtual void setSlope(const Mat& /*slope*/) {}
|
||||
};
|
||||
|
||||
class CV_EXPORTS ELULayer : public ActivationLayer
|
||||
@@ -1920,6 +1944,16 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<AttentionOnnxAiLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS CausalConvWithStateLayer : public Layer {
|
||||
public:
|
||||
static Ptr<CausalConvWithStateLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS CumProdLayer : public Layer {
|
||||
public:
|
||||
static Ptr<CumProdLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS GroupNormLayer : public Layer {
|
||||
public:
|
||||
static Ptr<GroupNormLayer> create(const LayerParams ¶ms);
|
||||
|
||||
@@ -835,7 +835,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
*/
|
||||
CV_WRAP void setParam(int layer, int numParam, CV_ND const Mat &blob);
|
||||
/** @brief Sets the parameter blob of a layer identified by its name or output tensor name.
|
||||
* @param layerName layer name (classic engine) or raw ONNX output tensor name (ENGINE_NEW).
|
||||
* @param layerName raw ONNX output tensor name (ENGINE_OPENCV).
|
||||
* @param numParam index of the constant weight input to update (0 = kernel, 1 = bias, etc.).
|
||||
* @param blob the new parameter value.
|
||||
*/
|
||||
@@ -1083,10 +1083,9 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
|
||||
enum EngineType
|
||||
{
|
||||
ENGINE_CLASSIC=1, //!< Force use the old dnn engine similar to 4.x branch
|
||||
ENGINE_NEW=2, //!< Force use the new dnn engine. The engine does not support non CPU back-ends for now.
|
||||
ENGINE_AUTO=3, //!< Try to use the new engine and then fall back to the classic version.
|
||||
ENGINE_ORT=4 //!< Try to use ONNX Runtime wrapper (ONNX only, requires build with WITH_ONNXRUNTIME=ON).
|
||||
ENGINE_AUTO=0, //!< Automatically select the engine. Currently resolves to ENGINE_OPENCV; the mapping may change as more engines are added.
|
||||
ENGINE_OPENCV=1, //!< Use OpenCV's built-in DNN engine. Does not support non-CPU back-ends for now.
|
||||
ENGINE_ORT=2 //!< Use the ONNX Runtime wrapper (ONNX only, requires build with WITH_ONNXRUNTIME=ON).
|
||||
};
|
||||
|
||||
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/">TensorFlow</a> framework's format.
|
||||
@@ -1094,7 +1093,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
* @param config path to the .pbtxt file that contains text graph definition in protobuf format.
|
||||
* Resulting Net object is built by text graph using weights from a binary one that
|
||||
* let us make it more flexible.
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV.
|
||||
* @param extraOutputs specify model outputs explicitly, in addition to the outputs the graph analyzer finds.
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* @returns Net object.
|
||||
@@ -1107,7 +1106,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/">TensorFlow</a> framework's format.
|
||||
* @param bufferModel buffer containing the content of the pb file
|
||||
* @param bufferConfig buffer containing the content of the pbtxt file
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV.
|
||||
* @param extraOutputs specify model outputs explicitly, in addition to the outputs the graph analyzer finds.
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* @returns Net object.
|
||||
@@ -1124,7 +1123,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
* @param lenModel length of bufferModel
|
||||
* @param bufferConfig buffer containing the content of the pbtxt file
|
||||
* @param lenConfig length of bufferConfig
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV.
|
||||
* @param extraOutputs specify model outputs explicitly, in addition to the outputs the graph analyzer finds.
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
*/
|
||||
@@ -1135,7 +1134,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
|
||||
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/lite">TFLite</a> framework's format.
|
||||
* @param model path to the .tflite file with binary flatbuffers description of the network architecture
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* @returns Net object.
|
||||
*/
|
||||
@@ -1143,7 +1142,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
|
||||
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/lite">TFLite</a> framework's format.
|
||||
* @param bufferModel buffer containing the content of the tflite file
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* @returns Net object.
|
||||
*/
|
||||
@@ -1154,7 +1153,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
* It differs from the above function only in what argument(s) it accepts.
|
||||
* @param bufferModel buffer containing the content of the tflite file
|
||||
* @param lenModel length of bufferModel
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
*/
|
||||
CV_EXPORTS Net readNetFromTFLite(const char *bufferModel, size_t lenModel, int engine=ENGINE_AUTO);
|
||||
@@ -1171,9 +1170,8 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
* * `*.pbtxt` (TensorFlow, https://www.tensorflow.org/)
|
||||
* * `*.xml` (OpenVINO, https://software.intel.com/openvino-toolkit)
|
||||
* @param[in] framework Explicit framework name tag to determine a format.
|
||||
* @param[in] engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* Use ENGINE_CLASSIC if you want to use other back-ends.
|
||||
* @returns Net object.
|
||||
*
|
||||
* This function automatically detects an origin framework of trained model
|
||||
@@ -1192,9 +1190,8 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
* @param[in] framework Name of origin framework.
|
||||
* @param[in] bufferModel A buffer with a content of binary file with weights
|
||||
* @param[in] bufferConfig A buffer with a content of text file contains network configuration.
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* Use ENGINE_CLASSIC if you want to use other back-ends.
|
||||
* @returns Net object.
|
||||
*/
|
||||
CV_EXPORTS_W Net readNet(const String& framework, const std::vector<uchar>& bufferModel,
|
||||
@@ -1237,7 +1234,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
|
||||
/** @brief Reads a network model <a href="https://onnx.ai/">ONNX</a>.
|
||||
* @param onnxFile path to the .onnx file with text description of the network architecture.
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* @returns Network object that ready to do forward, throw an exception in failure cases.
|
||||
*/
|
||||
@@ -1247,7 +1244,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
* in-memory buffer.
|
||||
* @param buffer memory address of the first byte of the buffer.
|
||||
* @param sizeBuffer size of the buffer.
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* @returns Network object that ready to do forward, throw an exception
|
||||
* in failure cases.
|
||||
*/
|
||||
@@ -1256,7 +1253,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
/** @brief Reads a network model from <a href="https://onnx.ai/">ONNX</a>
|
||||
* in-memory buffer.
|
||||
* @param buffer in-memory buffer that stores the ONNX model bytes.
|
||||
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
|
||||
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
|
||||
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
|
||||
* @returns Network object that ready to do forward, throw an exception
|
||||
* in failure cases.
|
||||
|
||||
@@ -45,7 +45,7 @@ public class DnnForwardAndRetrieve extends OpenCVTestCase {
|
||||
public void testForwardAndRetrieve()
|
||||
{
|
||||
// Verifies forwardAndRetrieve nested list marshalling using a small ONNX model instead of the removed Caffe importer.
|
||||
Net net = Dnn.readNetFromONNX(modelFileName, Dnn.ENGINE_CLASSIC);
|
||||
Net net = Dnn.readNetFromONNX(modelFileName, Dnn.ENGINE_OPENCV);
|
||||
net.setPreferableBackend(Dnn.DNN_BACKEND_OPENCV);
|
||||
|
||||
// split_0.onnx declares a single 4D input named "image" of shape [1, 3, 2, 2].
|
||||
|
||||
@@ -366,7 +366,7 @@ class dnn_test(NewOpenCVTests):
|
||||
for backend, target in self.dnnBackendsAndTargets:
|
||||
printParams(backend, target)
|
||||
|
||||
net = cv.dnn.readNet(model, engine=cv.dnn.ENGINE_CLASSIC)
|
||||
net = cv.dnn.readNet(model, engine=cv.dnn.ENGINE_OPENCV)
|
||||
|
||||
net.setPreferableBackend(backend)
|
||||
net.setPreferableTarget(target)
|
||||
@@ -415,7 +415,7 @@ class dnn_test(NewOpenCVTests):
|
||||
for backend, target in self.dnnBackendsAndTargets:
|
||||
printParams(backend, target)
|
||||
|
||||
net = cv.dnn.readNet(model_path, "", "", engine=cv.dnn.ENGINE_CLASSIC)
|
||||
net = cv.dnn.readNet(model_path, "", "", engine=cv.dnn.ENGINE_OPENCV)
|
||||
|
||||
node_name = net.getLayerNames()[0]
|
||||
w = net.getParam(node_name, 0) # returns the original tensor of three-dimensional shape
|
||||
|
||||
@@ -83,6 +83,13 @@ static const GemmParam_t test_matmul_configs[] = {
|
||||
{ {16, 197, 64 }, {16, 64, 197} },
|
||||
{ {16, 50, 64}, {16, 64, 50} },
|
||||
{ {16, 50, 50}, {16, 50, 64} },
|
||||
|
||||
// transformer token generation cases (thin-M GEMV, fastGemmThin path)
|
||||
{ {1, 1, 768}, {1, 768, 768} },
|
||||
{ {1, 1, 768}, {1, 768, 3072} },
|
||||
{ {1, 1, 3072}, {1, 3072, 768} },
|
||||
{ {1, 2, 768}, {1, 768, 768} },
|
||||
{ {1, 4, 768}, {1, 768, 768} },
|
||||
};
|
||||
|
||||
struct GemmParamId
|
||||
|
||||
@@ -145,12 +145,6 @@ PERF_TEST_P_(DNNTestNetwork, SSD)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DEBUG_VERYLONG);
|
||||
|
||||
// SSD_VGG16's specialized preprocessing is handled by the new engine importer only.
|
||||
auto engine_forced = static_cast<dnn::EngineType>(
|
||||
utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", dnn::ENGINE_AUTO));
|
||||
if (engine_forced == dnn::ENGINE_CLASSIC)
|
||||
throw SkipTestException("SSD_VGG16 is supported on the new DNN engine only");
|
||||
|
||||
processNet("dnn/onnx/models/ssd_vgg16.onnx", "", cv::Size(300, 300));
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,49 @@ struct BufferAllocator
|
||||
releaseBuffer(toBuf);
|
||||
}
|
||||
|
||||
// Allocate a Loop/Scan body, keeping its closure (outer-scope) args alive across it.
|
||||
void assignSubgraphKeepingClosure(const Ptr<Graph>& body)
|
||||
{
|
||||
std::unordered_set<int> bodyDefined;
|
||||
for (Arg ba : body->inputs())
|
||||
bodyDefined.insert(ba.idx);
|
||||
for (const Ptr<Layer>& blayer : body->prog()) {
|
||||
if (!blayer) continue;
|
||||
for (Arg bo : blayer->outputs)
|
||||
bodyDefined.insert(bo.idx);
|
||||
}
|
||||
std::unordered_set<int> closureBumped;
|
||||
for (const Ptr<Layer>& blayer : body->prog()) {
|
||||
if (!blayer) continue;
|
||||
for (Arg bi : blayer->inputs) {
|
||||
if (bi.idx <= 0) continue;
|
||||
if (bodyDefined.count(bi.idx)) continue;
|
||||
if (netimpl->isConstArg(bi)) continue;
|
||||
if (bufidxs[bi.idx] < 0) continue;
|
||||
if (closureBumped.insert(bi.idx).second) {
|
||||
usecounts[bi.idx]++;
|
||||
buf_usecounts[bufidxs[bi.idx]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> saved_freebufs = freebufs;
|
||||
freebufs.clear();
|
||||
assign(body);
|
||||
freebufs = saved_freebufs;
|
||||
|
||||
for (int idx : closureBumped) {
|
||||
int bidx = bufidxs[idx];
|
||||
if (--usecounts[idx] == 0) {
|
||||
if (bidx >= 0)
|
||||
releaseBuffer(bidx);
|
||||
} else if (bidx >= 0) {
|
||||
CV_Assert(buf_usecounts[bidx] > 0);
|
||||
--buf_usecounts[bidx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename _Tp> std::ostream&
|
||||
dumpArgVec(std::ostream& strm, const std::string& name, const vector<_Tp>& vec) const
|
||||
{
|
||||
@@ -355,48 +398,11 @@ struct BufferAllocator
|
||||
}
|
||||
}
|
||||
|
||||
// The body reads names produced in the enclosing scope
|
||||
// (closure references) without listing them in the Loop/If
|
||||
// layer's inputs. Bump the outer-scope usecount of each
|
||||
// such arg so its buffer survives until the subgraph runs.
|
||||
std::unordered_set<int> bodyDefined;
|
||||
for (Arg ba : body->inputs())
|
||||
bodyDefined.insert(ba.idx);
|
||||
for (const Ptr<Layer>& blayer : body->prog()) {
|
||||
if (!blayer) continue;
|
||||
for (Arg bo : blayer->outputs)
|
||||
bodyDefined.insert(bo.idx);
|
||||
}
|
||||
std::unordered_set<int> closureBumped;
|
||||
for (const Ptr<Layer>& blayer : body->prog()) {
|
||||
if (!blayer) continue;
|
||||
for (Arg bi : blayer->inputs) {
|
||||
if (bi.idx <= 0) continue;
|
||||
if (bodyDefined.count(bi.idx)) continue;
|
||||
if (netimpl->isConstArg(bi)) continue;
|
||||
if (bufidxs[bi.idx] < 0) continue;
|
||||
if (closureBumped.insert(bi.idx).second) {
|
||||
usecounts[bi.idx]++;
|
||||
buf_usecounts[bufidxs[bi.idx]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> saved_freebufs = freebufs;
|
||||
freebufs.clear();
|
||||
assign(body);
|
||||
freebufs = saved_freebufs;
|
||||
|
||||
for (int idx : closureBumped) {
|
||||
int bidx = bufidxs[idx];
|
||||
if (--usecounts[idx] == 0) {
|
||||
if (bidx >= 0)
|
||||
releaseBuffer(bidx);
|
||||
} else if (bidx >= 0) {
|
||||
CV_Assert(buf_usecounts[bidx] > 0);
|
||||
--buf_usecounts[bidx];
|
||||
}
|
||||
}
|
||||
assignSubgraphKeepingClosure(body);
|
||||
} else if (opname == "Scan") {
|
||||
auto subgraphs = layer->subgraphs();
|
||||
CV_Assert(subgraphs && subgraphs->size() == 1);
|
||||
assignSubgraphKeepingClosure(subgraphs->at(0));
|
||||
}
|
||||
|
||||
for (auto out: outputs) {
|
||||
|
||||
@@ -68,6 +68,7 @@ struct ConstArgs
|
||||
Conv2Layer* conv = dynamic_cast<Conv2Layer*>(layer_ptr);
|
||||
ConvTranspose2Layer* deconv = dynamic_cast<ConvTranspose2Layer*>(layer_ptr);
|
||||
BatchNorm2Layer* bn = dynamic_cast<BatchNorm2Layer*>(layer_ptr);
|
||||
ChannelsPReLULayer* prelu = dynamic_cast<ChannelsPReLULayer*>(layer_ptr);
|
||||
//ActivationLayer* activ = dynamic_cast<ActivationLayer*>(layer_ptr);
|
||||
|
||||
if (tail_const) {
|
||||
@@ -88,6 +89,10 @@ struct ConstArgs
|
||||
} else if (bn && bn->freezeScaleBias()) {
|
||||
// batch norm with constant parameters
|
||||
unuse_tail = true;
|
||||
} else if (prelu && ninputs == 2) {
|
||||
prelu->setSlope(netimpl->__tensors__[inputs[1].idx]);
|
||||
prelu->inputs.resize(1);
|
||||
unuse_tail = true;
|
||||
}/* else if (activ && dynamic_cast<ReLU6Layer>(activ)) {
|
||||
// [TODO] ...
|
||||
unuse_tail = true;
|
||||
|
||||
@@ -86,6 +86,7 @@ void initializeLayerFactory()
|
||||
|
||||
CV_DNN_REGISTER_LAYER_CLASS(If, IfLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Loop, LoopLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Scan, ScanLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Concat, ConcatLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Concat2, Concat2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(ConstantOfShape, ConstantOfShapeLayer);
|
||||
@@ -216,6 +217,7 @@ void initializeLayerFactory()
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Attention, AttentionLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(SDPA, SDPALayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(AttentionOnnxAi, AttentionOnnxAiLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(CausalConvWithState, CausalConvWithStateLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(RotaryEmbedding, RotaryEmbeddingLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(GroupNormalization, GroupNormLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Cast, CastLayer);
|
||||
@@ -252,6 +254,7 @@ void initializeLayerFactory()
|
||||
CV_DNN_REGISTER_LAYER_CLASS(LSTM2, LSTM2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(GRU, GRULayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(CumSum, CumSumLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(CumProd, CumProdLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Einsum, EinsumLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Hardmax, HardmaxLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(GatherND, GatherNDLayer);
|
||||
|
||||
@@ -376,7 +376,7 @@ std::ostream& Layer::dump(std::ostream& strm, int indent, bool comma) const
|
||||
std::vector<std::string> names;
|
||||
if (opname == "If")
|
||||
names = {"then", "else"};
|
||||
else if (opname == "Loop")
|
||||
else if (opname == "Loop" || opname == "Scan")
|
||||
names = {"body"};
|
||||
else {
|
||||
CV_Error(Error::StsError,
|
||||
|
||||
@@ -53,8 +53,11 @@ static void avgPool32f(const void* inp_, void* out_,
|
||||
float* out = (float*)out_ + nc0*planesize;
|
||||
float iksize = 1.f/ksize;
|
||||
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
int nlanes = VTraits<v_float32>::vlanes();
|
||||
// RVV (CV_SIMD_SCALABLE) disabled for the m1 switch: with the fixed C0=8 the
|
||||
// block is narrower than the register at VLEN>=512, tripping this assert. Runs
|
||||
// scalar on RVV; re-enable via v_setvlmax<v_float32>(C0) (#29493, cf #29180).
|
||||
CV_Assert(C0 == nlanes || C0 == nlanes*2 || C0 % (nlanes*4) == 0);
|
||||
v_float32 z = vx_setzero_f32();
|
||||
v_float32 vscale0 = vx_setall_f32(iksize);
|
||||
@@ -69,12 +72,12 @@ static void avgPool32f(const void* inp_, void* out_,
|
||||
y0 >= inner_y0 && y0 < inner_y1 ? inner_x0 : W;
|
||||
int yi_ = y0*SY - padY0;
|
||||
|
||||
#if !(CV_SIMD || CV_SIMD_SCALABLE)
|
||||
#if !(CV_SIMD)
|
||||
memset(out, 0, W*C0*sizeof(out[0]));
|
||||
#endif
|
||||
|
||||
for(;;) {
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
if (nlanes == C0) {
|
||||
for (; x0 < x1; x0++) {
|
||||
int xi_ = x0*SX - padX0;
|
||||
@@ -162,7 +165,7 @@ static void avgPool32f(const void* inp_, void* out_,
|
||||
break;
|
||||
x1 = inner_x1;
|
||||
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
if (nlanes == C0) {
|
||||
for (; x0 < x1; x0++) {
|
||||
int xi_ = x0*SX - padX0;
|
||||
|
||||
@@ -118,8 +118,16 @@ namespace
|
||||
ushort* out = dst_bits.ptr<ushort>(r);
|
||||
for (int i = 0; i < cols_x_cn; ++i)
|
||||
{
|
||||
// float32 -> bfloat16 with round-to-nearest-even (matches ONNX).
|
||||
Cv32suf u; u.f = in[i];
|
||||
out[i] = (ushort)(u.u >> 16);
|
||||
const uint32_t x = u.u;
|
||||
if ((x & 0x7fffffffu) > 0x7f800000u) // NaN: keep it NaN
|
||||
out[i] = (ushort)((x >> 16) | 0x0040u);
|
||||
else
|
||||
{
|
||||
const uint32_t bias = 0x7fffu + ((x >> 16) & 1u);
|
||||
out[i] = (ushort)((x + bias) >> 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -210,7 +218,6 @@ public:
|
||||
const int in0CN = in0Type >= 0 ? CV_MAT_CN(in0Type) : 1;
|
||||
int planDepth = targetDepth;
|
||||
if (planDepth == CV_16F) planDepth = CV_32F;
|
||||
if (planDepth == CV_16BF) planDepth = CV_16U;
|
||||
const int outType = CV_MAKETYPE(planDepth, in0CN);
|
||||
outputs.assign(1, outType);
|
||||
}
|
||||
@@ -243,6 +250,13 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// bf16 needs the float->bfloat16 bit reduction; a plain OpenCL convertTo
|
||||
// would numerically round instead. Fall back to the CPU path.
|
||||
if (runtimeTargetDepth == CV_16BF)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputs[0].depth() == outputs[0].depth())
|
||||
inputs[0].copyTo(outputs[0]);
|
||||
else
|
||||
@@ -291,8 +305,7 @@ public:
|
||||
}
|
||||
CV_CheckGE(runtimeTargetDepth, 0, "Cast: failed to resolve target data type at runtime");
|
||||
|
||||
int plannedDDepth = (runtimeTargetDepth == CV_16F) ? CV_32F :
|
||||
(runtimeTargetDepth == CV_16BF ? CV_16U : runtimeTargetDepth);
|
||||
int plannedDDepth = (runtimeTargetDepth == CV_16F) ? CV_32F : runtimeTargetDepth;
|
||||
if (dst0.depth() != plannedDDepth)
|
||||
dst0.create(dst0.size(), CV_MAKETYPE(plannedDDepth, src0.channels()));
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "layers_common.hpp"
|
||||
#include <opencv2/dnn/shape_utils.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
/*
|
||||
Implementation of CausalConvWithState, as defined in ONNX specification:
|
||||
https://onnx.ai/onnx/operators/onnx__CausalConvWithState.html
|
||||
|
||||
Opset 27 is covered.
|
||||
*/
|
||||
class CausalConvWithStateLayerImpl CV_FINAL : public CausalConvWithStateLayer
|
||||
{
|
||||
public:
|
||||
CausalConvWithStateLayerImpl(const LayerParams& params)
|
||||
{
|
||||
setParamsFrom(params);
|
||||
std::string act = params.get<std::string>("activation", "");
|
||||
silu = (act == "silu" || act == "swish");
|
||||
CV_Check(act, act.empty() || silu, "CausalConvWithState: unsupported activation");
|
||||
}
|
||||
|
||||
bool supportBackend(int backendId) CV_OVERRIDE { return backendId == DNN_BACKEND_OPENCV; }
|
||||
|
||||
static bool present(const std::vector<Mat>& in, size_t i) { return in.size() > i && !in[i].empty(); }
|
||||
|
||||
void getTypes(const std::vector<MatType>& inputs, const int requiredOutputs, const int,
|
||||
std::vector<MatType>& outputs, std::vector<MatType>& internals) const CV_OVERRIDE
|
||||
{
|
||||
CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_16F, "");
|
||||
outputs.assign(requiredOutputs, inputs[0]);
|
||||
internals.clear();
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>& inputs, const int,
|
||||
std::vector<MatShape>& outputs, std::vector<MatShape>&) const CV_OVERRIDE
|
||||
{
|
||||
CV_CheckGE(inputs.size(), (size_t)2, "CausalConvWithState needs input and weight");
|
||||
CV_CheckEQ(inputs[0].dims, 3, "input must be [batch, channels, seq]");
|
||||
CV_CheckEQ(inputs[1].dims, 3, "weight must be [channels, 1, kernel]");
|
||||
const int B = inputs[0][0], C = inputs[0][1], T = inputs[0][2], K = inputs[1][2];
|
||||
outputs.assign(1, MatShape{B, C, T});
|
||||
outputs.push_back(MatShape{B, C, K - 1});
|
||||
return false;
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays) CV_OVERRIDE
|
||||
{
|
||||
std::vector<Mat> rawIn, rawOut;
|
||||
inputs_arr.getMatVector(rawIn);
|
||||
outputs_arr.getMatVector(rawOut);
|
||||
|
||||
const bool fp16 = rawIn[0].depth() == CV_16F;
|
||||
std::vector<Mat> in32, out32;
|
||||
if (fp16)
|
||||
{
|
||||
in32.resize(rawIn.size());
|
||||
for (size_t i = 0; i < rawIn.size(); ++i)
|
||||
if (!rawIn[i].empty()) rawIn[i].convertTo(in32[i], CV_32F);
|
||||
out32.resize(rawOut.size());
|
||||
for (size_t i = 0; i < rawOut.size(); ++i)
|
||||
out32[i].create(rawOut[i].dims, rawOut[i].size.p, CV_32F);
|
||||
}
|
||||
std::vector<Mat>& inputs = fp16 ? in32 : rawIn;
|
||||
std::vector<Mat>& outputs = fp16 ? out32 : rawOut;
|
||||
|
||||
const Mat& input = inputs[0];
|
||||
const Mat& weight = inputs[1];
|
||||
const bool has_bias = present(inputs, 2);
|
||||
const bool has_past = present(inputs, 3);
|
||||
|
||||
const int B = input.size[0], C = input.size[1], T = input.size[2];
|
||||
const int K = weight.size[2];
|
||||
const int P = K - 1; // state / left-pad width
|
||||
|
||||
const float* Ip = input.ptr<float>();
|
||||
const float* Wp = weight.ptr<float>();
|
||||
const float* Bp = has_bias ? inputs[2].ptr<float>() : nullptr;
|
||||
const float* Sp = has_past ? inputs[3].ptr<float>() : nullptr;
|
||||
float* Op = outputs[0].ptr<float>();
|
||||
float* PSp = outputs[1].ptr<float>();
|
||||
|
||||
parallel_for_(Range(0, B * C), [&](const Range& r)
|
||||
{
|
||||
std::vector<float> pad(P + T);
|
||||
for (int bc = r.start; bc < r.end; ++bc)
|
||||
{
|
||||
const int c = bc % C;
|
||||
const float* x = Ip + (size_t)bc * T;
|
||||
const float* w = Wp + (size_t)c * K;
|
||||
|
||||
for (int j = 0; j < P; ++j)
|
||||
pad[j] = has_past ? Sp[(size_t)bc * P + j] : 0.f;
|
||||
for (int t = 0; t < T; ++t)
|
||||
pad[P + t] = x[t];
|
||||
|
||||
const float bias = has_bias ? Bp[c] : 0.f;
|
||||
float* o = Op + (size_t)bc * T;
|
||||
for (int t = 0; t < T; ++t)
|
||||
{
|
||||
float acc = bias;
|
||||
for (int k = 0; k < K; ++k)
|
||||
acc += w[k] * pad[t + k];
|
||||
o[t] = acc;
|
||||
}
|
||||
if (silu)
|
||||
for (int t = 0; t < T; ++t)
|
||||
o[t] = o[t] / (1.f + std::exp(-o[t]));
|
||||
|
||||
float* ps = PSp + (size_t)bc * P;
|
||||
for (int j = 0; j < P; ++j)
|
||||
ps[j] = pad[T + j];
|
||||
}
|
||||
});
|
||||
|
||||
if (fp16)
|
||||
for (size_t i = 0; i < rawOut.size(); ++i)
|
||||
out32[i].convertTo(rawOut[i], CV_16F);
|
||||
}
|
||||
|
||||
int64 getFLOPS(const std::vector<MatShape>& inputs, const std::vector<MatShape>&) const CV_OVERRIDE
|
||||
{
|
||||
const int64 B = inputs[0][0], C = inputs[0][1], T = inputs[0][2], K = inputs[1][2];
|
||||
return B * C * T * (2 * K + 4);
|
||||
}
|
||||
|
||||
private:
|
||||
bool silu = false;
|
||||
};
|
||||
|
||||
Ptr<CausalConvWithStateLayer> CausalConvWithStateLayer::create(const LayerParams& params)
|
||||
{
|
||||
return makePtr<CausalConvWithStateLayerImpl>(params);
|
||||
}
|
||||
|
||||
}} // namespace cv::dnn
|
||||
@@ -38,14 +38,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
|
||||
parallel_for_(Range(0, NC1), [&](const Range& range)
|
||||
{
|
||||
constexpr int MAX_CONV_DIMS = ConvState::MAX_CONV_DIMS;
|
||||
#if CV_SIMD_SCALABLE
|
||||
// RVV: universal intrinsics use LMUL=2, so the float vector width tracks
|
||||
// the hardware VLEN at runtime. The block size C0 == defaultC0 == vlanes()
|
||||
// (e.g. 16 at VLEN=256), so read it at runtime instead of fixing it to 8 (#28852).
|
||||
const int C0 = (int)cs.inpshape.back();
|
||||
#else
|
||||
constexpr int C0 = 8;
|
||||
#endif
|
||||
|
||||
CV_Assert(cs.nspatialdims <= MAX_CONV_DIMS && MAX_CONV_DIMS == 3);
|
||||
CV_Assert(C0 == cs.inpshape.back());
|
||||
@@ -80,14 +73,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
|
||||
const float* activParams = cs.activParams.data();
|
||||
ActivationFunc activation = cs.activation;
|
||||
float maxval = FLT_MAX, defaultAlpha = 0.f;
|
||||
#if CV_SIMD_SCALABLE
|
||||
// C0 is runtime on RVV; size scratch by the compile-time upper bound.
|
||||
float scalebuf[VTraits<v_float32>::max_nlanes];
|
||||
float biasbuf[VTraits<v_float32>::max_nlanes];
|
||||
float alphabuf[VTraits<v_float32>::max_nlanes];
|
||||
#else
|
||||
float scalebuf[C0], biasbuf[C0], alphabuf[C0];
|
||||
#endif
|
||||
if (fastActivation == FAST_ACTIV_CLIP) {
|
||||
CV_Assert(cs.activParams.size() == 2u);
|
||||
maxval = activParams[1];
|
||||
@@ -103,10 +89,13 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
|
||||
defaultAlpha = 1.f;
|
||||
}
|
||||
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
v_float32 v_maxval = vx_setall_f32(maxval);
|
||||
v_float32 z = vx_setzero_f32();
|
||||
const int nlanes = VTraits<v_float32>::vlanes();
|
||||
// RVV (CV_SIMD_SCALABLE) disabled for the m1 switch: with the fixed C0=8 the
|
||||
// block is narrower than the register at VLEN>=512, tripping this assert. Runs
|
||||
// scalar on RVV; re-enable via v_setvlmax<v_float32>(C0) (#29493, cf #29180).
|
||||
CV_Assert(C0 == nlanes || C0 == nlanes*2 || C0 % (nlanes*4) == 0);
|
||||
#endif
|
||||
|
||||
@@ -140,12 +129,12 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
|
||||
y0 >= inner_y0 && y0 < inner_y1 ? inner_x0 : W;
|
||||
int yi_ = y0*SY - padY0;
|
||||
|
||||
#if !(CV_SIMD || CV_SIMD_SCALABLE)
|
||||
#if !(CV_SIMD)
|
||||
memset(out, 0, W*C0*sizeof(out[0]));
|
||||
#endif
|
||||
|
||||
for(;;) {
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
if (nlanes == C0) {
|
||||
v_float32 sc0 = vx_load(scalebuf), b0 = vx_load(biasbuf);
|
||||
v_float32 alpha0 = vx_load(alphabuf);
|
||||
@@ -232,7 +221,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
|
||||
break;
|
||||
x1 = inner_x1;
|
||||
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
if (nlanes == C0) {
|
||||
v_float32 sc0 = vx_load(scalebuf), b0 = vx_load(biasbuf), alpha0 = vx_load(alphabuf);
|
||||
for (; x0 < x1; x0++) {
|
||||
@@ -349,7 +338,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
|
||||
x1 = W;
|
||||
}
|
||||
|
||||
#if !(CV_SIMD || CV_SIMD_SCALABLE)
|
||||
#if !(CV_SIMD)
|
||||
if (residual) {
|
||||
for (int x = 0; x < W*C0; x += C0) {
|
||||
for (int c = 0; c < C0; c++) {
|
||||
|
||||
@@ -386,7 +386,13 @@ CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN
|
||||
CONV_FINALIZE_OUT2(8, 9, CONV_ADD_NO_RESIDUAL2); \
|
||||
}
|
||||
|
||||
#elif CV_SIMD_SCALABLE
|
||||
// TODO(#29493): RVV conv is temporarily scalar. This universal path assumed
|
||||
// K0 == vlanes(), which under m1 holds only at VLEN=256. Disabled as in #29180.
|
||||
// Re-enable in a follow-up with a portable v_setvlmax<v_float32>(K0) universal
|
||||
// intrinsic (no-op on fixed-width ISAs, sets the vl/mask on RVV/SVE) so one vector
|
||||
// spans exactly the K0=8 block at any VLEN; the optimized multi-pixel case is a
|
||||
// later RVV-HAL step. See PR #29493 discussion.
|
||||
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
|
||||
|
||||
/////////////////////////// scalable (RVV) implementation /////////////////////////////
|
||||
// K0 == vlanes(), so each of the 10 spatial positions needs exactly one vector
|
||||
@@ -603,8 +609,7 @@ static void scatterScalarOut(bool aligned_k, int k_base, int k_count, int K0shif
|
||||
#define CONV_INIT_SCALAR_SUMS() \
|
||||
v_float32x4 zz = v_setzero_f32(); \
|
||||
v_float32x4 s0 = zz, s1 = zz
|
||||
#elif CV_SIMD_SCALABLE
|
||||
// RVV: K0 == vlanes(), so a single scalable vector spans the whole output block.
|
||||
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
|
||||
#define CONV_INIT_SCALAR_SUMS() \
|
||||
v_float32 zz = vx_setzero_f32(); \
|
||||
v_float32 s0 = zz
|
||||
@@ -642,7 +647,7 @@ static void scatterScalarOut(bool aligned_k, int k_base, int k_count, int K0shif
|
||||
s0 = v_min(s0, _vmx); s1 = v_min(s1, _vmx); \
|
||||
v_store((outbuf), s0); v_store((outbuf) + 4, s1); \
|
||||
}
|
||||
#elif CV_SIMD_SCALABLE
|
||||
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
|
||||
#define CONV_FINALIZE_SCALAR_OUT(outbuf) \
|
||||
{ \
|
||||
v_float32 _vsc = vx_load(scalebuf); \
|
||||
@@ -2202,17 +2207,11 @@ static void conv32fC8(const void* inp__, const void* residual__, void* out__,
|
||||
|
||||
parallel_for_(Range(0, total_tasks_gen), [&](const Range& range) {
|
||||
constexpr int SPAT_BLOCK_SIZE = 10;
|
||||
#if CV_SIMD_SCALABLE
|
||||
// RVV: block size follows the runtime vector width (defaultC0 = vlanes(), LMUL=2).
|
||||
const int C0 = (int)inpshape.back();
|
||||
int C0shift = 0; while ((1 << C0shift) < C0) C0shift++;
|
||||
const int K0 = C0, K0shift = C0shift;
|
||||
constexpr int C0BUF = VTraits<v_float32>::max_nlanes; // compile-time scratch bound
|
||||
#else
|
||||
// The block size is a fixed property of the blocked layout on every platform,
|
||||
// so C0/K0 are compile-time constants here (#29493).
|
||||
constexpr int C0shift = 3, K0shift = C0shift;
|
||||
constexpr int C0 = 1 << C0shift, K0 = C0;
|
||||
constexpr int C0BUF = K0;
|
||||
#endif
|
||||
|
||||
CV_Assert_N(inpshape.back() == C0, outshape.back() == K0);
|
||||
|
||||
@@ -2523,10 +2522,7 @@ static void conv32fC8(const void* inp__, const void* residual__, void* out__,
|
||||
CONV_UPDATE_BLOCK1(5);
|
||||
CONV_UPDATE_BLOCK1(6);
|
||||
CONV_UPDATE_BLOCK1(7);
|
||||
#elif CV_SIMD_SCALABLE
|
||||
// RVV: K0 == vlanes(); weights are contiguous over kk for a fixed c0,
|
||||
// so one vx_load gives the whole K0-wide weight vector. Broadcast the
|
||||
// input lane and accumulate into the persistent block accumulator.
|
||||
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
|
||||
for (int c0 = 0; c0 < C0; ++c0) {
|
||||
v_float32 w = vx_load(wptr + c0*K0);
|
||||
v_float32 x = vx_setall_f32(inptr[c0]);
|
||||
@@ -2557,16 +2553,9 @@ static void conv32fC8(const void* inp__, const void* residual__, void* out__,
|
||||
cv::dnn::ConvFunc getConvFunc_(int depth, int C0)
|
||||
{
|
||||
ConvFunc func = nullptr;
|
||||
#if CV_SIMD_SCALABLE
|
||||
// RVV: block size follows the runtime vector width; accept the supported pow2 widths.
|
||||
if (depth == CV_32F && (C0 == 8 || C0 == 16 || C0 == 32 || C0 == 64)) {
|
||||
func = conv32fC8;
|
||||
}
|
||||
#else
|
||||
if (depth == CV_32F && C0 == 8) {
|
||||
func = conv32fC8;
|
||||
}
|
||||
#endif
|
||||
return func;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ void run_fused_softmax(
|
||||
const int tmax = is_causal ? std::min(past_seq_len + tq + 1, seq_len_kv) : seq_len_kv;
|
||||
float maxVal = -FLT_MAX;
|
||||
int tk = 0;
|
||||
#if CV_SIMD
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
const int w = VTraits<v_float32>::vlanes();
|
||||
v_float32 v_max_val = vx_setall_f32(maxVal);
|
||||
v_float32 v_softcap = vx_setall_f32(softcap);
|
||||
@@ -173,7 +173,7 @@ void run_fused_softmax(
|
||||
if (do_softmax) {
|
||||
float sum = 0.f;
|
||||
tk = 0;
|
||||
#if CV_SIMD
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
v_float32 v_sum = vx_setzero_f32();
|
||||
v_float32 v_max_val_shift = vx_setall_f32(maxVal);
|
||||
for (; tk <= seq_len_kv - w; tk += w) {
|
||||
@@ -192,7 +192,7 @@ void run_fused_softmax(
|
||||
|
||||
float inv_sum = 1.f / sum;
|
||||
tk = 0;
|
||||
#if CV_SIMD
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
v_float32 v_inv_sum = vx_setall_f32(inv_sum);
|
||||
for (; tk <= seq_len_kv - w; tk += w) {
|
||||
v_float32 v_val = vx_load(&data[offset + tk]);
|
||||
|
||||
@@ -318,6 +318,45 @@ static inline int fast_gemm_thin_lanes() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CV_SIMD_SCALABLE
|
||||
// Apply the alpha/beta epilogue to one accumulator row.
|
||||
static inline void fast_gemm_thin_store_row(float* c, v_float32 acc,
|
||||
v_float32 v_alpha, float beta) {
|
||||
if (beta == 0.f)
|
||||
vx_store(c, v_mul(acc, v_alpha));
|
||||
else if (beta == 1.f)
|
||||
vx_store(c, v_fma(acc, v_alpha, vx_load(c)));
|
||||
else
|
||||
vx_store(c, v_fma(acc, v_alpha, v_mul(vx_load(c), vx_setall_f32(beta))));
|
||||
}
|
||||
|
||||
// Multiply MR (1..4) rows of A by one packed-B strip. Sizeless vector types
|
||||
// cannot form the acc[] array the CV_SIMD path uses, so the accumulators are
|
||||
// named variables selected by the compile-time MR (the `if (MR > n)` branches
|
||||
// fold away) and stay in registers for the whole K loop. Accumulation remains
|
||||
// in increasing-k order, bit-exact with the previous scratch-buffer code.
|
||||
template<int MR>
|
||||
static inline void fast_gemm_thin_block(int K, const float* A, int lda0, int lda1,
|
||||
const float* b_strip, int NR,
|
||||
v_float32 v_alpha, float beta,
|
||||
float* c_strip, int ldc) {
|
||||
v_float32 c0 = vx_setzero_f32(), c1 = vx_setzero_f32();
|
||||
v_float32 c2 = vx_setzero_f32(), c3 = vx_setzero_f32();
|
||||
for (int k = 0; k < K; k++) {
|
||||
v_float32 bv = vx_load(b_strip + k * NR);
|
||||
const float* a = A + k * lda1;
|
||||
c0 = v_fma(bv, vx_setall_f32(a[0]), c0);
|
||||
if (MR > 1) c1 = v_fma(bv, vx_setall_f32(a[lda0]), c1);
|
||||
if (MR > 2) c2 = v_fma(bv, vx_setall_f32(a[2 * lda0]), c2);
|
||||
if (MR > 3) c3 = v_fma(bv, vx_setall_f32(a[3 * lda0]), c3);
|
||||
}
|
||||
fast_gemm_thin_store_row(c_strip, c0, v_alpha, beta);
|
||||
if (MR > 1) fast_gemm_thin_store_row(c_strip + ldc, c1, v_alpha, beta);
|
||||
if (MR > 2) fast_gemm_thin_store_row(c_strip + 2 * ldc, c2, v_alpha, beta);
|
||||
if (MR > 3) fast_gemm_thin_store_row(c_strip + 3 * ldc, c3, v_alpha, beta);
|
||||
}
|
||||
#endif // CV_SIMD_SCALABLE
|
||||
|
||||
static inline void fast_gemm_thin_strip(int M, int K, float alpha,
|
||||
const float* A, int lda0, int lda1,
|
||||
const float* b_strip,
|
||||
@@ -353,37 +392,17 @@ static inline void fast_gemm_thin_strip(int M, int K, float alpha,
|
||||
}
|
||||
}
|
||||
#elif CV_SIMD_SCALABLE
|
||||
// Scalable vector types (e.g. RVV) are sizeless and cannot form arrays;
|
||||
// back the per-row accumulators with a scalar scratch buffer.
|
||||
// Process the strip in register-resident row blocks; the common thin case
|
||||
// M <= 4 (transformer token generation) is a single pass.
|
||||
const int NR = VTraits<v_float32>::vlanes();
|
||||
float acc_buf[FAST_GEMM_THIN_MAX_M * VTraits<v_float32>::max_nlanes];
|
||||
for (int m = 0; m < M; m++) vx_store(acc_buf + m * NR, vx_setzero_f32());
|
||||
|
||||
for (int k = 0; k < K; k++) {
|
||||
v_float32 bv = vx_load(b_strip + k * NR);
|
||||
for (int m = 0; m < M; m++) {
|
||||
v_float32 am = vx_setall_f32(A[m * lda0 + k * lda1]);
|
||||
v_float32 acc_m = vx_load(acc_buf + m * NR);
|
||||
vx_store(acc_buf + m * NR, v_fma(bv, am, acc_m));
|
||||
}
|
||||
}
|
||||
|
||||
const v_float32 v_alpha = vx_setall_f32(alpha);
|
||||
if (beta == 0.f) {
|
||||
for (int m = 0; m < M; m++)
|
||||
vx_store(c_strip + m * ldc, v_mul(vx_load(acc_buf + m * NR), v_alpha));
|
||||
} else if (beta == 1.f) {
|
||||
for (int m = 0; m < M; m++) {
|
||||
v_float32 cv = vx_load(c_strip + m * ldc);
|
||||
vx_store(c_strip + m * ldc, v_fma(vx_load(acc_buf + m * NR), v_alpha, cv));
|
||||
}
|
||||
} else {
|
||||
const v_float32 v_beta = vx_setall_f32(beta);
|
||||
for (int m = 0; m < M; m++) {
|
||||
v_float32 cv = vx_load(c_strip + m * ldc);
|
||||
cv = v_mul(cv, v_beta);
|
||||
vx_store(c_strip + m * ldc, v_fma(vx_load(acc_buf + m * NR), v_alpha, cv));
|
||||
}
|
||||
int m = 0;
|
||||
for (; m + 4 <= M; m += 4)
|
||||
fast_gemm_thin_block<4>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc);
|
||||
switch (M - m) {
|
||||
case 1: fast_gemm_thin_block<1>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc); break;
|
||||
case 2: fast_gemm_thin_block<2>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc); break;
|
||||
case 3: fast_gemm_thin_block<3>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc); break;
|
||||
}
|
||||
#else
|
||||
const int NR = FAST_GEMM_THIN_SCALAR_NR;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "layers_common.hpp"
|
||||
|
||||
#include <opencv2/dnn/shape_utils.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
/*
|
||||
Implementation of CumProd, as defined in ONNX specification:
|
||||
https://onnx.ai/onnx/operators/onnx__CumProd.html
|
||||
|
||||
Opset 26 is covered.
|
||||
*/
|
||||
class CumProdLayerImpl CV_FINAL : public CumProdLayer
|
||||
{
|
||||
public:
|
||||
CumProdLayerImpl(const LayerParams& params)
|
||||
{
|
||||
axis_raw = params.get<int>("axis", 0);
|
||||
exclusive_raw = params.get<int>("exclusive", 0);
|
||||
reverse_raw = params.get<int>("reverse", 0);
|
||||
setParamsFrom(params);
|
||||
}
|
||||
|
||||
bool supportBackend(int backendId) CV_OVERRIDE { return backendId == DNN_BACKEND_OPENCV; }
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>& inputs, const int,
|
||||
std::vector<MatShape>& outputs, std::vector<MatShape>&) const CV_OVERRIDE
|
||||
{
|
||||
outputs.assign(1, inputs[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
void getTypes(const std::vector<MatType>& inputs, const int, const int,
|
||||
std::vector<MatType>& outputs, std::vector<MatType>&) const CV_OVERRIDE
|
||||
{
|
||||
CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_64F ||
|
||||
inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F, "");
|
||||
outputs.assign(1, inputs[0]);
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE
|
||||
{
|
||||
if (inputs_arr.depth() == CV_16F)
|
||||
{
|
||||
forward_fallback(inputs_arr, outputs_arr, internals_arr);
|
||||
return;
|
||||
}
|
||||
std::vector<Mat> inputs, outputs;
|
||||
inputs_arr.getMatVector(inputs);
|
||||
outputs_arr.getMatVector(outputs);
|
||||
CV_CheckTypeEQ(inputs[0].depth(), outputs[0].depth(), "");
|
||||
|
||||
switch (inputs[0].depth())
|
||||
{
|
||||
case CV_32F: forwardImpl<float>(inputs, outputs); break;
|
||||
case CV_32S: forwardImpl<int32_t>(inputs, outputs); break;
|
||||
case CV_64S: forwardImpl<int64_t>(inputs, outputs); break;
|
||||
case CV_64F: forwardImpl<double>(inputs, outputs); break;
|
||||
default: CV_Error(Error::BadDepth, "");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void forwardImpl(const std::vector<Mat>& inputs, std::vector<Mat>& outputs)
|
||||
{
|
||||
const Mat& src_mat = inputs[0];
|
||||
const T* src_ptr = src_mat.ptr<T>();
|
||||
|
||||
int axis = inputs.size() > 1 ? parseAxis(inputs[1]) : axis_raw;
|
||||
axis = normalize_axis(axis, src_mat.dims);
|
||||
|
||||
Mat& dst_mat = outputs[0];
|
||||
T* dst_ptr = dst_mat.ptr<T>();
|
||||
|
||||
const bool exclusive = exclusive_raw == 1;
|
||||
const bool reverse = reverse_raw == 1;
|
||||
|
||||
// View data as [outer_size, target_size, inner_size] around the scan axis.
|
||||
const size_t outer_size = src_mat.total(0, axis);
|
||||
const size_t target_size = src_mat.size[axis];
|
||||
const size_t inner_size = src_mat.total(axis + 1);
|
||||
const size_t outer_step_length = target_size * inner_size;
|
||||
|
||||
const int target_start = reverse ? (int)target_size - 1 : 0;
|
||||
const int target_stop = reverse ? -1 : (int)target_size;
|
||||
const int target_delta = reverse ? -1 : 1;
|
||||
const int target_step = target_delta * (int)inner_size;
|
||||
const int exclusive_delta = exclusive ? target_step : 0;
|
||||
|
||||
// Each outer slice holds independent scans, so parallelize over it.
|
||||
parallel_for_(Range(0, (int)outer_size), [&](const Range& range)
|
||||
{
|
||||
for (int outer_idx = range.start; outer_idx < range.end; outer_idx++)
|
||||
{
|
||||
const size_t target_offset = (size_t)outer_idx * outer_step_length;
|
||||
|
||||
// First element: multiplicative identity when exclusive, else the source value.
|
||||
size_t first_inner_offset = target_offset + (size_t)target_start * inner_size;
|
||||
if (exclusive)
|
||||
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
|
||||
dst_ptr[first_inner_offset + inner_idx] = (T)1;
|
||||
else
|
||||
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
|
||||
dst_ptr[first_inner_offset + inner_idx] = src_ptr[first_inner_offset + inner_idx];
|
||||
|
||||
for (int target_idx = target_start + target_delta; target_idx != target_stop; target_idx += target_delta)
|
||||
{
|
||||
const size_t inner_offset = target_offset + (size_t)target_idx * inner_size;
|
||||
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
|
||||
{
|
||||
dst_ptr[inner_offset + inner_idx] = dst_ptr[inner_offset - target_step + inner_idx] *
|
||||
src_ptr[inner_offset - exclusive_delta + inner_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int64 getFLOPS(const std::vector<MatShape>& inputs, const std::vector<MatShape>&) const CV_OVERRIDE
|
||||
{
|
||||
return (int64)total(inputs[0]); // one multiply per element
|
||||
}
|
||||
|
||||
int parseAxis(const Mat& axis_mat)
|
||||
{
|
||||
CV_CheckEQ(axis_mat.total(), 1u, "Axis tensor should contain single value");
|
||||
if (axis_mat.type() == CV_32SC1)
|
||||
return axis_mat.at<int32_t>(0);
|
||||
Mat axis_mat_int;
|
||||
axis_mat.convertTo(axis_mat_int, CV_32SC1);
|
||||
return axis_mat_int.at<int32_t>(0);
|
||||
}
|
||||
|
||||
int axis_raw;
|
||||
int exclusive_raw;
|
||||
int reverse_raw;
|
||||
};
|
||||
|
||||
Ptr<CumProdLayer> CumProdLayer::create(const LayerParams& params)
|
||||
{
|
||||
return makePtr<CumProdLayerImpl>(params);
|
||||
}
|
||||
|
||||
}} // namespace cv::dnn
|
||||
@@ -3742,6 +3742,14 @@ class ChannelsPReLUImpl CV_FINAL : public ElementWiseLayer<ChannelsPReLUFunctor>
|
||||
public:
|
||||
using ElementWiseLayer<ChannelsPReLUFunctor>::ElementWiseLayer;
|
||||
|
||||
void setSlope(const Mat& slope) CV_OVERRIDE
|
||||
{
|
||||
slope.reshape(1, (int)slope.total()).convertTo(func.scale, CV_32F);
|
||||
#ifdef HAVE_OPENCL
|
||||
func.scale_umat.release();
|
||||
#endif
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr,
|
||||
OutputArrayOfArrays outputs_arr,
|
||||
OutputArrayOfArrays internals_arr) CV_OVERRIDE
|
||||
@@ -3849,6 +3857,13 @@ private:
|
||||
|
||||
Ptr<Layer> ChannelsPReLULayer::create(const LayerParams& params)
|
||||
{
|
||||
if (params.blobs.empty())
|
||||
{
|
||||
// Slope comes as a second input; constArgs() fills the scale in later.
|
||||
Ptr<ChannelsPReLUImpl> l(new ChannelsPReLUImpl(ChannelsPReLUFunctor()));
|
||||
l->setParamsFrom(params);
|
||||
return l;
|
||||
}
|
||||
CV_Assert(params.blobs.size() == 1);
|
||||
Mat scale = params.blobs[0];
|
||||
float slope = *scale.ptr<float>();
|
||||
|
||||
@@ -50,9 +50,12 @@ static void maxPool32f(const void* inp_, void* out_, const ConvState& cs)
|
||||
float* out = (float*)out_ + nc0*planesize;
|
||||
const float INITVAL = -FLT_MAX;
|
||||
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
int nlanes = VTraits<v_float32>::vlanes();
|
||||
v_float32 s_min = vx_setall_f32(INITVAL);
|
||||
// RVV (CV_SIMD_SCALABLE) disabled for the m1 switch: with the fixed C0=8 the
|
||||
// block is narrower than the register at VLEN>=512, tripping this assert. Runs
|
||||
// scalar on RVV; re-enable via v_setvlmax<v_float32>(C0) (#29493, cf #29180).
|
||||
CV_Assert(C0 == nlanes || C0 == nlanes*2 || C0 % (nlanes*4) == 0);
|
||||
#endif
|
||||
|
||||
@@ -65,13 +68,13 @@ static void maxPool32f(const void* inp_, void* out_, const ConvState& cs)
|
||||
y0 >= inner_y0 && y0 < inner_y1 ? inner_x0 : W;
|
||||
int yi_ = y0*SY - padY0;
|
||||
|
||||
#if !(CV_SIMD || CV_SIMD_SCALABLE)
|
||||
#if !(CV_SIMD)
|
||||
for (int c = 0; c < C0*W; c++)
|
||||
out[c] = INITVAL;
|
||||
#endif
|
||||
|
||||
for(;;) {
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
if (nlanes == C0) {
|
||||
for (; x0 < x1; x0++) {
|
||||
int xi_ = x0*SX - padX0;
|
||||
@@ -136,7 +139,7 @@ static void maxPool32f(const void* inp_, void* out_, const ConvState& cs)
|
||||
break;
|
||||
x1 = inner_x1;
|
||||
|
||||
#if CV_SIMD || CV_SIMD_SCALABLE
|
||||
#if CV_SIMD
|
||||
if (nlanes == C0) {
|
||||
for (; x0 < x1; x0++) {
|
||||
int xi_ = x0*SX - padX0;
|
||||
|
||||
@@ -417,7 +417,8 @@ public:
|
||||
}
|
||||
else if (!baseIsFloat && expIsFloat)
|
||||
{
|
||||
out_type = CV_32F;
|
||||
// ONNX Pow output type follows the base (X): integer base -> integer output.
|
||||
out_type = inputs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -52,7 +52,8 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
out.assign(1, MatShape(1, 1));
|
||||
// Reduced (mean/sum) loss is a rank-0 scalar in ONNX, not a [1] tensor.
|
||||
out.assign(1, MatShape::scalar());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -164,7 +165,7 @@ public:
|
||||
}
|
||||
}
|
||||
const float out = (reduction == LOSS_REDUCTION_SUM) ? static_cast<float>(num) : static_cast<float>((den > 0.0) ? (num / den) : 0.0);
|
||||
out_loss.at<float>(0) = out;
|
||||
out_loss.ptr<float>()[0] = out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -254,45 +254,31 @@ class LSTM2LayerImpl CV_FINAL : public LSTM2Layer
|
||||
batchSize = input[0].size[0];
|
||||
}
|
||||
|
||||
// ONNX LSTM inputs: X(0), W(1), R(2), B(3), sequence_lens(4),
|
||||
// initial_h(5), initial_c(6), P(7). Inputs 3..7 are optional and
|
||||
// may be present-but-empty (e.g. CNTK exports keep all 8 slots with
|
||||
// empties for unused ones). Gather by presence/non-emptiness rather
|
||||
// than by the raw input count so optional/empty inputs are ignored.
|
||||
auto hasInput = [&](int idx) {
|
||||
return idx < numInputs && !input[idx].empty();
|
||||
};
|
||||
|
||||
std::vector<Mat> blobs_;
|
||||
int hidShape [] = {1 + static_cast<int>(bidirectional), batchSize, numHidden};
|
||||
int biasShape [] = {1 + static_cast<int>(bidirectional), 8 * numHidden};
|
||||
|
||||
blobs_.push_back(input[1].clone());
|
||||
blobs_.push_back(input[2].clone());
|
||||
switch (numInputs) {
|
||||
case 3:
|
||||
// X, W, R are given
|
||||
// create bias
|
||||
blobs_.push_back(Mat::zeros(2, biasShape, input[0].type()));
|
||||
// create h0, c0
|
||||
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
|
||||
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
|
||||
break;
|
||||
case 4:
|
||||
// X, W, R, B are given
|
||||
blobs_.push_back(input[3]);
|
||||
// create h0, c0
|
||||
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
|
||||
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
|
||||
break;
|
||||
case 7:
|
||||
// X, W, R, B, h0, c0 are given
|
||||
blobs_.push_back(input[3]);
|
||||
blobs_.push_back(input[5]);
|
||||
blobs_.push_back(input[6]);
|
||||
break;
|
||||
case 8:
|
||||
// X, W, R, B, seqlen, h0, c0, P are given
|
||||
blobs_.push_back(input[3]);
|
||||
blobs_.push_back(input[5]);
|
||||
blobs_.push_back(input[6]);
|
||||
blobs_.push_back(input[7]);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsNotImplemented, "Insufficient inputs for LSTM layer. "
|
||||
"Required inputs: X, W, R, B, seqLen, h0, c0 [, P for peephole]");
|
||||
}
|
||||
CV_Assert(numInputs >= 3); // X, W, R are mandatory
|
||||
blobs_.push_back(input[1].clone()); // W
|
||||
blobs_.push_back(input[2].clone()); // R
|
||||
// B
|
||||
blobs_.push_back(hasInput(3) ? input[3] : Mat::zeros(2, biasShape, input[0].type()));
|
||||
// initial_h
|
||||
blobs_.push_back(hasInput(5) ? input[5] : Mat::zeros(3, hidShape, input[0].type()));
|
||||
// initial_c
|
||||
blobs_.push_back(hasInput(6) ? input[6] : Mat::zeros(3, hidShape, input[0].type()));
|
||||
// P (peephole) - only when the layer was configured to use it and the input is present
|
||||
if (usePeephole && hasInput(7))
|
||||
blobs_.push_back(input[7]);
|
||||
|
||||
// set outputs to 0
|
||||
for (auto& out : output)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "../net_impl.hpp"
|
||||
#include "layers_common.hpp"
|
||||
#include <opencv2/dnn.hpp>
|
||||
|
||||
namespace cv { namespace dnn {
|
||||
CV__DNN_INLINE_NS_BEGIN
|
||||
|
||||
// ONNX op: Scan — https://onnx.ai/onnx/operators/onnx__Scan.html
|
||||
// Supported opsets: 9 .. latest (opset-9 dataflow semantics).
|
||||
// Opset-8 Scan (leading batch dimension + optional sequence_lens input) has different
|
||||
// semantics and is NOT supported.
|
||||
//
|
||||
// Net::Impl::forwardGraph drives the loop; this layer only holds the body and its attributes.
|
||||
class ScanLayerImpl CV_FINAL : public ScanLayer
|
||||
{
|
||||
public:
|
||||
explicit ScanLayerImpl(const LayerParams& params)
|
||||
{
|
||||
setParamsFrom(params);
|
||||
num_scan_inputs = params.get<int>("num_scan_inputs");
|
||||
readIntList(params, "scan_input_axes", input_axes);
|
||||
readIntList(params, "scan_output_axes", output_axes);
|
||||
readIntList(params, "scan_input_directions", input_dirs);
|
||||
readIntList(params, "scan_output_directions", output_dirs);
|
||||
readIntList(params, "scan_output_ranks", output_ranks);
|
||||
}
|
||||
|
||||
std::vector<Ptr<Graph> >* subgraphs() const CV_OVERRIDE { return &body_; }
|
||||
bool dynamicOutputShapes() const CV_OVERRIDE { return true; }
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>&,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape>& outputs,
|
||||
std::vector<MatShape>& internals) const CV_OVERRIDE
|
||||
{
|
||||
outputs.assign(std::max(1, requiredOutputs), MatShape());
|
||||
internals.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
int numScanInputs() const CV_OVERRIDE { return num_scan_inputs; }
|
||||
const std::vector<int>& scanInputAxes() const CV_OVERRIDE { return input_axes; }
|
||||
const std::vector<int>& scanOutputAxes() const CV_OVERRIDE { return output_axes; }
|
||||
const std::vector<int>& scanInputDirections() const CV_OVERRIDE { return input_dirs; }
|
||||
const std::vector<int>& scanOutputDirections() const CV_OVERRIDE { return output_dirs; }
|
||||
const std::vector<int>& scanOutputRanks() const CV_OVERRIDE { return output_ranks; }
|
||||
|
||||
private:
|
||||
static void readIntList(const LayerParams& params, const std::string& name, std::vector<int>& out)
|
||||
{
|
||||
out.clear();
|
||||
if (!params.has(name)) return;
|
||||
const DictValue& v = params.get(name);
|
||||
out.resize(v.size());
|
||||
for (int i = 0; i < v.size(); ++i) out[i] = v.get<int>(i);
|
||||
}
|
||||
|
||||
int num_scan_inputs = 0;
|
||||
std::vector<int> input_axes, output_axes, input_dirs, output_dirs, output_ranks;
|
||||
mutable std::vector<Ptr<Graph> > body_;
|
||||
};
|
||||
|
||||
Ptr<ScanLayer> ScanLayer::create(const LayerParams& params)
|
||||
{
|
||||
return makePtr<ScanLayerImpl>(params);
|
||||
}
|
||||
CV__DNN_INLINE_NS_END
|
||||
}} // namespace cv::dnn
|
||||
@@ -49,7 +49,8 @@ public:
|
||||
for (size_t i = 2; i < x.size(); ++i) y.push_back(x[i]);
|
||||
out.push_back(y);
|
||||
} else {
|
||||
out.push_back(MatShape(1,1));
|
||||
// Reduced (mean/sum) loss is a rank-0 scalar in ONNX, not a [1] tensor.
|
||||
out.push_back(MatShape::scalar());
|
||||
}
|
||||
|
||||
if (requiredOutputs >= 2)
|
||||
@@ -325,7 +326,7 @@ public:
|
||||
|
||||
const float out = (reduction == LOSS_REDUCTION_SUM) ? static_cast<float>(num)
|
||||
: static_cast<float>((den > 0.0) ? (num / den) : 0.0);
|
||||
out_loss.at<float>(0) = out;
|
||||
out_loss.ptr<float>()[0] = out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include "net_impl.hpp"
|
||||
#include "opencv2/core/hal/intrin.hpp"
|
||||
|
||||
#ifdef HAVE_ONNXRUNTIME
|
||||
#include <onnxruntime_cxx_api.h>
|
||||
@@ -74,14 +73,10 @@ Net::Impl::Impl()
|
||||
// onnx_opset = 0;
|
||||
|
||||
accuracy = CV_32F;
|
||||
// The block size is a network-wide memory-layout contract, so it must not depend
|
||||
// on the SIMD width of the host: C0 == 8 is the mainstream, well-tested setting.
|
||||
// (Deriving it from vlanes() made the layout hardware-dependent; see the #29493 discussion.)
|
||||
defaultC0 = DEFAULT_C0;
|
||||
#if CV_SIMD_SCALABLE
|
||||
// RVV: the universal intrinsics use LMUL=2, so the float vector width is
|
||||
// (VLEN/32)*2 (16 at VLEN=256). The blocked-layout kernels fill a full
|
||||
// vector per channel block, so the net-wide block size must match that
|
||||
// width rather than the fixed default of 8 (#28852).
|
||||
defaultC0 = std::max((int)DEFAULT_C0, VTraits<v_float32>::vlanes());
|
||||
#endif
|
||||
enableFP16 = haveFP16 = false;
|
||||
// FP16 is not ready yet in the new DNN engine
|
||||
// Ticket: https://github.com/opencv/opencv/issues/26196
|
||||
@@ -1201,7 +1196,18 @@ void Net::Impl::forward(std::vector<std::vector<Mat>>& outputBlobs,
|
||||
FPDenormalsIgnoreHintScope fp_denormals_ignore_scope;
|
||||
|
||||
if (mainGraph)
|
||||
CV_Error(Error::StsNotImplemented, "The new dnn engine doesn't support inference until a specified layer. If you want to run the whole model, please don't set the outputName argument in the forward() call. If you want to run the model until a specified layer, please use the old dnn engine");
|
||||
{
|
||||
// In the new engine every requested name maps to a single graph output tensor,
|
||||
// so each nested list holds exactly one blob.
|
||||
std::vector<std::string> names(outBlobNames.begin(), outBlobNames.end());
|
||||
std::vector<Mat> flat;
|
||||
forwardWithMultipleOutputs(flat, names);
|
||||
CV_Assert(flat.size() == outBlobNames.size());
|
||||
outputBlobs.resize(outBlobNames.size());
|
||||
for (size_t i = 0; i < outBlobNames.size(); i++)
|
||||
outputBlobs[i].assign(1, flat[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LayerPin> pins;
|
||||
for (int i = 0; i < outBlobNames.size(); i++)
|
||||
@@ -1636,21 +1642,20 @@ void Net::Impl::setInput(InputArray blob, const String& name, double scalefactor
|
||||
|
||||
Mat Net::Impl::getParam(int layer, int numParam) const
|
||||
{
|
||||
LayerData& ld = getLayerData(layer);
|
||||
std::vector<Mat>& layerBlobs = getLayerInstance(ld)->blobs;
|
||||
std::vector<Mat>& layerBlobs = getLayer(layer)->blobs;
|
||||
CV_Assert(numParam < (int)layerBlobs.size());
|
||||
return layerBlobs[numParam];
|
||||
}
|
||||
|
||||
void Net::Impl::setParam(int layer, int numParam, const Mat& blob)
|
||||
{
|
||||
LayerData& ld = getLayerData(layer);
|
||||
|
||||
// FIXIT we should not modify "execution" instance
|
||||
std::vector<Mat>& layerBlobs = getLayerInstance(ld)->blobs;
|
||||
std::vector<Mat>& layerBlobs = getLayer(layer)->blobs;
|
||||
CV_Assert(numParam < (int)layerBlobs.size());
|
||||
// we don't make strong checks, use this function carefully
|
||||
layerBlobs[numParam] = blob;
|
||||
if (mainGraph)
|
||||
finalizeLayers = true;
|
||||
}
|
||||
|
||||
void Net::Impl::setParam(const std::string& outputTensorName, int numParam, const Mat& blob)
|
||||
@@ -1662,9 +1667,16 @@ void Net::Impl::setParam(const std::string& outputTensorName, int numParam, cons
|
||||
if (excl != std::string::npos)
|
||||
it = argnames.find(outputTensorName.substr(excl + 1));
|
||||
}
|
||||
if (it == argnames.end())
|
||||
if (it == argnames.end()) {
|
||||
// Not a tensor name; try it as a layer name.
|
||||
int lid = getLayerId(outputTensorName);
|
||||
if (lid >= 0) {
|
||||
setParam(lid, numParam, blob);
|
||||
return;
|
||||
}
|
||||
CV_Error_(Error::StsObjectNotFound,
|
||||
("DNN: tensor '%s' not found in the graph", outputTensorName.c_str()));
|
||||
}
|
||||
|
||||
int targetIdx = (int)it->second;
|
||||
const std::vector<Ptr<Layer>>& prog = mainGraph->prog();
|
||||
@@ -2466,6 +2478,21 @@ std::vector<String> Net::Impl::getUnconnectedOutLayersNames() /*const*/
|
||||
}
|
||||
|
||||
|
||||
// The new graph engine has no FP16 execution path yet: it runs FP32 on CPU regardless
|
||||
// of the requested (e.g. OpenCL FP16) target. Map FP16 input types to FP32 so that
|
||||
// shape/FLOPS inference through Layer::getTypes() does not reject them.
|
||||
static std::vector<MatType> filterFP16InputTypes(const std::vector<MatType>& types)
|
||||
{
|
||||
std::vector<MatType> result = types;
|
||||
for (MatType& t : result)
|
||||
{
|
||||
if (t == CV_16F)
|
||||
t = CV_32F;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int64 Net::Impl::getFLOPSGraph(const Ptr<Graph>& graph,
|
||||
const std::vector<MatShape>& shapeCache,
|
||||
const std::vector<MatType>& typeCache) const
|
||||
@@ -2524,10 +2551,14 @@ int64 Net::Impl::getFLOPS(const std::vector<MatShape>& netInputShapes,
|
||||
const std::vector<MatType>& netInputTypes) /*const*/
|
||||
{
|
||||
if (mainGraph) {
|
||||
// The new graph engine executes in FP32 on CPU regardless of the requested
|
||||
// target, so FP16 input types (e.g. coming from an OpenCL FP16 target) would be
|
||||
// rejected by Layer::getTypes(). Normalize them to FP32 for shape/FLOPS inference.
|
||||
std::vector<MatType> inputTypes = filterFP16InputTypes(netInputTypes);
|
||||
LayerShapes shapes;
|
||||
std::vector<MatShape> shapeCache;
|
||||
std::vector<MatType> typeCache;
|
||||
tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache);
|
||||
tryInferShapes(netInputShapes, inputTypes, shapes, shapeCache, typeCache);
|
||||
return getFLOPSGraph(mainGraph, shapeCache, typeCache);
|
||||
}
|
||||
|
||||
@@ -2553,10 +2584,11 @@ int64 Net::Impl::getFLOPS(
|
||||
const std::vector<MatType>& netInputTypes) /*const*/
|
||||
{
|
||||
if (mainGraph) {
|
||||
std::vector<MatType> inputTypes = filterFP16InputTypes(netInputTypes);
|
||||
LayerShapes shapes;
|
||||
std::vector<MatShape> shapeCache;
|
||||
std::vector<MatType> typeCache;
|
||||
tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache);
|
||||
tryInferShapes(netInputShapes, inputTypes, shapes, shapeCache, typeCache);
|
||||
|
||||
CV_Assert(0 <= layerId && layerId < (int)totalLayers);
|
||||
int localIdx = layerId;
|
||||
|
||||
+154
-37
@@ -5,7 +5,6 @@
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include "net_impl.hpp"
|
||||
#include "opencv2/core/hal/intrin.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
@@ -546,16 +545,6 @@ void Net::Impl::prepareForInference()
|
||||
#endif
|
||||
|
||||
if (!prepared) {
|
||||
#if CV_SIMD_SCALABLE
|
||||
// RVV (#28852): keep quantized graphs at C0=8. The int8 kernels are hardwired to
|
||||
// C0=8 (VNNI/NEON weight packing + per-channel quantization) and run scalar on RVV,
|
||||
// so a wider block gives no benefit and breaks them. Only fp32 graphs use the wider
|
||||
// vlanes()-based defaultC0. Signed-int8 (CV_8S) args are the quantization signature
|
||||
// (uint8 image inputs are CV_8U, so they don't trigger this).
|
||||
for (const ArgData& a : args) {
|
||||
if (a.type == CV_8S) { defaultC0 = 8; break; }
|
||||
}
|
||||
#endif
|
||||
fuseQDQ();
|
||||
constFold();
|
||||
fuseBN();
|
||||
@@ -700,6 +689,24 @@ void Net::Impl::forwardMainGraph(InputArrayOfArrays inputs, OutputArrayOfArrays
|
||||
kvCacheManager.applyRoutes();
|
||||
}
|
||||
|
||||
// Assign a single result to an output array, including a (pre-allocated) vector
|
||||
// of Mat/UMat, which _OutputArray::assign(Mat) does not handle.
|
||||
static void assignSingleOutput(OutputArrayOfArrays outputBlobs, const Mat& result)
|
||||
{
|
||||
_InputArray::KindFlag k = outputBlobs.kind();
|
||||
if (k == _InputArray::STD_VECTOR_MAT) {
|
||||
std::vector<Mat>& v = outputBlobs.getMatVecRef();
|
||||
v.resize(1);
|
||||
result.copyTo(v[0]);
|
||||
} else if (k == _InputArray::STD_VECTOR_UMAT) {
|
||||
std::vector<UMat>& v = outputBlobs.getUMatVecRef();
|
||||
v.resize(1);
|
||||
result.copyTo(v[0]);
|
||||
} else {
|
||||
outputBlobs.assign(result);
|
||||
}
|
||||
}
|
||||
|
||||
void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayOfArrays outputBlobs)
|
||||
{
|
||||
#ifdef HAVE_ONNXRUNTIME
|
||||
@@ -726,7 +733,7 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
|
||||
std::vector<int> outIdxs(1, outIdx);
|
||||
std::vector<Mat> outs = runOrtSession(netInputLayer->blobs, outIdxs);
|
||||
CV_Assert(outs.size() == 1);
|
||||
outputBlobs.assign(outs[0]);
|
||||
assignSingleOutput(outputBlobs, outs[0]);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -754,7 +761,7 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
|
||||
const std::vector<Arg>& gr_outputs = mainGraph->outputs();
|
||||
for (size_t i = 0; i < gr_outputs.size(); i++) {
|
||||
if (gr_outputs[i].idx == targetArg.idx) {
|
||||
outputBlobs.assign(outs[i]);
|
||||
assignSingleOutput(outputBlobs, outs[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -771,9 +778,9 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
|
||||
if (result.shape().layout == DATA_LAYOUT_BLOCK) {
|
||||
Mat converted;
|
||||
transformLayout(result, converted, originalLayout, originalLayout, defaultC0);
|
||||
outputBlobs.assign(converted);
|
||||
assignSingleOutput(outputBlobs, converted);
|
||||
} else {
|
||||
outputBlobs.assign(result.clone());
|
||||
assignSingleOutput(outputBlobs, result.clone());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -782,7 +789,7 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
|
||||
std::vector<Mat> inps, outs;
|
||||
forwardMainGraph(inps, outs);
|
||||
CV_Assert(!outs.empty());
|
||||
outputBlobs.assign(outs[0]);
|
||||
assignSingleOutput(outputBlobs, outs[0]);
|
||||
}
|
||||
|
||||
void Net::Impl::forwardWithMultipleOutputs(OutputArrayOfArrays outblobs, const std::vector<std::string>& outnames)
|
||||
@@ -859,45 +866,44 @@ void Net::Impl::forwardWithMultipleOutputs(OutputArrayOfArrays outblobs, const s
|
||||
const std::vector<Arg>& outargs = mainGraph->outputs();
|
||||
std::vector<int> outidxs;
|
||||
int i, j, noutputs = (int)outargs.size();
|
||||
if (!outnames.empty()) {
|
||||
CV_CheckEQ((int)outnames.size(), noutputs, "the number of requested and actual outputs must be the same");
|
||||
if (noutputs == 1 && outnames[0].empty())
|
||||
;
|
||||
else {
|
||||
for (i = 0; i < noutputs; i++) {
|
||||
const std::string& outname = outnames[i];
|
||||
for (j = 0; j < noutputs; j++) {
|
||||
const ArgData& adata = args.at(outargs[j].idx);
|
||||
if (adata.name == outname) {
|
||||
outidxs.push_back((int)j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == noutputs) {
|
||||
CV_Error_(Error::StsObjectNotFound, ("the required output '%s' is not found", outname.c_str()));
|
||||
if (outnames.empty() || (noutputs == 1 && outnames.size() == 1 && outnames[0].empty())) {
|
||||
for (i = 0; i < noutputs; i++)
|
||||
outidxs.push_back(i);
|
||||
} else {
|
||||
for (i = 0; i < (int)outnames.size(); i++) {
|
||||
const std::string& outname = outnames[i];
|
||||
for (j = 0; j < noutputs; j++) {
|
||||
const ArgData& adata = args.at(outargs[j].idx);
|
||||
if (adata.name == outname) {
|
||||
outidxs.push_back((int)j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == noutputs) {
|
||||
CV_Error_(Error::StsObjectNotFound, ("the required output '%s' is not found", outname.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<Mat> inps={}, outs;
|
||||
forwardMainGraph(inps, outs);
|
||||
CV_Assert(outs.size() == noutputs);
|
||||
int nout = (int)outidxs.size();
|
||||
std::vector<Mat>* outMats = nullptr;
|
||||
std::vector<UMat>* outUMats = nullptr;
|
||||
_InputArray::KindFlag outKind = outblobs.kind();
|
||||
if (outKind == _InputArray::STD_VECTOR_MAT) {
|
||||
outMats = &outblobs.getMatVecRef();
|
||||
outMats->resize(noutputs);
|
||||
outMats->resize(nout);
|
||||
} else if (outKind == _InputArray::STD_VECTOR_UMAT) {
|
||||
outUMats = &outblobs.getUMatVecRef();
|
||||
outUMats->resize(noutputs);
|
||||
outUMats->resize(nout);
|
||||
} else if (outKind == _InputArray::MAT || outKind == _InputArray::UMAT) {
|
||||
CV_Assert(noutputs == 1);
|
||||
CV_Assert(nout == 1);
|
||||
} else {
|
||||
CV_Error(Error::StsBadArg, "outputs must be Mat, UMat, a vector of Mat's or a vector of UMat's");
|
||||
}
|
||||
for (i = 0; i < noutputs; i++) {
|
||||
int j = outidxs.empty() ? i : outidxs[i];
|
||||
for (i = 0; i < nout; i++) {
|
||||
int j = outidxs[i];
|
||||
Mat src = outs[j];
|
||||
if (outMats) {
|
||||
src.copyTo(outMats->at(i));
|
||||
@@ -1127,6 +1133,49 @@ void Net::Impl::setGraphInput(Ptr<Graph>& graph, size_t idx, const Mat& m)
|
||||
}
|
||||
}
|
||||
|
||||
// Slice a Scan input at index `idx` along `axis`, removing that axis (contiguous result).
|
||||
static Mat sliceScanAxis(const Mat& m, int axis, int idx)
|
||||
{
|
||||
std::vector<Range> r(m.dims, Range::all());
|
||||
r[axis] = Range(idx, idx + 1);
|
||||
Mat sub = m(r).clone();
|
||||
std::vector<int> ns;
|
||||
for (int d = 0; d < m.dims; d++)
|
||||
if (d != axis) ns.push_back(m.size[d]);
|
||||
if (ns.empty()) ns.push_back(1);
|
||||
return sub.reshape(0, (int)ns.size(), &ns[0]);
|
||||
}
|
||||
|
||||
// Stack per-iteration Scan outputs into one tensor with a new axis at `axis`.
|
||||
static Mat stackScanAxis(const std::vector<Mat>& perIter, int axis, bool reverse)
|
||||
{
|
||||
if (perIter.empty()) return Mat();
|
||||
const Mat& first = perIter[0];
|
||||
const int e = first.dims, T = (int)perIter.size();
|
||||
if (axis < 0) axis += e + 1;
|
||||
CV_Assert(axis >= 0 && axis <= e);
|
||||
|
||||
std::vector<int> os, ss;
|
||||
for (int d = 0; d < e; d++) {
|
||||
if (d == axis) { os.push_back(T); ss.push_back(1); }
|
||||
os.push_back(first.size[d]);
|
||||
ss.push_back(first.size[d]);
|
||||
}
|
||||
if (axis == e) { os.push_back(T); ss.push_back(1); }
|
||||
|
||||
Mat stacked;
|
||||
stacked.create((int)os.size(), &os[0], first.type());
|
||||
for (int k = 0; k < T; k++) {
|
||||
int idx = reverse ? (T - 1 - k) : k;
|
||||
std::vector<Range> r(os.size(), Range::all());
|
||||
r[axis] = Range(k, k + 1);
|
||||
Mat dst = stacked(r);
|
||||
Mat src = perIter[idx].reshape(0, (int)ss.size(), &ss[0]);
|
||||
src.copyTo(dst);
|
||||
}
|
||||
return stacked;
|
||||
}
|
||||
|
||||
void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
|
||||
OutputArrayOfArrays outputs_, bool isMainGraph)
|
||||
{
|
||||
@@ -1214,6 +1263,7 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
|
||||
else {
|
||||
Ptr<IfLayer> iflayer = layer.dynamicCast<IfLayer>();
|
||||
Ptr<LoopLayer> loopLayer = layer.dynamicCast<LoopLayer>();
|
||||
Ptr<ScanLayer> scanLayer = layer.dynamicCast<ScanLayer>();
|
||||
if (iflayer) {
|
||||
int branch = iflayer->branch(inpMats[0]);
|
||||
Ptr<Graph> subgraph = subgraphs->at(branch);
|
||||
@@ -1306,6 +1356,73 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
|
||||
outMats[n_state + i] = stacked;
|
||||
}
|
||||
}
|
||||
else if (scanLayer) {
|
||||
CV_Assert(subgraphs->size() == 1);
|
||||
Ptr<Graph> body = subgraphs->at(0);
|
||||
|
||||
const int M = scanLayer->numScanInputs();
|
||||
const int bodyNIn = (int)body->inputs().size();
|
||||
const int S = bodyNIn - M; // loop-carried state count
|
||||
const int K = (int)body->outputs().size() - S; // scan output count
|
||||
CV_Assert(M > 0 && S >= 0 && K >= 0);
|
||||
|
||||
// Reject opset-8 (batch dim + sequence_lens); those semantics are not implemented.
|
||||
CV_CheckEQ((int)inpMats.size(), S + M,
|
||||
"Scan: opset-8 form (batch dim + sequence_lens) is not supported; re-export with opset>=9");
|
||||
|
||||
const std::vector<int>& iax = scanLayer->scanInputAxes();
|
||||
const std::vector<int>& oax = scanLayer->scanOutputAxes();
|
||||
const std::vector<int>& idir = scanLayer->scanInputDirections();
|
||||
const std::vector<int>& odir = scanLayer->scanOutputDirections();
|
||||
const std::vector<int>& orank = scanLayer->scanOutputRanks();
|
||||
|
||||
std::vector<Mat> scanIn(M);
|
||||
std::vector<int> inAxis(M);
|
||||
std::vector<char> inRev(M);
|
||||
int T = -1;
|
||||
for (int j = 0; j < M; j++) {
|
||||
scanIn[j] = inpMats[S + j];
|
||||
int ax = iax.empty() ? 0 : iax[j];
|
||||
if (ax < 0) ax += scanIn[j].dims;
|
||||
inAxis[j] = ax;
|
||||
inRev[j] = (char)(!idir.empty() && idir[j] != 0);
|
||||
int len = scanIn[j].size[ax];
|
||||
if (T < 0) T = len; else CV_Assert(T == len);
|
||||
}
|
||||
CV_Assert(T >= 0);
|
||||
|
||||
std::vector<Mat> state(S);
|
||||
for (int i = 0; i < S; i++) state[i] = inpMats[i];
|
||||
|
||||
std::vector<std::vector<Mat> > history(K);
|
||||
std::vector<Mat> inputs(bodyNIn), outputs;
|
||||
|
||||
for (int t = 0; t < T; t++) {
|
||||
for (int i = 0; i < S; i++) inputs[i] = state[i];
|
||||
for (int j = 0; j < M; j++) {
|
||||
int idx = inRev[j] ? (T - 1 - t) : t;
|
||||
inputs[S + j] = sliceScanAxis(scanIn[j], inAxis[j], idx);
|
||||
}
|
||||
forwardGraph(body, inputs, outputs, false);
|
||||
// Deep-copy: body buffers are recycled across iterations.
|
||||
// TODO: alias state in/out buffers like Loop to drop this copy.
|
||||
for (int i = 0; i < S; i++) state[i] = outputs[i].clone();
|
||||
for (int k = 0; k < K; k++) history[k].push_back(outputs[S + k].clone());
|
||||
}
|
||||
|
||||
outMats.assign(state.begin(), state.end());
|
||||
outMats.resize(S + K);
|
||||
for (int k = 0; k < K; k++) {
|
||||
int ax = oax.empty() ? 0 : oax[k];
|
||||
bool rev = !odir.empty() && odir[k] != 0;
|
||||
// 0-D scalar output is stored as [1]; drop it so T scalars stack to [T], not [T,1].
|
||||
if (k < (int)orank.size() && orank[k] == 0) {
|
||||
for (Mat& e : history[k])
|
||||
e = e.reshape(0, 0, nullptr);
|
||||
}
|
||||
outMats[S + k] = stackScanAxis(history[k], ax, rev);
|
||||
}
|
||||
}
|
||||
else {
|
||||
CV_Error_(Error::StsNotImplemented,
|
||||
("unknown layer type '%s' with subgraphs", layer->type.c_str()));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -202,12 +202,14 @@ protected:
|
||||
void parseClip (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConcat (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseLoop (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseScan (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseIf (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConstant (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConstantOfShape (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConv (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConvTranspose (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseCumSum (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseCumProd (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseDepthSpaceOps (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseDetectionOutput (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parsePriorBox (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
@@ -285,6 +287,7 @@ protected:
|
||||
// URL: https://github.com/microsoft/onnxruntime/blob/master/docs/ContribOperators.md
|
||||
void parseAttention (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseAttentionOnnxAi (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseCausalConvWithState (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseSDPA (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseDequantizeLinear (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseQuantizeLinear (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
@@ -719,7 +722,7 @@ Net ONNXImporter2::parseModel()
|
||||
sstrm << "DNN/ONNX: the model ";
|
||||
if (!onnxFilename.empty())
|
||||
sstrm << "'" << onnxFilename << "' ";
|
||||
sstrm << "cannot be loaded with the new parser. Trying the older parser. ";
|
||||
sstrm << "cannot be loaded by the DNN engine.";
|
||||
if (!missing_ops.empty()) {
|
||||
sstrm << " Unsupported operations:\n";
|
||||
auto it = missing_ops.begin();
|
||||
@@ -740,19 +743,25 @@ Net ONNXImporter2::parseModel()
|
||||
bool ONNXImporter2::parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoProto, ArgData& data)
|
||||
{
|
||||
CV_Assert(valueInfoProto.has_name());
|
||||
CV_Assert(valueInfoProto.has_type());
|
||||
// Subgraph body value-infos may omit type/shape; leave unset, inferred at runtime.
|
||||
if (!valueInfoProto.has_type())
|
||||
return true;
|
||||
const opencv_onnx::TypeProto& typeProto = valueInfoProto.type();
|
||||
CV_Assert(typeProto.has_tensor_type());
|
||||
if (!typeProto.has_tensor_type())
|
||||
return true;
|
||||
const opencv_onnx::TypeProto::Tensor& tensor = typeProto.tensor_type();
|
||||
CV_Assert(tensor.has_shape());
|
||||
const opencv_onnx::TensorShapeProto& tensorShape = tensor.shape();
|
||||
auto elem_type = tensor.elem_type();
|
||||
|
||||
data.type = dataType2cv(elem_type);
|
||||
if (data.type < 0) {
|
||||
CV_Error(Error::StsNotImplemented, format("unsupported datatype '%s'", dataType2str(elem_type).c_str()));
|
||||
if (tensor.has_elem_type()) {
|
||||
auto elem_type = tensor.elem_type();
|
||||
data.type = dataType2cv(elem_type);
|
||||
if (data.type < 0) {
|
||||
CV_Error(Error::StsNotImplemented, format("unsupported datatype '%s'", dataType2str(elem_type).c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!tensor.has_shape())
|
||||
return true;
|
||||
const opencv_onnx::TensorShapeProto& tensorShape = tensor.shape();
|
||||
int dim_size = tensorShape.dim_size();
|
||||
CV_CheckGE(dim_size, 0, "");
|
||||
MatShape shape(dim_size);
|
||||
@@ -768,21 +777,6 @@ bool ONNXImporter2::parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoP
|
||||
} else {
|
||||
// ONNX allows dimensions without dim_value and dim_param.
|
||||
// Treat them as unnamed symbolic dimensions.
|
||||
// NOTE: LSTM with unnamed dimensions is not ready in the new graph
|
||||
// engine yet, so force fallback to classic parser.
|
||||
if (curr_graph_proto)
|
||||
{
|
||||
const int n_nodes = curr_graph_proto->node_size();
|
||||
for (int i = 0; i < n_nodes; ++i)
|
||||
{
|
||||
const std::string& op = curr_graph_proto->node(i).op_type();
|
||||
if (op == "LSTM")
|
||||
{
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
val_j = net.findDim("", true);
|
||||
}
|
||||
//CV_Assert(0 <= val_j && val_j <= INT_MAX);
|
||||
@@ -1354,7 +1348,9 @@ void ONNXImporter2::parseLSTM(LayerParams& layerParams, const opencv_onnx::NodeP
|
||||
layerParams.set("produce_sequence_y", need_y);
|
||||
|
||||
|
||||
if (lstm_proto.input_size() == 8)
|
||||
// The 8th input (P, peephole weights) is optional; an absent ONNX input is
|
||||
// encoded as an empty name. Only enable peephole when it is actually present.
|
||||
if (lstm_proto.input_size() == 8 && !lstm_proto.input(7).empty())
|
||||
layerParams.set("use_peephole", true);
|
||||
|
||||
|
||||
@@ -1462,9 +1458,17 @@ void ONNXImporter2::parsePRelu(LayerParams& layerParams, const opencv_onnx::Node
|
||||
{
|
||||
layerParams.type = "PReLU";
|
||||
CV_Assert(node_inputs.size() == 2);
|
||||
CV_Assert(net.isConstArg(node_inputs[1]));
|
||||
layerParams.blobs.push_back(net.argTensor(node_inputs[1]));
|
||||
addLayer(layerParams, node_proto, 1);
|
||||
if (net.isConstArg(node_inputs[1]))
|
||||
{
|
||||
layerParams.blobs.push_back(net.argTensor(node_inputs[1]));
|
||||
addLayer(layerParams, node_proto, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slope produced by a foldable subgraph (e.g. Reshape of an initializer):
|
||||
// keep it as a second input for constFold()/constArgs() to resolve.
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseLpNormalization(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
@@ -1721,6 +1725,46 @@ void ONNXImporter2::parseLoop(LayerParams& layerParams,
|
||||
*loopLayer->subgraphs() = subgraphs;
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseScan(LayerParams& layerParams,
|
||||
const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
CV_Assert(layerParams.has("num_scan_inputs"));
|
||||
layerParams.type = "Scan";
|
||||
|
||||
std::vector<Arg> saved_inputs = node_inputs, saved_outputs = node_outputs;
|
||||
std::vector<Ptr<Graph> > subgraphs(1);
|
||||
for (int i = 0; i < node_proto.attribute_size(); ++i)
|
||||
{
|
||||
const auto& attr = node_proto.attribute(i);
|
||||
if (attr.name() == "body")
|
||||
{
|
||||
opencv_onnx::GraphProto body = attr.g();
|
||||
subgraphs[0] = parseGraph(&body, false);
|
||||
}
|
||||
}
|
||||
CV_Assert(!subgraphs[0].empty());
|
||||
node_inputs = saved_inputs;
|
||||
node_outputs = saved_outputs;
|
||||
|
||||
const int num_scan_inputs = layerParams.get<int>("num_scan_inputs");
|
||||
const int n_state = (int)subgraphs[0]->inputs().size() - num_scan_inputs;
|
||||
const int n_scan_out = (int)subgraphs[0]->outputs().size() - n_state;
|
||||
if (n_state >= 0 && n_scan_out > 0)
|
||||
{
|
||||
std::vector<int> scan_output_ranks(n_scan_out);
|
||||
for (int k = 0; k < n_scan_out; ++k)
|
||||
{
|
||||
const Arg& a = subgraphs[0]->outputs()[n_state + k];
|
||||
scan_output_ranks[k] = netimpl->args.at(a.idx).shape.dims;
|
||||
}
|
||||
layerParams.set("scan_output_ranks", DictValue::arrayInt(scan_output_ranks.data(), n_scan_out));
|
||||
}
|
||||
|
||||
addLayer(layerParams, node_proto);
|
||||
Ptr<Layer>& scanLayer = curr_prog.back();
|
||||
*scanLayer->subgraphs() = subgraphs;
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseIf(LayerParams& layerParams,
|
||||
const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
@@ -2120,6 +2164,22 @@ void ONNXImporter2::parseCumSum(LayerParams& layerParams, const opencv_onnx::Nod
|
||||
addLayer(layerParams, node_proto, ninputs);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseCumProd(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
int ninputs = node_proto.input_size();
|
||||
CV_Assert(ninputs == 2);
|
||||
layerParams.type = "CumProd";
|
||||
if (net.isConstArg(node_inputs[1]))
|
||||
{
|
||||
Mat axisTensor;
|
||||
net.argTensor(node_inputs[1]).convertTo(axisTensor, CV_32S);
|
||||
CV_Assert(axisTensor.total() == 1);
|
||||
layerParams.set("axis", axisTensor.at<int>(0));
|
||||
ninputs = 1;
|
||||
}
|
||||
addLayer(layerParams, node_proto, ninputs);
|
||||
}
|
||||
|
||||
// "Equal" "Greater" "Less" "Pow" "Add" "Sub" "Mul" "Div" "Sum" "Min" "Max" "GreaterOrEqual" "LessOrEqual" "And" "Or" "Xor"
|
||||
void ONNXImporter2::parseElementWise(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
|
||||
{
|
||||
@@ -2632,6 +2692,11 @@ void ONNXImporter2::parseSDPA(LayerParams& params, const opencv_onnx::NodeProto&
|
||||
addLayer(params, node_proto, 3);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseCausalConvWithState(LayerParams& params, const opencv_onnx::NodeProto& node_proto) {
|
||||
params.type = "CausalConvWithState";
|
||||
addLayer(params, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseRoiAlign(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
layerParams.type = "RoiAlign";
|
||||
@@ -2695,6 +2760,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
|
||||
dispatch["GatherElements"] = &ONNXImporter2::parseGatherElements;
|
||||
dispatch["Concat"] = &ONNXImporter2::parseConcat;
|
||||
dispatch["Loop"] = &ONNXImporter2::parseLoop;
|
||||
dispatch["Scan"] = &ONNXImporter2::parseScan;
|
||||
dispatch["If"] = &ONNXImporter2::parseIf;
|
||||
dispatch["Resize"] = &ONNXImporter2::parseResize2;
|
||||
dispatch["Size"] = &ONNXImporter2::parseSize;
|
||||
@@ -2724,6 +2790,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
|
||||
dispatch["DetectionOutput"] = &ONNXImporter2::parseDetectionOutput;
|
||||
dispatch["PriorBox"] = &ONNXImporter2::parsePriorBox;
|
||||
dispatch["CumSum"] = &ONNXImporter2::parseCumSum;
|
||||
dispatch["CumProd"] = &ONNXImporter2::parseCumProd;
|
||||
dispatch["SpaceToDepth"] = dispatch["DepthToSpace"] = &ONNXImporter2::parseDepthSpaceOps;
|
||||
dispatch["ScatterElements"] = dispatch["Scatter"] = dispatch["ScatterND"] = &ONNXImporter2::parseScatter;
|
||||
dispatch["Tile"] = &ONNXImporter2::parseTile;
|
||||
@@ -2770,6 +2837,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
|
||||
// Opset domain cannot be modified from onnx_graph_simplifier.cpp so this
|
||||
// operator cannot be parsed if only added in buildDispatchMap_COM_MICROSOFT
|
||||
dispatch["Attention"] = &ONNXImporter2::parseAttentionOnnxAi;
|
||||
dispatch["CausalConvWithState"] = &ONNXImporter2::parseCausalConvWithState;
|
||||
|
||||
domain_dispatch_map[str_domain_ai_onnx] = dispatch;
|
||||
}
|
||||
|
||||
@@ -3263,31 +3263,24 @@ void TFLayerHandler::handleFailed(const tensorflow::NodeDef& layer)
|
||||
|
||||
} // namespace
|
||||
|
||||
// The TensorFlow importer always runs on the OpenCV engine; ENGINE_AUTO resolves to it.
|
||||
static void warnIfUnsupportedTfEngine(int engine)
|
||||
{
|
||||
static const int engine_forced =
|
||||
(int)utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
if (engine_forced == ENGINE_OPENCV)
|
||||
engine = engine_forced;
|
||||
if (engine != ENGINE_AUTO && engine != ENGINE_OPENCV)
|
||||
CV_LOG_WARNING(NULL, "DNN/TF: only ENGINE_AUTO and ENGINE_OPENCV are supported; "
|
||||
"using ENGINE_OPENCV.");
|
||||
}
|
||||
|
||||
Net readNetFromTensorflow(const String &model, const String &config, int engine,
|
||||
const std::vector<String>& extraOutputs)
|
||||
{
|
||||
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
if(engine_forced != ENGINE_AUTO)
|
||||
engine = engine_forced;
|
||||
|
||||
if (engine == ENGINE_AUTO)
|
||||
{
|
||||
try
|
||||
{
|
||||
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(),
|
||||
true, extraOutputs);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "Can't parse model with the new dnn engine, trying to parse with the old dnn engine: " << e.what());
|
||||
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(),
|
||||
false, extraOutputs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(), engine == ENGINE_NEW || engine == ENGINE_AUTO, extraOutputs);
|
||||
}
|
||||
warnIfUnsupportedTfEngine(engine);
|
||||
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(),
|
||||
/*newEngine*/ true, extraOutputs);
|
||||
}
|
||||
|
||||
Net readNetFromTensorflow(const char* bufferModel, size_t lenModel,
|
||||
@@ -3295,26 +3288,9 @@ Net readNetFromTensorflow(const char* bufferModel, size_t lenModel,
|
||||
int engine,
|
||||
const std::vector<String>& extraOutputs)
|
||||
{
|
||||
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
if(engine_forced != ENGINE_AUTO)
|
||||
engine = engine_forced;
|
||||
|
||||
if (engine == ENGINE_AUTO)
|
||||
{
|
||||
try
|
||||
{
|
||||
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig, true, extraOutputs);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "Can't parse model with the new dnn engine, trying to parse with the old dnn engine: " << e.what());
|
||||
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig, false, extraOutputs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig, engine == ENGINE_NEW || engine == ENGINE_AUTO, extraOutputs);
|
||||
}
|
||||
warnIfUnsupportedTfEngine(engine);
|
||||
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig,
|
||||
/*newEngine*/ true, extraOutputs);
|
||||
}
|
||||
|
||||
Net readNetFromTensorflow(const std::vector<uchar>& bufferModel, const std::vector<uchar>& bufferConfig, int engine,
|
||||
|
||||
@@ -1503,10 +1503,20 @@ void TFLiteImporter::getQuantParams(const Operator& op, float& inpScale, int& in
|
||||
}
|
||||
}
|
||||
|
||||
Net readNetFromTFLite(const String &modelPath, int engine) {
|
||||
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
if(engine_forced != ENGINE_AUTO)
|
||||
// The TFLite importer always runs on the OpenCV engine; ENGINE_AUTO resolves to it.
|
||||
static void warnIfUnsupportedTFLiteEngine(int engine)
|
||||
{
|
||||
static const int engine_forced =
|
||||
(int)utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
if (engine_forced == ENGINE_OPENCV)
|
||||
engine = engine_forced;
|
||||
if (engine != ENGINE_AUTO && engine != ENGINE_OPENCV)
|
||||
CV_LOG_WARNING(NULL, "DNN/TFLite: only ENGINE_AUTO and ENGINE_OPENCV are supported; "
|
||||
"using ENGINE_OPENCV.");
|
||||
}
|
||||
|
||||
Net readNetFromTFLite(const String &modelPath, int engine) {
|
||||
warnIfUnsupportedTFLiteEngine(engine);
|
||||
|
||||
Net net;
|
||||
|
||||
@@ -1526,7 +1536,7 @@ Net readNetFromTFLite(const String &modelPath, int engine) {
|
||||
ifs.read(content.data(), sz);
|
||||
CV_Assert(!ifs.bad());
|
||||
|
||||
TFLiteImporter(net, content.data(), content.size(), engine == ENGINE_NEW || engine == ENGINE_AUTO);
|
||||
TFLiteImporter(net, content.data(), content.size(), /*newEngine*/ true);
|
||||
return net;
|
||||
}
|
||||
|
||||
@@ -1535,12 +1545,10 @@ Net readNetFromTFLite(const std::vector<uchar>& bufferModel, int engine) {
|
||||
}
|
||||
|
||||
Net readNetFromTFLite(const char *bufferModel, size_t bufSize, int engine) {
|
||||
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
if(engine_forced != ENGINE_AUTO)
|
||||
engine = engine_forced;
|
||||
warnIfUnsupportedTFLiteEngine(engine);
|
||||
|
||||
Net net;
|
||||
TFLiteImporter(net, bufferModel, bufSize, engine == ENGINE_NEW || engine == ENGINE_AUTO);
|
||||
TFLiteImporter(net, bufferModel, bufSize, /*newEngine*/ true);
|
||||
return net;
|
||||
}
|
||||
|
||||
|
||||
@@ -269,13 +269,6 @@ TEST_P(DNNTestNetwork, SSD_VGG16)
|
||||
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH);
|
||||
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
Mat sample = imread(findDataFile("dnn/street.png"));
|
||||
Mat inp = blobFromImage(sample, 1.0f, Size(300, 300), Scalar(), false);
|
||||
|
||||
@@ -90,17 +90,6 @@ TEST(Reproducibility_SSD, Accuracy)
|
||||
CV_TEST_TAG_DEBUG_VERYLONG
|
||||
);
|
||||
|
||||
// The classic engine importer no longer carries the Caffe-SSD specific
|
||||
// handling (LpNormalization/DetectionOutput); this model is supported on
|
||||
// the new engine only.
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
Net net = readNetFromONNX(findDataFile("dnn/onnx/models/ssd_vgg16.onnx", false));
|
||||
ASSERT_FALSE(net.empty());
|
||||
net.setPreferableBackend(DNN_BACKEND_OPENCV);
|
||||
|
||||
@@ -82,6 +82,12 @@ void normAssert(
|
||||
{
|
||||
cv::Mat refMat = ref.getMat();
|
||||
cv::Mat testMat = test.getMat();
|
||||
// 16-bit float types (half / bfloat16) are compared as float32: the two-arg
|
||||
// cvtest::norm requires identical types and does not down-convert them.
|
||||
if (refMat.depth() == CV_16F || refMat.depth() == CV_16BF)
|
||||
refMat.convertTo(refMat, CV_32F);
|
||||
if (testMat.depth() == CV_16F || testMat.depth() == CV_16BF)
|
||||
testMat.convertTo(testMat, CV_32F);
|
||||
const cv::MatShape refShape = refMat.shape();
|
||||
const cv::MatShape testShape = testMat.shape();
|
||||
const bool scalar1dCompatible =
|
||||
|
||||
@@ -55,13 +55,6 @@ static std::string _tf(TString filename)
|
||||
|
||||
TEST(Test_YOLO, read_yolov4_onnx)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
Net net = readNet(findDataFile("dnn/yolov4.onnx", false));
|
||||
ASSERT_FALSE(net.empty());
|
||||
}
|
||||
@@ -78,14 +71,6 @@ public:
|
||||
float nmsThreshold = 0.4, bool useWinograd = true,
|
||||
int zeroPadW = 0, Size inputSize = Size())
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
checkBackend();
|
||||
|
||||
Mat img1 = imread(_tf("dog416.png"));
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static Mat referenceMatMul(const Mat& A, const Mat& B, bool trans_a, float alpha)
|
||||
{
|
||||
const int M = trans_a ? A.cols : A.rows;
|
||||
const int K = trans_a ? A.rows : A.cols;
|
||||
const int N = B.cols;
|
||||
Mat expected(M, N, CV_32F);
|
||||
|
||||
for (int m = 0; m < M; m++)
|
||||
{
|
||||
float* dst = expected.ptr<float>(m);
|
||||
for (int n = 0; n < N; n++)
|
||||
{
|
||||
float acc = 0.f;
|
||||
for (int k = 0; k < K; k++)
|
||||
{
|
||||
const float a = trans_a ? A.ptr<float>(k)[m] : A.ptr<float>(m)[k];
|
||||
acc += a * B.ptr<float>(k)[n];
|
||||
}
|
||||
dst[n] = alpha * acc;
|
||||
}
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
|
||||
// Exercises the fastGemmThin path (constant-B MatMul). M covers every register
|
||||
// block width and the multi-block remainder, N covers full strips and partial
|
||||
// column tails for any VLEN <= 512.
|
||||
typedef testing::TestWithParam<tuple<int, int, int, int, float>> DNN_FastGemmThin;
|
||||
|
||||
TEST_P(DNN_FastGemmThin, MatMulAccuracy)
|
||||
{
|
||||
int M = get<0>(GetParam()), N = get<1>(GetParam()), K = get<2>(GetParam());
|
||||
int trans_a = get<3>(GetParam());
|
||||
float alpha = get<4>(GetParam());
|
||||
|
||||
RNG rng(0x5EED);
|
||||
Mat B(K, N, CV_32F); rng.fill(B, RNG::UNIFORM, -1.f, 1.f);
|
||||
Mat A(trans_a ? K : M, trans_a ? M : K, CV_32F);
|
||||
rng.fill(A, RNG::UNIFORM, -1.f, 1.f);
|
||||
|
||||
LayerParams lp;
|
||||
lp.type = "MatMul";
|
||||
lp.name = "thin_matmul";
|
||||
lp.set("transA", trans_a != 0);
|
||||
lp.set("transB", false);
|
||||
lp.set("alpha", alpha);
|
||||
lp.blobs.push_back(B);
|
||||
|
||||
Net net;
|
||||
net.addLayerToPrev(lp.name, lp.type, lp);
|
||||
net.setInputsNames(std::vector<String>{ "A" });
|
||||
net.setInput(A, "A");
|
||||
Mat actual = net.forward();
|
||||
Mat expected = referenceMatMul(A, B, trans_a != 0, alpha);
|
||||
|
||||
EXPECT_LE(cv::norm(expected, actual, NORM_INF), 2e-5f * std::max(K, 1))
|
||||
<< "M=" << M << ", N=" << N << ", K=" << K
|
||||
<< ", trans_a=" << trans_a << ", alpha=" << alpha;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, DNN_FastGemmThin, Combine(
|
||||
Values(1, 2, 3, 4, 5),
|
||||
Values(16, 19, 64, 67, 80, 83),
|
||||
Values(1, 3, 4, 7, 16, 64),
|
||||
Values(0, 1),
|
||||
Values(1.f, -0.5f)
|
||||
));
|
||||
|
||||
}} // namespace
|
||||
@@ -153,9 +153,7 @@ TEST_F(Test_Graph_Simplifier, BiasedMatMulSubgraph) {
|
||||
/* Test for 1 subgraphs
|
||||
- BiasedMatMulSubgraph
|
||||
*/
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
const std::string expected = engine_forced == cv::dnn::ENGINE_CLASSIC ? "MatMul" : "Gemm";
|
||||
const std::string expected = "Gemm";
|
||||
test("biased_matmul", expected);
|
||||
}
|
||||
|
||||
|
||||
@@ -2249,21 +2249,10 @@ TEST(Layer_LSTM, repeatedInference)
|
||||
|
||||
TEST(Layer_If, resize)
|
||||
{
|
||||
// Skip this test when the classic DNN engine is explicitly requested. The
|
||||
// "if" layer is supported only by the new engine.
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
// Mark the test as skipped and exit early.
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string imgname = findDataFile("cv/shared/lena.png", true);
|
||||
const std::string modelname = findDataFile("dnn/onnx/models/if_layer.onnx", true);
|
||||
|
||||
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_NEW);
|
||||
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
|
||||
Mat src = imread(imgname), blob;
|
||||
dnn::blobFromImage(src, blob, 1.0, cv::Size(), cv::Scalar(), false, false);
|
||||
|
||||
@@ -2286,16 +2275,8 @@ TEST(Layer_If, resize)
|
||||
|
||||
TEST(Layer_If, subgraph_name_scoping)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string modelname = findDataFile("dnn/onnx/models/subgraph_name_scoping.onnx", true);
|
||||
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_NEW);
|
||||
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
|
||||
|
||||
int xshape[1] = {2};
|
||||
Mat x(1, xshape, CV_32F);
|
||||
@@ -2331,16 +2312,8 @@ TEST(Layer_If, subgraph_name_scoping)
|
||||
|
||||
TEST(Layer_Size, onnx_1d)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string modelname = findDataFile("dnn/onnx/models/test_size_1d_model.onnx", true);
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_NEW);
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
|
||||
|
||||
int sz1d[1] = {7};
|
||||
cv::Mat x(1, sz1d, CV_32F);
|
||||
@@ -2358,16 +2331,8 @@ TEST(Layer_Size, onnx_1d)
|
||||
|
||||
TEST(Layer_Size, onnx_0d_scalar)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string modelname = findDataFile("dnn/onnx/models/test_size_0d_model.onnx", true);
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_NEW);
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
|
||||
|
||||
cv::Mat x(1, 1, CV_32F);
|
||||
x.at<float>(0, 0) = 3.14f;
|
||||
@@ -2430,20 +2395,11 @@ class TESTKVCache : public testing::TestWithParam<std::string>
|
||||
public:
|
||||
void testKVCache(const std::string& layout)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
// Mark the test as skipped and exit early.
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string model_path = "dnn/onnx/models/test_attention_kv_cache_" + layout + ".onnx";
|
||||
|
||||
Net netWithKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_NEW);
|
||||
Net netWithKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_OPENCV);
|
||||
netWithKVCache.enableKVCache();
|
||||
Net netWithoutKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_NEW);
|
||||
Net netWithoutKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_OPENCV);
|
||||
|
||||
int T = 523, Nq = 8, Nkv = 4, D = 256;
|
||||
// Keep the prefill larger than one cache page, then exercise generation
|
||||
|
||||
@@ -798,13 +798,6 @@ TEST_P(Reproducibility_ViT_ONNX, Accuracy)
|
||||
Target targetId = GetParam();
|
||||
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_1GB : CV_TEST_TAG_MEMORY_2GB);
|
||||
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
|
||||
auto engine_forced = static_cast<EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
|
||||
if (engine_forced == ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string modelname = _tf("vit_base_patch16_224_Opset16.onnx", false);
|
||||
Net net = readNetFromONNX(modelname);
|
||||
@@ -832,7 +825,7 @@ TEST_P(Reproducibility_ViT_ONNX, Accuracy)
|
||||
topK(out, res, K);
|
||||
ASSERT_EQ(int(res.size()), K);
|
||||
|
||||
// Reference top-5 captured from the ONNX Runtime engine (OPENCV_FORCE_DNN_ENGINE=4).
|
||||
// Reference top-5 captured from the ONNX Runtime engine (OPENCV_FORCE_DNN_ENGINE=2, ENGINE_ORT).
|
||||
std::vector<std::pair<int, float> > ref = {
|
||||
{285, 7.683f}, {282, 7.182f}, {281, 6.894f}, {287, 3.623f}, {283, 3.287f}
|
||||
};
|
||||
@@ -859,13 +852,6 @@ TEST_P(Reproducibility_BERT_ONNX, Accuracy)
|
||||
|
||||
Target targetId = GetParam();
|
||||
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
|
||||
auto engine_forced = static_cast<EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
|
||||
if (engine_forced == ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string modelname = _tf("onnx/models/bert.onnx", false);
|
||||
Net net = readNetFromONNX(modelname);
|
||||
@@ -931,14 +917,6 @@ typedef testing::TestWithParam<Target> Reproducibility_MobileNetSSD_ONNX;
|
||||
TEST_P(Reproducibility_MobileNetSSD_ONNX, Accuracy)
|
||||
{
|
||||
Target targetId = GetParam();
|
||||
auto engine_forced = static_cast<EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
|
||||
if (engine_forced == ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
|
||||
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
|
||||
|
||||
@@ -1265,14 +1243,6 @@ INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOXS_ONNX,
|
||||
typedef testing::TestWithParam<Target> Reproducibility_BlazeFace_ONNX;
|
||||
TEST_P(Reproducibility_BlazeFace_ONNX, Accuracy)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
Target targetId = GetParam();
|
||||
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
|
||||
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
|
||||
@@ -1381,16 +1351,8 @@ TEST_P(Reproducibility_SwinIR_ONNX, Accuracy)
|
||||
Target targetId = GetParam();
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_512MB, CV_TEST_TAG_LONG);
|
||||
|
||||
auto engine_forced = static_cast<EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
|
||||
if (engine_forced == ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string modelname = _tf("onnx/models/swinir_x4_gan.onnx", false);
|
||||
Net net = readNetFromONNX(modelname, ENGINE_NEW);
|
||||
Net net = readNetFromONNX(modelname, ENGINE_OPENCV);
|
||||
ASSERT_FALSE(net.empty());
|
||||
|
||||
net.setPreferableBackend(DNN_BACKEND_OPENCV);
|
||||
|
||||
@@ -109,13 +109,6 @@ TEST(SoftNMS, Accuracy)
|
||||
// NMS with dynamic output shapes is only supported by the new engine.
|
||||
TEST(NMS, ZeroDetections_Reshape)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string onnxmodel = findDataFile("dnn/onnx/models/nms_reshape_empty.onnx");
|
||||
cv::dnn::Net net = cv::dnn::readNetFromONNX(onnxmodel);
|
||||
|
||||
@@ -554,7 +554,6 @@ static const TestCase testConformanceConfig[] = {
|
||||
{"test_pow_bcast_array", 2, 1},
|
||||
{"test_pow_bcast_scalar", 2, 1},
|
||||
{"test_pow_example", 2, 1},
|
||||
{"test_pow_types_float", 2, 1},
|
||||
{"test_pow_types_float32_int32", 2, 1},
|
||||
{"test_pow_types_float32_int64", 2, 1},
|
||||
{"test_pow_types_float32_uint32", 2, 1},
|
||||
@@ -1673,6 +1672,160 @@ static const TestCase testConformanceConfig[] = {
|
||||
{"test_top_k_uint64", 0, 0},
|
||||
{"test_unique_length_1", 0, 0},
|
||||
{"test_wrap_pad", 0, 0},
|
||||
|
||||
// ===== ONNX 1.22 additions =====
|
||||
{"test_attention_4d_softcap_neginf_mask", 4, 1},
|
||||
{"test_attention_4d_softcap_neginf_mask_expanded", 4, 1},
|
||||
{"test_attention_4d_softcap_neginf_mask_poison", 4, 1},
|
||||
{"test_attention_4d_softcap_neginf_mask_poison_expanded", 4, 1},
|
||||
{"test_bitcast_2d_float32_to_int32", 1, 1},
|
||||
{"test_bitcast_bool_to_uint8", 1, 1},
|
||||
{"test_bitcast_float32_to_int32", 1, 1},
|
||||
{"test_bitcast_float64_to_int64", 1, 1},
|
||||
{"test_bitcast_int32_to_float32", 1, 1},
|
||||
{"test_bitcast_int64_to_float64", 1, 1},
|
||||
{"test_bitcast_int8_to_uint8", 1, 1},
|
||||
{"test_bitcast_scalar_float32_to_int32", 1, 1},
|
||||
{"test_bitcast_uint16_to_int16", 1, 1},
|
||||
{"test_bitcast_uint32_to_int32", 1, 1},
|
||||
{"test_cast_FLOAT16_to_INT2", 1, 1},
|
||||
{"test_cast_FLOAT16_to_UINT2", 1, 1},
|
||||
{"test_cast_FLOAT_to_INT2", 1, 1},
|
||||
{"test_cast_FLOAT_to_UINT2", 1, 1},
|
||||
{"test_cast_INT2_to_FLOAT", 1, 1},
|
||||
{"test_cast_INT2_to_FLOAT16", 1, 1},
|
||||
{"test_cast_INT2_to_INT8", 1, 1},
|
||||
{"test_cast_UINT2_to_FLOAT", 1, 1},
|
||||
{"test_cast_UINT2_to_FLOAT16", 1, 1},
|
||||
{"test_cast_UINT2_to_UINT8", 1, 1},
|
||||
{"test_castlike_FLOAT16_to_INT2", 2, 1},
|
||||
{"test_castlike_FLOAT16_to_INT2_expanded", 2, 1},
|
||||
{"test_castlike_FLOAT16_to_UINT2", 2, 1},
|
||||
{"test_castlike_FLOAT16_to_UINT2_expanded", 2, 1},
|
||||
{"test_castlike_FLOAT_to_INT2", 2, 1},
|
||||
{"test_castlike_FLOAT_to_INT2_expanded", 2, 1},
|
||||
{"test_castlike_FLOAT_to_UINT2", 2, 1},
|
||||
{"test_castlike_FLOAT_to_UINT2_expanded", 2, 1},
|
||||
{"test_castlike_INT2_to_FLOAT", 2, 1},
|
||||
{"test_castlike_INT2_to_FLOAT16", 2, 1},
|
||||
{"test_castlike_INT2_to_FLOAT16_expanded", 2, 1},
|
||||
{"test_castlike_INT2_to_FLOAT_expanded", 2, 1},
|
||||
{"test_castlike_INT2_to_INT8", 2, 1},
|
||||
{"test_castlike_INT2_to_INT8_expanded", 2, 1},
|
||||
{"test_castlike_UINT2_to_FLOAT", 2, 1},
|
||||
{"test_castlike_UINT2_to_FLOAT16", 2, 1},
|
||||
{"test_castlike_UINT2_to_FLOAT16_expanded", 2, 1},
|
||||
{"test_castlike_UINT2_to_FLOAT_expanded", 2, 1},
|
||||
{"test_castlike_UINT2_to_UINT8", 2, 1},
|
||||
{"test_castlike_UINT2_to_UINT8_expanded", 2, 1},
|
||||
{"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ_expanded", 2, 1},
|
||||
{"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN_expanded", 2, 1},
|
||||
{"test_causal_conv_with_state_b1_c1_degenerate", 2, 2},
|
||||
{"test_causal_conv_with_state_b1_c1_degenerate_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_basic", 2, 2},
|
||||
{"test_causal_conv_with_state_basic_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_decode_step", 4, 2},
|
||||
{"test_causal_conv_with_state_decode_step_expanded", 4, 2},
|
||||
{"test_causal_conv_with_state_fp16", 2, 2},
|
||||
{"test_causal_conv_with_state_fp16_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_kernel_size_one", 2, 2},
|
||||
{"test_causal_conv_with_state_kernel_size_one_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_short_input_no_past_state", 2, 2},
|
||||
{"test_causal_conv_with_state_short_input_no_past_state_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_silu", 2, 2},
|
||||
{"test_causal_conv_with_state_silu_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_silu_fp16", 2, 2},
|
||||
{"test_causal_conv_with_state_silu_fp16_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_silu_with_past_state", 3, 2},
|
||||
{"test_causal_conv_with_state_silu_with_past_state_expanded", 3, 2},
|
||||
{"test_causal_conv_with_state_swish_alias", 2, 2},
|
||||
{"test_causal_conv_with_state_swish_alias_expanded", 2, 2},
|
||||
{"test_causal_conv_with_state_with_bias", 3, 2},
|
||||
{"test_causal_conv_with_state_with_bias_and_past_state", 4, 2},
|
||||
{"test_causal_conv_with_state_with_bias_and_past_state_expanded", 4, 2},
|
||||
{"test_causal_conv_with_state_with_bias_expanded", 3, 2},
|
||||
{"test_causal_conv_with_state_with_past_state", 3, 2},
|
||||
{"test_causal_conv_with_state_with_past_state_expanded", 3, 2},
|
||||
{"test_cumprod_1d", 2, 1},
|
||||
{"test_cumprod_1d_exclusive", 2, 1},
|
||||
{"test_cumprod_1d_int32_exclusive", 2, 1},
|
||||
{"test_cumprod_1d_reverse", 2, 1},
|
||||
{"test_cumprod_1d_reverse_exclusive", 2, 1},
|
||||
{"test_cumprod_2d_axis_0", 2, 1},
|
||||
{"test_cumprod_2d_axis_1", 2, 1},
|
||||
{"test_cumprod_2d_int32", 2, 1},
|
||||
{"test_cumprod_2d_negative_axis", 2, 1},
|
||||
{"test_dequantizelinear_int2", 3, 1},
|
||||
{"test_dequantizelinear_uint2", 3, 1},
|
||||
{"test_dft_irfft", 2, 1},
|
||||
{"test_dft_irfft_opset19", 1, 1},
|
||||
{"test_dft_rfft", 2, 1},
|
||||
{"test_dft_rfft_opset19", 1, 1},
|
||||
{"test_div_int32_trunc", 2, 1},
|
||||
{"test_flexattention", 3, 1},
|
||||
{"test_flexattention_causal_mask", 3, 1},
|
||||
{"test_flexattention_causal_mask_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_diff_head_sizes", 3, 1},
|
||||
{"test_flexattention_diff_head_sizes_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_double", 3, 1},
|
||||
{"test_flexattention_double_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_fp16", 3, 1},
|
||||
{"test_flexattention_fp16_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_gqa", 3, 1},
|
||||
{"test_flexattention_gqa_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_prob_mod", 3, 1},
|
||||
{"test_flexattention_prob_mod_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_relative_positional", 3, 1},
|
||||
{"test_flexattention_relative_positional_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_scaled", 3, 1},
|
||||
{"test_flexattention_scaled_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_score_mod", 3, 1},
|
||||
{"test_flexattention_score_mod_expanded_ver26", 3, 1},
|
||||
{"test_flexattention_soft_cap", 3, 1},
|
||||
{"test_flexattention_soft_cap_expanded_ver26", 3, 1},
|
||||
{"test_linear_attention_decode_step", 6, 2},
|
||||
{"test_linear_attention_decode_step_expanded", 6, 2},
|
||||
{"test_linear_attention_delta", 4, 2},
|
||||
{"test_linear_attention_delta_expanded", 4, 2},
|
||||
{"test_linear_attention_explicit_scale", 5, 2},
|
||||
{"test_linear_attention_explicit_scale_expanded", 5, 2},
|
||||
{"test_linear_attention_fp16", 5, 2},
|
||||
{"test_linear_attention_fp16_expanded", 5, 2},
|
||||
{"test_linear_attention_gated", 4, 2},
|
||||
{"test_linear_attention_gated_delta", 5, 2},
|
||||
{"test_linear_attention_gated_delta_beta_scalar", 5, 2},
|
||||
{"test_linear_attention_gated_delta_beta_scalar_expanded", 5, 2},
|
||||
{"test_linear_attention_gated_delta_expanded", 5, 2},
|
||||
{"test_linear_attention_gated_delta_gqa", 5, 2},
|
||||
{"test_linear_attention_gated_delta_gqa_expanded", 5, 2},
|
||||
{"test_linear_attention_gated_delta_mqa", 5, 2},
|
||||
{"test_linear_attention_gated_delta_mqa_expanded", 5, 2},
|
||||
{"test_linear_attention_gated_expanded", 4, 2},
|
||||
{"test_linear_attention_gated_per_head_decay", 4, 2},
|
||||
{"test_linear_attention_gated_per_head_decay_expanded", 4, 2},
|
||||
{"test_linear_attention_linear", 3, 2},
|
||||
{"test_linear_attention_linear_expanded", 3, 2},
|
||||
{"test_linear_attention_linear_t1_no_past", 3, 2},
|
||||
{"test_linear_attention_linear_t1_no_past_expanded", 3, 2},
|
||||
{"test_linear_attention_no_past_explicit_zeros", 6, 2},
|
||||
{"test_linear_attention_no_past_explicit_zeros_expanded", 6, 2},
|
||||
{"test_linear_attention_prefill_with_past", 6, 2},
|
||||
{"test_linear_attention_prefill_with_past_expanded", 6, 2},
|
||||
{"test_matmul_1d_1d", 2, 1},
|
||||
{"test_matmul_1d_3d", 2, 1},
|
||||
{"test_matmul_4d_1d", 2, 1},
|
||||
{"test_matmul_bcast", 2, 1},
|
||||
{"test_nonmaxsuppression_iou_threshold_boundary", 5, 1},
|
||||
{"test_quantizelinear_int2", 3, 1},
|
||||
{"test_quantizelinear_uint2", 3, 1},
|
||||
{"test_range_bfloat16_type_positive_delta", 3, 1},
|
||||
{"test_range_bfloat16_type_positive_delta_expanded", 3, 1},
|
||||
{"test_range_float16_type_positive_delta", 3, 1},
|
||||
{"test_range_float16_type_positive_delta_expanded", 3, 1},
|
||||
{"test_scan9_multi_state", 3, 3},
|
||||
{"test_scan9_scalar", 2, 2},
|
||||
{"test_scatter_elements_with_reduction_mul", 3, 1},
|
||||
};
|
||||
|
||||
|
||||
@@ -1784,18 +1937,6 @@ public:
|
||||
#include "test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp"
|
||||
};
|
||||
|
||||
EngineType engine_forced =
|
||||
(EngineType)utils::getConfigurationParameterSizeT(
|
||||
"OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
|
||||
|
||||
if (engine_forced == ENGINE_CLASSIC) {
|
||||
classic_deny_list = {
|
||||
#include "test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp"
|
||||
};
|
||||
} else {
|
||||
classic_deny_list = {};
|
||||
}
|
||||
|
||||
#ifdef HAVE_HALIDE
|
||||
halide_deny_list = {
|
||||
#include "test_onnx_conformance_layer_filter__halide_denylist.inl.hpp"
|
||||
@@ -1912,6 +2053,11 @@ TEST_P(Test_ONNX_conformance, Layer_Test)
|
||||
default_l1 = std::max(default_l1, 2e-4);
|
||||
default_lInf = std::max(default_lInf, 1e-3);
|
||||
}
|
||||
// fp16 CausalConvWithState keeps fp16 output precision (~8e-4 Inf) on fp32 targets.
|
||||
if (name == "test_causal_conv_with_state_fp16" || name == "test_causal_conv_with_state_silu_fp16") {
|
||||
default_l1 = std::max(default_l1, 2e-4);
|
||||
default_lInf = std::max(default_lInf, 2e-3);
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_HALIDE
|
||||
else if (backend == DNN_BACKEND_HALIDE)
|
||||
@@ -1983,6 +2129,12 @@ TEST_P(Test_ONNX_conformance, Layer_Test)
|
||||
default_l1 = std::max(default_l1, 2e-4);
|
||||
default_lInf = std::max(default_lInf, 1e-3);
|
||||
}
|
||||
// fp16 CausalConvWithState keeps fp16 output precision (~8e-4 Inf) on fp32 targets
|
||||
// (the layer falls back to the CPU path).
|
||||
if (name == "test_causal_conv_with_state_fp16" || name == "test_causal_conv_with_state_silu_fp16") {
|
||||
default_l1 = std::max(default_l1, 2e-4);
|
||||
default_lInf = std::max(default_lInf, 2e-3);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
|
||||
@@ -2423,8 +2423,12 @@ CASE(test_rms_normalization_4d_axis_negative_4_expanded)
|
||||
SKIP;
|
||||
CASE(test_rms_normalization_default_axis_expanded)
|
||||
SKIP;
|
||||
CASE(test_scan9_multi_state)
|
||||
SKIP;
|
||||
CASE(test_scan9_scalar)
|
||||
SKIP;
|
||||
CASE(test_scan9_sum)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_scan_sum)
|
||||
// no filter
|
||||
CASE(test_scatter_elements_with_axis)
|
||||
@@ -3326,6 +3330,96 @@ CASE(test_attention_4d_with_qk_matmul_softmax)
|
||||
SKIP;
|
||||
CASE(test_attention_4d_with_qk_matmul_softmax_expanded)
|
||||
SKIP;
|
||||
|
||||
// ONNX 1.22 additions: unsupported on OpenVINO
|
||||
CASE(test_dft_rfft)
|
||||
SKIP;
|
||||
CASE(test_nonmaxsuppression_iou_threshold_boundary)
|
||||
SKIP;
|
||||
CASE(test_attention_4d_softcap_neginf_mask_expanded)
|
||||
SKIP;
|
||||
CASE(test_attention_4d_softcap_neginf_mask_poison_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_b1_c1_degenerate_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_basic_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_decode_step_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_kernel_size_one_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_short_input_no_past_state_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_silu_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_silu_with_past_state_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_swish_alias_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_with_bias_and_past_state_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_with_bias_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_with_past_state_expanded)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_b1_c1_degenerate)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_basic)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_decode_step)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_fp16)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_kernel_size_one)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_short_input_no_past_state)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_silu)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_silu_fp16)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_silu_with_past_state)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_swish_alias)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_with_bias)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_with_bias_and_past_state)
|
||||
SKIP;
|
||||
CASE(test_causal_conv_with_state_with_past_state)
|
||||
SKIP;
|
||||
CASE(test_cumprod_1d)
|
||||
SKIP;
|
||||
CASE(test_cumprod_1d_exclusive)
|
||||
SKIP;
|
||||
CASE(test_cumprod_1d_int32_exclusive)
|
||||
SKIP;
|
||||
CASE(test_cumprod_1d_reverse)
|
||||
SKIP;
|
||||
CASE(test_cumprod_1d_reverse_exclusive)
|
||||
SKIP;
|
||||
CASE(test_cumprod_2d_axis_0)
|
||||
SKIP;
|
||||
CASE(test_cumprod_2d_axis_1)
|
||||
SKIP;
|
||||
CASE(test_cumprod_2d_int32)
|
||||
SKIP;
|
||||
CASE(test_cumprod_2d_negative_axis)
|
||||
SKIP;
|
||||
CASE(test_flexattention_scaled_expanded_ver26)
|
||||
SKIP;
|
||||
CASE(test_range_bfloat16_type_positive_delta)
|
||||
SKIP;
|
||||
CASE(test_range_float16_type_positive_delta)
|
||||
SKIP;
|
||||
CASE(test_dft_rfft_opset19)
|
||||
SKIP;
|
||||
CASE(test_div_int32_trunc)
|
||||
SKIP;
|
||||
CASE(test_matmul_bcast)
|
||||
SKIP;
|
||||
CASE(test_scatter_elements_with_reduction_mul)
|
||||
SKIP;
|
||||
END_SWITCH()
|
||||
#undef EOF_LABEL
|
||||
#undef BEGIN_SWITCH
|
||||
|
||||
@@ -736,6 +736,9 @@
|
||||
"test_reduce_sum_square_empty_set_expanded",
|
||||
"test_reduce_log_sum_exp_empty_set_expanded",
|
||||
"test_loop11",
|
||||
"test_scan9_multi_state", // Scan supported only by the new graph engine
|
||||
"test_scan9_scalar", // ---- same as above ---
|
||||
"test_scan9_sum", // ---- same as above ---
|
||||
"test_eyelike_populate_off_main_diagonal",
|
||||
"test_eyelike_with_dtype",
|
||||
"test_eyelike_without_dtype",
|
||||
@@ -916,3 +919,43 @@
|
||||
"test_attention_4d_with_qk_matmul_expanded",
|
||||
"test_attention_4d_with_qk_matmul_softmax",
|
||||
"test_attention_4d_with_qk_matmul_softmax_expanded",
|
||||
"test_dft_rfft",
|
||||
"test_nonmaxsuppression_iou_threshold_boundary",
|
||||
"test_attention_4d_softcap_neginf_mask_expanded",
|
||||
"test_attention_4d_softcap_neginf_mask_poison_expanded",
|
||||
"test_causal_conv_with_state_b1_c1_degenerate_expanded",
|
||||
"test_causal_conv_with_state_basic_expanded",
|
||||
"test_causal_conv_with_state_decode_step_expanded",
|
||||
"test_causal_conv_with_state_kernel_size_one_expanded",
|
||||
"test_causal_conv_with_state_short_input_no_past_state_expanded",
|
||||
"test_causal_conv_with_state_silu_expanded",
|
||||
"test_causal_conv_with_state_silu_with_past_state_expanded",
|
||||
"test_causal_conv_with_state_swish_alias_expanded",
|
||||
"test_causal_conv_with_state_with_bias_and_past_state_expanded",
|
||||
"test_causal_conv_with_state_with_bias_expanded",
|
||||
"test_causal_conv_with_state_with_past_state_expanded",
|
||||
"test_causal_conv_with_state_b1_c1_degenerate",
|
||||
"test_causal_conv_with_state_basic",
|
||||
"test_causal_conv_with_state_decode_step",
|
||||
"test_causal_conv_with_state_fp16",
|
||||
"test_causal_conv_with_state_kernel_size_one",
|
||||
"test_causal_conv_with_state_short_input_no_past_state",
|
||||
"test_causal_conv_with_state_silu",
|
||||
"test_causal_conv_with_state_silu_fp16",
|
||||
"test_causal_conv_with_state_silu_with_past_state",
|
||||
"test_causal_conv_with_state_swish_alias",
|
||||
"test_causal_conv_with_state_with_bias",
|
||||
"test_causal_conv_with_state_with_bias_and_past_state",
|
||||
"test_causal_conv_with_state_with_past_state",
|
||||
"test_cumprod_1d",
|
||||
"test_cumprod_1d_exclusive",
|
||||
"test_cumprod_1d_int32_exclusive",
|
||||
"test_cumprod_1d_reverse",
|
||||
"test_cumprod_1d_reverse_exclusive",
|
||||
"test_cumprod_2d_axis_0",
|
||||
"test_cumprod_2d_axis_1",
|
||||
"test_cumprod_2d_int32",
|
||||
"test_cumprod_2d_negative_axis",
|
||||
"test_flexattention_scaled_expanded_ver26",
|
||||
"test_range_bfloat16_type_positive_delta",
|
||||
"test_range_float16_type_positive_delta",
|
||||
|
||||
@@ -272,8 +272,8 @@
|
||||
"test_reversesequence_batch", // Issue:: Parser: Can't create layer "onnx_node_output_0!y" of type "ReverseSequence" in function 'getLayerInstance'
|
||||
"test_reversesequence_time", // ---- same as above ---
|
||||
"test_rnn_seq_length", // Issue:: Parser: Can't create layer "onnx_node_output_1!Y_h" of type "RNN" in function 'getLayerInstance'
|
||||
"test_scan9_sum", // Issue:: Parser: 'Graph' is not supported in function 'getLayerParams'
|
||||
"test_scan_sum", // ---- same as above ---
|
||||
// Scan edge cases beyond the opset-9+ dataflow the new engine supports:
|
||||
"test_scan_sum", // opset-8 Scan (leading batch dim + sequence_lens, different semantics)
|
||||
"test_sequence_insert_at_back", // Issue:: Parser: typeProto.has_tensor_type() in function 'populateNet'
|
||||
"test_sequence_insert_at_front", // ---- same as above ---
|
||||
"test_sequence_map_add_1_sequence_1_tensor",
|
||||
@@ -331,3 +331,122 @@
|
||||
"test_training_dropout_mask", // ---- same as above ---
|
||||
"test_training_dropout_zero_ratio_mask", // ---- same as above ---
|
||||
"test_unique_length_1", //incorrect output
|
||||
|
||||
// ===== ONNX 1.22 additions: ops/dtypes not yet supported by the importer =====
|
||||
// BitCast op not supported by the ONNX importer
|
||||
"test_bitcast_2d_float32_to_int32",
|
||||
"test_bitcast_bool_to_uint8",
|
||||
"test_bitcast_float32_to_int32",
|
||||
"test_bitcast_float64_to_int64",
|
||||
"test_bitcast_int32_to_float32",
|
||||
"test_bitcast_int64_to_float64",
|
||||
"test_bitcast_int8_to_uint8",
|
||||
"test_bitcast_scalar_float32_to_int32",
|
||||
"test_bitcast_uint16_to_int16",
|
||||
"test_bitcast_uint32_to_int32",
|
||||
// INT2/UINT2 (2-bit) dtype not supported
|
||||
"test_cast_FLOAT16_to_INT2",
|
||||
"test_cast_FLOAT16_to_UINT2",
|
||||
"test_cast_FLOAT_to_INT2",
|
||||
"test_cast_FLOAT_to_UINT2",
|
||||
"test_cast_INT2_to_FLOAT",
|
||||
"test_cast_INT2_to_FLOAT16",
|
||||
"test_cast_INT2_to_INT8",
|
||||
"test_cast_UINT2_to_FLOAT",
|
||||
"test_cast_UINT2_to_FLOAT16",
|
||||
"test_cast_UINT2_to_UINT8",
|
||||
"test_castlike_FLOAT16_to_INT2",
|
||||
"test_castlike_FLOAT16_to_INT2_expanded",
|
||||
"test_castlike_FLOAT16_to_UINT2",
|
||||
"test_castlike_FLOAT16_to_UINT2_expanded",
|
||||
"test_castlike_FLOAT_to_INT2",
|
||||
"test_castlike_FLOAT_to_INT2_expanded",
|
||||
"test_castlike_FLOAT_to_UINT2",
|
||||
"test_castlike_FLOAT_to_UINT2_expanded",
|
||||
"test_castlike_INT2_to_FLOAT",
|
||||
"test_castlike_INT2_to_FLOAT16",
|
||||
"test_castlike_INT2_to_FLOAT16_expanded",
|
||||
"test_castlike_INT2_to_FLOAT_expanded",
|
||||
"test_castlike_INT2_to_INT8",
|
||||
"test_castlike_INT2_to_INT8_expanded",
|
||||
"test_castlike_UINT2_to_FLOAT",
|
||||
"test_castlike_UINT2_to_FLOAT16",
|
||||
"test_castlike_UINT2_to_FLOAT16_expanded",
|
||||
"test_castlike_UINT2_to_FLOAT_expanded",
|
||||
"test_castlike_UINT2_to_UINT8",
|
||||
"test_castlike_UINT2_to_UINT8_expanded",
|
||||
"test_dequantizelinear_int2",
|
||||
"test_dequantizelinear_uint2",
|
||||
"test_quantizelinear_int2",
|
||||
"test_quantizelinear_uint2",
|
||||
// FlexAttention op not supported
|
||||
"test_flexattention",
|
||||
"test_flexattention_causal_mask",
|
||||
"test_flexattention_diff_head_sizes",
|
||||
"test_flexattention_double",
|
||||
"test_flexattention_fp16",
|
||||
"test_flexattention_gqa",
|
||||
"test_flexattention_prob_mod",
|
||||
"test_flexattention_relative_positional",
|
||||
"test_flexattention_scaled",
|
||||
"test_flexattention_score_mod",
|
||||
"test_flexattention_soft_cap",
|
||||
// LinearAttention op not supported
|
||||
"test_linear_attention_decode_step",
|
||||
"test_linear_attention_decode_step_expanded",
|
||||
"test_linear_attention_delta",
|
||||
"test_linear_attention_delta_expanded",
|
||||
"test_linear_attention_explicit_scale",
|
||||
"test_linear_attention_explicit_scale_expanded",
|
||||
"test_linear_attention_fp16",
|
||||
"test_linear_attention_fp16_expanded",
|
||||
"test_linear_attention_gated",
|
||||
"test_linear_attention_gated_delta",
|
||||
"test_linear_attention_gated_delta_beta_scalar",
|
||||
"test_linear_attention_gated_delta_beta_scalar_expanded",
|
||||
"test_linear_attention_gated_delta_expanded",
|
||||
"test_linear_attention_gated_delta_gqa",
|
||||
"test_linear_attention_gated_delta_gqa_expanded",
|
||||
"test_linear_attention_gated_delta_mqa",
|
||||
"test_linear_attention_gated_delta_mqa_expanded",
|
||||
"test_linear_attention_gated_expanded",
|
||||
"test_linear_attention_gated_per_head_decay",
|
||||
"test_linear_attention_gated_per_head_decay_expanded",
|
||||
"test_linear_attention_linear",
|
||||
"test_linear_attention_linear_expanded",
|
||||
"test_linear_attention_linear_t1_no_past",
|
||||
"test_linear_attention_linear_t1_no_past_expanded",
|
||||
"test_linear_attention_no_past_explicit_zeros",
|
||||
"test_linear_attention_no_past_explicit_zeros_expanded",
|
||||
"test_linear_attention_prefill_with_past",
|
||||
"test_linear_attention_prefill_with_past_expanded",
|
||||
// misc unsupported (expanded subgraphs / new ops)
|
||||
"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ_expanded",
|
||||
"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN_expanded",
|
||||
"test_range_bfloat16_type_positive_delta_expanded",
|
||||
"test_range_float16_type_positive_delta_expanded",
|
||||
// ===== ONNX 1.22 additions: forward/accuracy not yet supported =====
|
||||
// MatMul with 1-D operand not supported (requires >=2D)
|
||||
"test_matmul_1d_1d",
|
||||
"test_matmul_1d_3d",
|
||||
"test_matmul_4d_1d",
|
||||
// DFT inverse RFFT not supported
|
||||
"test_dft_irfft",
|
||||
"test_dft_irfft_opset19",
|
||||
// Attention softcap accuracy
|
||||
"test_attention_4d_softcap_neginf_mask",
|
||||
"test_attention_4d_softcap_neginf_mask_poison",
|
||||
// CausalConvWithState fp16 (expanded) accuracy
|
||||
"test_causal_conv_with_state_fp16_expanded",
|
||||
"test_causal_conv_with_state_silu_fp16_expanded",
|
||||
// FlexAttention (expanded) accuracy
|
||||
"test_flexattention_causal_mask_expanded_ver26",
|
||||
"test_flexattention_diff_head_sizes_expanded_ver26",
|
||||
"test_flexattention_double_expanded_ver26",
|
||||
"test_flexattention_expanded_ver26",
|
||||
"test_flexattention_fp16_expanded_ver26",
|
||||
"test_flexattention_gqa_expanded_ver26",
|
||||
"test_flexattention_prob_mod_expanded_ver26",
|
||||
"test_flexattention_relative_positional_expanded_ver26",
|
||||
"test_flexattention_score_mod_expanded_ver26",
|
||||
"test_flexattention_soft_cap_expanded_ver26",
|
||||
|
||||
@@ -217,14 +217,6 @@ public:
|
||||
// output against an in-test attention reference computed from the same inputs.
|
||||
void testSDPAModel(const String& basename, double l1, double lInf)
|
||||
{
|
||||
// SDPA is only handled by the new-engine ONNX importer.
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC) {
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
Mat Q = blobFromNPY(_tf("data/input_" + basename + "_0.npy"));
|
||||
Mat KT = blobFromNPY(_tf("data/input_" + basename + "_1.npy"));
|
||||
Mat V = blobFromNPY(_tf("data/input_" + basename + "_2.npy"));
|
||||
@@ -2323,14 +2315,6 @@ TEST_P(Test_ONNX_layers, Gemm_External_Data)
|
||||
|
||||
TEST_P(Test_ONNX_layers, Quantized_MatMul_Variable_Weights)
|
||||
{
|
||||
auto engine_forced = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
testONNXModels("quantized_matmul_variable_inputs", npy, 1.3, 1.3);
|
||||
}
|
||||
|
||||
|
||||
@@ -1813,7 +1813,7 @@ TEST_P(Test_TensorFlow_nets, Mask_RCNN)
|
||||
outNames[0] = "detection_out_final";
|
||||
outNames[1] = "detection_masks";
|
||||
|
||||
Net net = readNetFromTensorflow(model, proto, ENGINE_AUTO, outNames);
|
||||
Net net = readNetFromTensorflow(model, proto, ENGINE_OPENCV, outNames);
|
||||
Mat refDetections = blobFromNPY(path("mask_rcnn_inception_v2_coco_2018_01_28.detection_out.npy"));
|
||||
Mat refMasks = blobFromNPY(path("mask_rcnn_inception_v2_coco_2018_01_28.detection_masks.npy"));
|
||||
Mat blob = blobFromImage(img, 1.0f, Size(800, 800), Scalar(), true, false);
|
||||
|
||||
@@ -859,7 +859,7 @@ public:
|
||||
CV_WRAP Params();
|
||||
CV_PROP_RW Size inputSize; //!< Input image size for the network, default 640x640
|
||||
CV_PROP_RW bool normalizeDescriptors; //!< Whether to L2-normalize descriptors, default true
|
||||
CV_PROP_RW int engine; //!< DNN engine type (dnn::EngineType), default ENGINE_NEW
|
||||
CV_PROP_RW int engine; //!< DNN engine type (dnn::EngineType), default ENGINE_AUTO
|
||||
CV_PROP_RW int backend; //!< DNN backend, default DNN_BACKEND_DEFAULT
|
||||
CV_PROP_RW int target; //!< DNN target, default DNN_TARGET_CPU
|
||||
};
|
||||
@@ -998,6 +998,19 @@ public:
|
||||
*/
|
||||
CV_WRAP virtual bool isMaskSupported() const = 0;
|
||||
|
||||
/** @brief Provides keypoint and image-size context for matchers that need it (e.g. LightGlueMatcher).
|
||||
|
||||
Must be called before match()/knnMatch()/radiusMatch() for matchers that require this context.
|
||||
Matchers that don't need it (e.g. BFMatcher, FlannBasedMatcher) ignore the call.
|
||||
|
||||
@param queryKpts Query image keypoints.
|
||||
@param trainKpts Train image keypoints.
|
||||
@param queryImageSize Size of the query image (width, height).
|
||||
@param trainImageSize Size of the train image (width, height).
|
||||
*/
|
||||
CV_WRAP virtual void setImagePairInfo(const std::vector<KeyPoint>& queryKpts, const std::vector<KeyPoint>& trainKpts,
|
||||
Size queryImageSize = Size(), Size trainImageSize = Size());
|
||||
|
||||
/** @brief Trains a descriptor matcher
|
||||
|
||||
Trains a descriptor matcher (for example, the flann index). In all methods to match, the method
|
||||
@@ -1349,6 +1362,10 @@ public:
|
||||
/** @brief Clears stored pair context information.
|
||||
*/
|
||||
CV_WRAP virtual void clearPairInfo() = 0;
|
||||
|
||||
/** @brief Convenience overload of setPairInfo() taking keypoints directly. */
|
||||
CV_WRAP void setImagePairInfo(const std::vector<KeyPoint>& queryKpts, const std::vector<KeyPoint>& trainKpts,
|
||||
Size queryImageSize = Size(), Size trainImageSize = Size()) CV_OVERRIDE;
|
||||
};
|
||||
|
||||
//! @} features_match
|
||||
|
||||
@@ -20,7 +20,7 @@ ALIKED::Params::Params()
|
||||
inputSize = Size(640, 640);
|
||||
normalizeDescriptors = true;
|
||||
#ifdef HAVE_OPENCV_DNN
|
||||
engine = dnn::ENGINE_NEW;
|
||||
engine = dnn::ENGINE_AUTO;
|
||||
backend = dnn::DNN_BACKEND_DEFAULT;
|
||||
target = dnn::DNN_TARGET_CPU;
|
||||
#else
|
||||
|
||||
@@ -580,6 +580,10 @@ bool DescriptorMatcher::empty() const
|
||||
void DescriptorMatcher::train()
|
||||
{}
|
||||
|
||||
void DescriptorMatcher::setImagePairInfo( const std::vector<KeyPoint>&, const std::vector<KeyPoint>&,
|
||||
Size, Size )
|
||||
{}
|
||||
|
||||
void DescriptorMatcher::match( InputArray queryDescriptors, InputArray trainDescriptors,
|
||||
std::vector<DMatch>& matches, InputArray mask ) const
|
||||
{
|
||||
|
||||
@@ -15,6 +15,26 @@ namespace cv
|
||||
LightGlueMatcher::LightGlueMatcher() {}
|
||||
LightGlueMatcher::~LightGlueMatcher() {}
|
||||
|
||||
void LightGlueMatcher::setImagePairInfo(const std::vector<KeyPoint>& queryKpts, const std::vector<KeyPoint>& trainKpts,
|
||||
Size queryImageSize, Size trainImageSize)
|
||||
{
|
||||
Mat qk((int)queryKpts.size(), 2, CV_32F);
|
||||
for (size_t i = 0; i < queryKpts.size(); ++i)
|
||||
{
|
||||
qk.at<float>((int)i, 0) = queryKpts[i].pt.x;
|
||||
qk.at<float>((int)i, 1) = queryKpts[i].pt.y;
|
||||
}
|
||||
|
||||
Mat tk((int)trainKpts.size(), 2, CV_32F);
|
||||
for (size_t i = 0; i < trainKpts.size(); ++i)
|
||||
{
|
||||
tk.at<float>((int)i, 0) = trainKpts[i].pt.x;
|
||||
tk.at<float>((int)i, 1) = trainKpts[i].pt.y;
|
||||
}
|
||||
|
||||
setPairInfo(qk, tk, queryImageSize, trainImageSize);
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENCV_DNN
|
||||
|
||||
struct LightGluePairContext
|
||||
|
||||
@@ -8,22 +8,12 @@
|
||||
#ifdef HAVE_OPENCV_DNN
|
||||
|
||||
#include "opencv2/dnn.hpp"
|
||||
#include "opencv2/core/utils/configuration.private.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static void skipIfClassicDnnEngine()
|
||||
{
|
||||
const auto engine = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine == cv::dnn::ENGINE_CLASSIC)
|
||||
throw SkipTestException("ALIKED/LightGlue reference outputs are generated with the new DNN engine");
|
||||
}
|
||||
|
||||
TEST(Features2d_ALIKED, Regression)
|
||||
{
|
||||
applyTestTag( CV_TEST_TAG_MEMORY_2GB);
|
||||
skipIfClassicDnnEngine();
|
||||
|
||||
const std::string modelPath = cvtest::findDataFile("dnn/onnx/models/aliked-n16rot-top1k-640.onnx", false);
|
||||
|
||||
@@ -88,7 +78,6 @@ TEST(Features2d_ALIKED, Regression)
|
||||
TEST(Features2d_LightGlue, Regression)
|
||||
{
|
||||
applyTestTag( CV_TEST_TAG_MEMORY_2GB);
|
||||
skipIfClassicDnnEngine();
|
||||
|
||||
const std::string alikedPath = cvtest::findDataFile("dnn/onnx/models/aliked-n16rot-top1k-640.onnx", false);
|
||||
const std::string lgPath = cvtest::findDataFile("dnn/onnx/models/aliked_lightglue.onnx", false);
|
||||
|
||||
@@ -14,17 +14,8 @@
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static void skipIfClassicDnnEngine()
|
||||
{
|
||||
const auto engine = static_cast<cv::dnn::EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
|
||||
if (engine == cv::dnn::ENGINE_CLASSIC)
|
||||
throw SkipTestException("DISK ONNX model is not supported by the classic DNN engine");
|
||||
}
|
||||
|
||||
static void testDiskRegression(const Size& imageSize, const std::string& tag)
|
||||
{
|
||||
skipIfClassicDnnEngine();
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_2GB);
|
||||
|
||||
Mat refKpts = blobFromNPY(cvtest::findDataFile("features/disk/box_in_scene_" + tag + "_kpts.npy"));
|
||||
@@ -84,7 +75,6 @@ TEST(Features2d_DISK, regression_512x384)
|
||||
|
||||
TEST(Features2d_DISK, MaxKeypointsAndThreshold)
|
||||
{
|
||||
skipIfClassicDnnEngine();
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_2GB);
|
||||
|
||||
const std::string modelPath = cvtest::findDataFile("dnn/disk.onnx", false);
|
||||
@@ -124,7 +114,6 @@ TEST(Features2d_DISK, MaxKeypointsAndThreshold)
|
||||
|
||||
TEST(Features2d_DISK, MaskSupport)
|
||||
{
|
||||
skipIfClassicDnnEngine();
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_2GB);
|
||||
|
||||
const std::string modelPath = cvtest::findDataFile("dnn/disk.onnx", false);
|
||||
@@ -153,7 +142,6 @@ TEST(Features2d_DISK, MaskSupport)
|
||||
|
||||
TEST(Features2d_DISK, InvalidImageSize)
|
||||
{
|
||||
skipIfClassicDnnEngine();
|
||||
const std::string modelPath = cvtest::findDataFile("dnn/disk.onnx", false);
|
||||
|
||||
EXPECT_THROW(DISK::create(modelPath, -1, 0.0f, Size(1000, 1024)), cv::Exception);
|
||||
|
||||
@@ -54,8 +54,9 @@ PERF_TEST_P(PointsNum_Algo, solvePnP,
|
||||
}
|
||||
|
||||
SANITY_CHECK(rvec, 1e-4);
|
||||
// the check is relaxed from 1e-4 to 2e-2 after LevMarq replacement
|
||||
SANITY_CHECK(tvec, 2e-2);
|
||||
// the check is relaxed from 1e-4 to 2e-2 after LevMarq replacement (#21018),
|
||||
// then to 3e-2 after the cv::Mat broadcasting element-wise engine rewrite (#29426)
|
||||
SANITY_CHECK(tvec, 3e-2);
|
||||
}
|
||||
|
||||
PERF_TEST_P(PointsNum_Algo, solvePnPSmallPoints,
|
||||
|
||||
@@ -504,7 +504,7 @@ double arcLength( InputArray _curve, bool is_closed )
|
||||
|
||||
static Rect maskBoundingRect( const Mat& img )
|
||||
{
|
||||
CV_Assert( img.depth() <= CV_8S && img.channels() == 1 );
|
||||
CV_Assert( (img.depth() <= CV_8S || img.depth() == CV_Bool) && img.channels() == 1 );
|
||||
|
||||
Size size = img.size();
|
||||
int xmin = size.width, ymin = -1, xmax = -1, ymax = -1, i, j, k;
|
||||
@@ -709,7 +709,10 @@ cv::Rect boundingRect(InputArray array)
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
Mat m = array.getMat();
|
||||
return m.depth() <= CV_8U ? maskBoundingRect(m) : pointSetBoundingRect(m);
|
||||
// CV_Bool is a 1-byte mask type (added in 5.0); route it through the
|
||||
// byte-wise mask path, same as CV_8U/CV_8S. See #29578.
|
||||
int depth = m.depth();
|
||||
return (depth <= CV_8U || depth == CV_Bool) ? maskBoundingRect(m) : pointSetBoundingRect(m);
|
||||
}
|
||||
|
||||
cv::Matx23d getRotationMatrix2D_(Point2f center, double angle, double scale)
|
||||
|
||||
@@ -96,4 +96,31 @@ TEST(Imgproc_BoundingRect, bug_24217)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/29578
|
||||
// cv::Mat_<bool>::depth() returns CV_Bool in 5.0, which regressed boundingRect
|
||||
// (it used to be treated as CV_8U in 4.x). A boolean mask must be handled as a
|
||||
// byte-wise mask, matching the CV_8UC1 result.
|
||||
TEST(Imgproc_BoundingRect, bool_mask_29578)
|
||||
{
|
||||
for (int image_width = 3; image_width < 20; image_width++)
|
||||
{
|
||||
for (int image_height = 1; image_height < 15; image_height++)
|
||||
{
|
||||
cv::Rect rect(0, image_height - 1, 3, 1);
|
||||
|
||||
cv::Mat_<bool> mask(cv::Size(image_width, image_height), false);
|
||||
mask(rect).setTo(true);
|
||||
|
||||
cv::Mat_<uchar> ref(cv::Size(image_width, image_height), (uchar)0);
|
||||
ref(rect).setTo(255);
|
||||
|
||||
EXPECT_EQ(mask.depth(), CV_Bool);
|
||||
cv::Rect result;
|
||||
ASSERT_NO_THROW(result = boundingRect(mask));
|
||||
EXPECT_EQ(result, rect);
|
||||
EXPECT_EQ(result, boundingRect(ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -201,6 +201,11 @@ typedef void (*TrackbarCallback)(int pos, void* userdata);
|
||||
*/
|
||||
typedef void (*OpenGlDrawCallback)(void* userdata);
|
||||
|
||||
/** @brief Callback function defined to be called when a window's resources should be freed. See cv::setOpenGlFreeCallback
|
||||
@param userdata The window's OpenGL user data to be freed.
|
||||
*/
|
||||
typedef void (*OpenGlFreeCallback)(void* userdata);
|
||||
|
||||
/** @brief Callback function for a button created by cv::createButton
|
||||
@param state current state of the button. It could be -1 for a push button, 0 or 1 for a check/radio box button.
|
||||
@param userdata The optional parameter.
|
||||
@@ -617,6 +622,26 @@ prototyped as void Foo(void\*) .
|
||||
*/
|
||||
CV_EXPORTS void setOpenGlDrawCallback(const String& winname, OpenGlDrawCallback onOpenGlDraw, void* userdata = 0);
|
||||
|
||||
/** @brief Sets a callback function to be called when OpenGL resources related to a window should be freed.
|
||||
|
||||
@param winname Name of the window.
|
||||
@param onOpenGlFree Pointer to the function to be called when resources should be freed. This
|
||||
function should be prototyped as void Foo(void\*) .
|
||||
*/
|
||||
CV_EXPORTS void setOpenGlFreeCallback(const String& winname, OpenGlFreeCallback onOpenGlFree);
|
||||
|
||||
/** @brief This function returns the pointer to the function associated to a window to be called every frame. See cv::setOpenGlDrawCallback.
|
||||
|
||||
@param winname Name of the window.
|
||||
*/
|
||||
CV_EXPORTS OpenGlDrawCallback getOpenGlDrawCallback(const String& winname);
|
||||
|
||||
/** @brief This function returns the pointer to the OpenGL user data associated to a window.
|
||||
|
||||
@param winname Name of the window.
|
||||
*/
|
||||
CV_EXPORTS void* getOpenGlUserData(const String& winname);
|
||||
|
||||
/** @brief Sets the specified window as current OpenGL context.
|
||||
|
||||
@param winname Name of the window.
|
||||
@@ -739,7 +764,7 @@ location of the window windowName.
|
||||
|
||||
@param windowName Name of the window.
|
||||
*/
|
||||
CV_EXPORTS void saveWindowParameters(const String& windowName);
|
||||
CV_EXPORTS_W void saveWindowParameters(const String& windowName);
|
||||
|
||||
/** @brief Loads parameters of the specified window.
|
||||
|
||||
@@ -748,7 +773,7 @@ location of the window windowName.
|
||||
|
||||
@param windowName Name of the window.
|
||||
*/
|
||||
CV_EXPORTS void loadWindowParameters(const String& windowName);
|
||||
CV_EXPORTS_W void loadWindowParameters(const String& windowName);
|
||||
|
||||
CV_EXPORTS int startLoop(int (*pt2Func)(int argc, char *argv[]), int argc, char* argv[]);
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
# Force Qt's headless-safe platform plugin when nothing else is already
|
||||
# configured. Without this, namedWindow() on a Qt-enabled build with no
|
||||
# display server available (e.g. CI workers, which do not run the Python
|
||||
# test suite under a virtual framebuffer) aborts the whole process instead
|
||||
# of raising a catchable exception -- so this must be set *before* the
|
||||
# first GUI call, and a plain try/except around namedWindow is not enough.
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
|
||||
class window_parameters_test(NewOpenCVTests):
|
||||
|
||||
def test_save_load_window_parameters(self):
|
||||
window_name = "test_save_load_window_parameters"
|
||||
try:
|
||||
cv.namedWindow(window_name, cv.WINDOW_NORMAL)
|
||||
except cv.error:
|
||||
self.skipTest("No GUI backend available in this build/environment")
|
||||
|
||||
try:
|
||||
# Should not raise when the library is built with a backend that
|
||||
# supports persisting window state (currently: Qt).
|
||||
cv.saveWindowParameters(window_name)
|
||||
cv.loadWindowParameters(window_name)
|
||||
except cv.error as e:
|
||||
# Expected on builds without Qt support (see NO_QT_ERR_MSG in window.cpp)
|
||||
self.skipTest("saveWindowParameters/loadWindowParameters require the Qt "
|
||||
"highgui backend: " + str(e))
|
||||
finally:
|
||||
cv.destroyWindow(window_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -116,6 +116,11 @@ void setOpenGLDrawCallbackImpl(const char* window_name, CvOpenGlDrawCallback cal
|
||||
void setOpenGLContextImpl(const char* window_name);
|
||||
void updateWindowImpl(const char* window_name);
|
||||
|
||||
typedef void (CV_CDECL *CvOpenGlFreeCallback)(void* userdata);
|
||||
void setOpenGLFreeCallbackImpl(const char* window_name, CvOpenGlFreeCallback callback);
|
||||
CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* window_name);
|
||||
void* getOpenGLUserDataImpl(const char* window_name);
|
||||
|
||||
|
||||
//Yannick Verdie 2010, Max Kostin 2015
|
||||
void cvSetModeWindow_W32(const char* name, double prop_value);
|
||||
|
||||
@@ -915,6 +915,24 @@ void cv::setOpenGlDrawCallback(const String& name, OpenGlDrawCallback callback,
|
||||
setOpenGLDrawCallbackImpl(name.c_str(), callback, userdata);
|
||||
}
|
||||
|
||||
void cv::setOpenGlFreeCallback(const String& name, OpenGlFreeCallback callback)
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
setOpenGLFreeCallbackImpl(name.c_str(), callback);
|
||||
}
|
||||
|
||||
cv::OpenGlDrawCallback cv::getOpenGlDrawCallback(const String& name)
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
return getOpenGLDrawCallbackImpl(name.c_str());
|
||||
}
|
||||
|
||||
void* cv::getOpenGlUserData(const String& name)
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
return getOpenGLUserDataImpl(name.c_str());
|
||||
}
|
||||
|
||||
void cv::setOpenGlContext(const String& windowName)
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
@@ -994,6 +1012,14 @@ void cv::imshow( const String& winname, InputArray _img )
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prevCallback = getOpenGlDrawCallback(winname);
|
||||
|
||||
if (prevCallback != nullptr && prevCallback != glDrawTextureCallback)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "OpenCV/UI: can't show image because the OpenGL callback is already being used on window: '" << winname << "'");
|
||||
return;
|
||||
}
|
||||
|
||||
const double autoSize = getWindowProperty(winname, WND_PROP_AUTOSIZE);
|
||||
|
||||
if (autoSize > 0)
|
||||
@@ -1113,6 +1139,21 @@ void updateWindowImpl(const char*)
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
|
||||
}
|
||||
|
||||
void setOpenGLFreeCallbackImpl(const char*, CvOpenGlFreeCallback)
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
|
||||
}
|
||||
|
||||
CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char*)
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
|
||||
}
|
||||
|
||||
void* getOpenGLUserDataImpl(const char*)
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
|
||||
}
|
||||
|
||||
#endif // !HAVE_OPENGL
|
||||
|
||||
//========================= Qt fallback =========================
|
||||
|
||||
@@ -727,6 +727,53 @@ void setOpenGLDrawCallbackImpl(const char* window_name, CvOpenGlDrawCallback cal
|
||||
}
|
||||
|
||||
|
||||
void setOpenGLFreeCallbackImpl(const char* window_name, CvOpenGlFreeCallback callback)
|
||||
{
|
||||
if (!guiMainThread)
|
||||
CV_Error( cv::Error::StsNullPtr, "NULL guiReceiver (please create a window)" );
|
||||
|
||||
QMetaObject::invokeMethod(guiMainThread,
|
||||
"setOpenGlFreeCallback",
|
||||
autoBlockingConnection(),
|
||||
Q_ARG(QString, QString(window_name)),
|
||||
Q_ARG(void*, (void*)callback));
|
||||
}
|
||||
|
||||
|
||||
CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* window_name)
|
||||
{
|
||||
if (!guiMainThread)
|
||||
CV_Error( cv::Error::StsNullPtr, "NULL guiReceiver (please create a window)" );
|
||||
|
||||
void* callback;
|
||||
|
||||
QMetaObject::invokeMethod(guiMainThread,
|
||||
"getOpenGlDrawCallback",
|
||||
autoBlockingConnection(),
|
||||
Q_RETURN_ARG(void*, callback),
|
||||
Q_ARG(QString, QString(window_name)));
|
||||
|
||||
return (CvOpenGlDrawCallback)callback;
|
||||
}
|
||||
|
||||
|
||||
void* getOpenGLUserDataImpl(const char* window_name)
|
||||
{
|
||||
if (!guiMainThread)
|
||||
CV_Error( cv::Error::StsNullPtr, "NULL guiReceiver (please create a window)" );
|
||||
|
||||
void* data;
|
||||
|
||||
QMetaObject::invokeMethod(guiMainThread,
|
||||
"getOpenGlUserData",
|
||||
autoBlockingConnection(),
|
||||
Q_RETURN_ARG(void*, data),
|
||||
Q_ARG(QString, QString(window_name)));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
void setOpenGLContextImpl(const char* window_name)
|
||||
{
|
||||
if (!guiMainThread)
|
||||
@@ -1238,6 +1285,34 @@ void GuiReceiver::setOpenGlDrawCallback(QString name, void* callback, void* user
|
||||
w->setOpenGlDrawCallback((CvOpenGlDrawCallback) callback, userdata);
|
||||
}
|
||||
|
||||
void GuiReceiver::setOpenGlFreeCallback(QString name, void* callback)
|
||||
{
|
||||
QPointer<CvWindow> w = icvFindWindowByName(name);
|
||||
|
||||
if (w)
|
||||
w->setOpenGlFreeCallback((CvOpenGlDrawCallback)callback);
|
||||
}
|
||||
|
||||
void* GuiReceiver::getOpenGlDrawCallback(QString name)
|
||||
{
|
||||
QPointer<CvWindow> w = icvFindWindowByName(name);
|
||||
|
||||
if (!w)
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<void*>(w->getOpenGlDrawCallback());
|
||||
}
|
||||
|
||||
void* GuiReceiver::getOpenGlUserData(QString name)
|
||||
{
|
||||
QPointer<CvWindow> w = icvFindWindowByName(name);
|
||||
|
||||
if (!w)
|
||||
return nullptr;
|
||||
|
||||
return w->getOpenGlUserData();
|
||||
}
|
||||
|
||||
void GuiReceiver::setOpenGlContext(QString name)
|
||||
{
|
||||
QPointer<CvWindow> w = icvFindWindowByName(name);
|
||||
@@ -1893,6 +1968,24 @@ void CvWindow::setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userda
|
||||
}
|
||||
|
||||
|
||||
void CvWindow::setOpenGlFreeCallback(CvOpenGlFreeCallback callback)
|
||||
{
|
||||
myView->setOpenGlFreeCallback(callback);
|
||||
}
|
||||
|
||||
|
||||
CvOpenGlDrawCallback CvWindow::getOpenGlDrawCallback()
|
||||
{
|
||||
return myView->getOpenGlDrawCallback();
|
||||
}
|
||||
|
||||
|
||||
void* CvWindow::getOpenGlUserData()
|
||||
{
|
||||
return myView->getOpenGlUserData();
|
||||
}
|
||||
|
||||
|
||||
void CvWindow::makeCurrentOpenGlContext()
|
||||
{
|
||||
myView->makeCurrentOpenGlContext();
|
||||
@@ -2587,6 +2680,24 @@ void DefaultViewPort::setOpenGlDrawCallback(CvOpenGlDrawCallback /*callback*/, v
|
||||
}
|
||||
|
||||
|
||||
void DefaultViewPort::setOpenGlFreeCallback(CvOpenGlFreeCallback /*callback*/)
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL");
|
||||
}
|
||||
|
||||
|
||||
CvOpenGlDrawCallback DefaultViewPort::getOpenGlDrawCallback()
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL");
|
||||
}
|
||||
|
||||
|
||||
void* DefaultViewPort::getOpenGlUserData()
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL");
|
||||
}
|
||||
|
||||
|
||||
void DefaultViewPort::makeCurrentOpenGlContext()
|
||||
{
|
||||
CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL");
|
||||
@@ -3201,11 +3312,15 @@ void DefaultViewPort::setSize(QSize /*size_*/)
|
||||
OpenGlViewPort::OpenGlViewPort(QWidget* _parent) : OpenCVQtWidgetBase(_parent), OCVViewPort(), size(-1, -1)
|
||||
{
|
||||
glDrawCallback = 0;
|
||||
glFreeCallback = 0;
|
||||
glDrawData = 0;
|
||||
}
|
||||
|
||||
OpenGlViewPort::~OpenGlViewPort()
|
||||
{
|
||||
// Fire the free callback so user GL resources are released (parity with GTK/w32).
|
||||
if (glFreeCallback && glDrawData)
|
||||
glFreeCallback(glDrawData);
|
||||
}
|
||||
|
||||
QWidget* OpenGlViewPort::getWidget()
|
||||
@@ -3245,6 +3360,21 @@ void OpenGlViewPort::setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void*
|
||||
glDrawData = userdata;
|
||||
}
|
||||
|
||||
void OpenGlViewPort::setOpenGlFreeCallback(CvOpenGlFreeCallback callback)
|
||||
{
|
||||
glFreeCallback = callback;
|
||||
}
|
||||
|
||||
CvOpenGlDrawCallback OpenGlViewPort::getOpenGlDrawCallback()
|
||||
{
|
||||
return glDrawCallback;
|
||||
}
|
||||
|
||||
void* OpenGlViewPort::getOpenGlUserData()
|
||||
{
|
||||
return glDrawData;
|
||||
}
|
||||
|
||||
void OpenGlViewPort::makeCurrentOpenGlContext()
|
||||
{
|
||||
makeCurrent();
|
||||
|
||||
@@ -156,6 +156,9 @@ public slots:
|
||||
void enablePropertiesButtonEachWindow();
|
||||
|
||||
void setOpenGlDrawCallback(QString name, void* callback, void* userdata);
|
||||
void setOpenGlFreeCallback(QString name, void* callback);
|
||||
void* getOpenGlDrawCallback(QString name);
|
||||
void* getOpenGlUserData(QString name);
|
||||
void setOpenGlContext(QString name);
|
||||
void updateWindow(QString name);
|
||||
double isOpenGl(QString name);
|
||||
@@ -327,6 +330,9 @@ public:
|
||||
static void addSlider2(CvWindow* w, QString name, int* value, int count, CvTrackbarCallback2 on_change, void* userdata);
|
||||
|
||||
void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata);
|
||||
void setOpenGlFreeCallback(CvOpenGlFreeCallback callback);
|
||||
CvOpenGlDrawCallback getOpenGlDrawCallback();
|
||||
void* getOpenGlUserData();
|
||||
void makeCurrentOpenGlContext();
|
||||
void updateGl();
|
||||
bool isOpenGl();
|
||||
@@ -413,6 +419,9 @@ public:
|
||||
virtual void startDisplayInfo(QString text, int delayms) = 0;
|
||||
|
||||
virtual void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) = 0;
|
||||
virtual void setOpenGlFreeCallback(CvOpenGlFreeCallback callback) = 0;
|
||||
virtual CvOpenGlDrawCallback getOpenGlDrawCallback() = 0;
|
||||
virtual void* getOpenGlUserData() = 0;
|
||||
virtual void makeCurrentOpenGlContext() = 0;
|
||||
virtual void updateGl() = 0;
|
||||
|
||||
@@ -465,6 +474,9 @@ public:
|
||||
void startDisplayInfo(QString text, int delayms) CV_OVERRIDE;
|
||||
|
||||
void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) CV_OVERRIDE;
|
||||
void setOpenGlFreeCallback(CvOpenGlFreeCallback callback) CV_OVERRIDE;
|
||||
CvOpenGlDrawCallback getOpenGlDrawCallback() CV_OVERRIDE;
|
||||
void* getOpenGlUserData() CV_OVERRIDE;
|
||||
void makeCurrentOpenGlContext() CV_OVERRIDE;
|
||||
void updateGl() CV_OVERRIDE;
|
||||
|
||||
@@ -487,6 +499,7 @@ private:
|
||||
QSize size;
|
||||
|
||||
CvOpenGlDrawCallback glDrawCallback;
|
||||
CvOpenGlFreeCallback glFreeCallback;
|
||||
void* glDrawData;
|
||||
};
|
||||
|
||||
@@ -514,6 +527,10 @@ public:
|
||||
void startDisplayInfo(QString text, int delayms) CV_OVERRIDE;
|
||||
|
||||
void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) CV_OVERRIDE;
|
||||
void setOpenGlFreeCallback(CvOpenGlFreeCallback callback) CV_OVERRIDE;
|
||||
CvOpenGlDrawCallback getOpenGlDrawCallback() CV_OVERRIDE;
|
||||
void* getOpenGlUserData() CV_OVERRIDE;
|
||||
|
||||
void makeCurrentOpenGlContext() CV_OVERRIDE;
|
||||
void updateGl() CV_OVERRIDE;
|
||||
|
||||
|
||||
@@ -557,7 +557,7 @@ struct CvWindow : CvUIBase
|
||||
last_key(0), flags(0), status(0),
|
||||
on_mouse(NULL), on_mouse_param(NULL)
|
||||
#ifdef HAVE_OPENGL
|
||||
,useGl(false), glDrawCallback(NULL), glDrawData(NULL), glArea(NULL)
|
||||
,useGl(false), glDrawCallback(NULL), glFreeCallback(NULL), glDrawData(NULL), glArea(NULL)
|
||||
#endif
|
||||
{
|
||||
CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name);
|
||||
@@ -583,6 +583,7 @@ struct CvWindow : CvUIBase
|
||||
bool useGl;
|
||||
|
||||
CvOpenGlDrawCallback glDrawCallback;
|
||||
CvOpenGlFreeCallback glFreeCallback;
|
||||
void* glDrawData;
|
||||
GtkWidget* glArea;
|
||||
#endif
|
||||
@@ -728,8 +729,10 @@ static Rect getImageRect_(const std::shared_ptr<CvWindow>& window)
|
||||
gint wx, wy;
|
||||
#ifdef HAVE_OPENGL
|
||||
if (window->useGl) {
|
||||
gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy);
|
||||
return Rect(wx, wy, gtk_widget_get_allocated_width(window->widget), gtk_widget_get_allocated_height(window->widget));
|
||||
// OpenGL windows render into the GtkGLArea, not window->widget
|
||||
GtkWidget* surf = window->glArea ? window->glArea : window->widget;
|
||||
gtk_widget_translate_coordinates(surf, gtk_widget_get_toplevel(surf), 0, 0, &wx, &wy);
|
||||
return Rect(wx, wy, gtk_widget_get_allocated_width(surf), gtk_widget_get_allocated_height(surf));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -846,8 +849,14 @@ double cvGetRatioWindow_GTK(const char* name)
|
||||
|
||||
static double getRatioWindow_(const std::shared_ptr<CvWindow>& window)
|
||||
{
|
||||
#ifdef HAVE_OPENGL
|
||||
// OpenGL windows render into the GtkGLArea, not window->widget.
|
||||
GtkWidget* surf = (window->useGl && window->glArea) ? window->glArea : window->widget;
|
||||
#else
|
||||
GtkWidget* surf = window->widget;
|
||||
#endif
|
||||
double result = static_cast<double>(
|
||||
gtk_widget_get_allocated_width(window->widget)) / gtk_widget_get_allocated_height(window->widget);
|
||||
gtk_widget_get_allocated_width(surf)) / gtk_widget_get_allocated_height(surf);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1085,6 +1094,7 @@ static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags
|
||||
createGlContext(window);
|
||||
|
||||
window->glDrawCallback = 0;
|
||||
window->glFreeCallback = 0;
|
||||
window->glDrawData = 0;
|
||||
#endif
|
||||
|
||||
@@ -1118,6 +1128,22 @@ static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags
|
||||
gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK) ;
|
||||
#endif //GTK_VERSION3_4
|
||||
|
||||
#if defined(HAVE_OPENGL) && defined(GTK_VERSION3)
|
||||
// For OpenGL windows the GtkGLArea is the visible widget; deliver mouse events from it.
|
||||
if ((flags & cv::WINDOW_OPENGL) && window->glArea)
|
||||
{
|
||||
g_signal_connect( window->glArea, "button-press-event", G_CALLBACK(icvOnMouse), window );
|
||||
g_signal_connect( window->glArea, "button-release-event", G_CALLBACK(icvOnMouse), window );
|
||||
g_signal_connect( window->glArea, "motion-notify-event", G_CALLBACK(icvOnMouse), window );
|
||||
g_signal_connect( window->glArea, "scroll-event", G_CALLBACK(icvOnMouse), window );
|
||||
#if defined(GTK_VERSION3_4)
|
||||
gtk_widget_add_events( window->glArea, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK );
|
||||
#else
|
||||
gtk_widget_add_events( window->glArea, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK );
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
gtk_widget_show( window->frame );
|
||||
gtk_window_set_title(GTK_WINDOW(window->frame), name.c_str());
|
||||
|
||||
@@ -1226,6 +1252,54 @@ void setOpenGLDrawCallbackImpl(const char* name, CvOpenGlDrawCallback callback,
|
||||
window->glDrawData = userdata;
|
||||
}
|
||||
|
||||
void setOpenGLFreeCallbackImpl(const char* name, CvOpenGlFreeCallback callback)
|
||||
{
|
||||
CV_Assert(name && "NULL name string");
|
||||
|
||||
CV_LOCK_MUTEX();
|
||||
|
||||
auto window = icvFindWindowByName(name);
|
||||
if( !window )
|
||||
return;
|
||||
|
||||
if (!window->useGl)
|
||||
CV_Error( cv::Error::OpenGlNotSupported, "Window was created without OpenGL context" );
|
||||
|
||||
window->glFreeCallback = callback;
|
||||
}
|
||||
|
||||
CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* name)
|
||||
{
|
||||
CV_Assert(name && "NULL name string");
|
||||
|
||||
CV_LOCK_MUTEX();
|
||||
|
||||
auto window = icvFindWindowByName(name);
|
||||
if (!window)
|
||||
return NULL;
|
||||
|
||||
if (!window->useGl)
|
||||
return NULL;
|
||||
|
||||
return window->glDrawCallback;
|
||||
}
|
||||
|
||||
void* getOpenGLUserDataImpl(const char* name)
|
||||
{
|
||||
CV_Assert(name && "NULL name string");
|
||||
|
||||
CV_LOCK_MUTEX();
|
||||
|
||||
auto window = icvFindWindowByName(name);
|
||||
if( !window )
|
||||
return NULL;
|
||||
|
||||
if (!window->useGl)
|
||||
return NULL;
|
||||
|
||||
return window->glDrawData;
|
||||
}
|
||||
|
||||
#endif // HAVE_OPENGL
|
||||
|
||||
|
||||
@@ -1239,6 +1313,12 @@ CvWindow::~CvWindow()
|
||||
inline void CvWindow::destroy()
|
||||
{
|
||||
CV_LOG_INFO(NULL, "OpenCV/UI: destroying GTK window: " << name);
|
||||
#ifdef HAVE_OPENGL
|
||||
if (glFreeCallback && glDrawData) {
|
||||
glFreeCallback(glDrawData);
|
||||
glDrawData = 0;
|
||||
}
|
||||
#endif // HAVE_OPENGL
|
||||
gtk_widget_destroy(frame);
|
||||
frame = nullptr;
|
||||
}
|
||||
@@ -1837,16 +1917,21 @@ static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_da
|
||||
// TODO move this logic to CvImageWidget
|
||||
// TODO add try-catch wrappers into all callbacks
|
||||
CvWindow* window = (CvWindow*)user_data;
|
||||
bool is_gl_area = false;
|
||||
#if defined(HAVE_OPENGL) && defined(GTK_VERSION3)
|
||||
is_gl_area = (window && window->glArea && widget == window->glArea);
|
||||
#endif
|
||||
if (!window || !widget ||
|
||||
window->signature != CV_WINDOW_MAGIC_VAL ||
|
||||
window->widget != widget ||
|
||||
(window->widget != widget && !is_gl_area) ||
|
||||
!window->on_mouse)
|
||||
return FALSE;
|
||||
|
||||
Point2f pt32f = {-1., -1.};
|
||||
Point pt = {-1,-1};
|
||||
int cv_event = -1, state = 0, flags = 0;
|
||||
CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
|
||||
// for OpenGL windows the image state lives on window->widget
|
||||
CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
|
||||
|
||||
if( event->type == GDK_MOTION_NOTIFY )
|
||||
{
|
||||
|
||||
@@ -229,6 +229,7 @@ struct CvWindow : public std::enable_shared_from_this<CvWindow>
|
||||
HGLRC hGLRC = 0;
|
||||
|
||||
CvOpenGlDrawCallback glDrawCallback = nullptr;
|
||||
CvOpenGlFreeCallback glFreeCallback = nullptr;
|
||||
void* glDrawData = nullptr;
|
||||
#endif
|
||||
};
|
||||
@@ -1084,6 +1085,7 @@ static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags
|
||||
}
|
||||
|
||||
window->glDrawCallback = 0;
|
||||
window->glFreeCallback = 0;
|
||||
window->glDrawData = 0;
|
||||
#endif
|
||||
|
||||
@@ -1157,6 +1159,51 @@ void setOpenGLDrawCallbackImpl(const char* name, CvOpenGlDrawCallback callback,
|
||||
window->glDrawData = userdata;
|
||||
}
|
||||
|
||||
void setOpenGLFreeCallbackImpl(const char* name, CvOpenGlFreeCallback callback)
|
||||
{
|
||||
AutoLock lock(getWindowMutex());
|
||||
|
||||
if (!name)
|
||||
CV_Error(Error::StsNullPtr, "NULL name string");
|
||||
|
||||
auto window = icvFindWindowByName(name);
|
||||
if (!window)
|
||||
CV_Error_(Error::StsNullPtr, ("NULL window: '%s'", name));
|
||||
|
||||
if (!window->useGl)
|
||||
CV_Error(Error::OpenGlNotSupported, "Window was created without OpenGL context");
|
||||
|
||||
window->glFreeCallback = callback;
|
||||
}
|
||||
|
||||
CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* name)
|
||||
{
|
||||
AutoLock lock(getWindowMutex());
|
||||
|
||||
if (!name)
|
||||
CV_Error(Error::StsNullPtr, "NULL name string");
|
||||
|
||||
auto window = icvFindWindowByName(name);
|
||||
if (!window || !window->useGl)
|
||||
return NULL;
|
||||
|
||||
return window->glDrawCallback;
|
||||
}
|
||||
|
||||
void* getOpenGLUserDataImpl(const char* name)
|
||||
{
|
||||
AutoLock lock(getWindowMutex());
|
||||
|
||||
if (!name)
|
||||
CV_Error(Error::StsNullPtr, "NULL name string");
|
||||
|
||||
auto window = icvFindWindowByName(name);
|
||||
if (!window || !window->useGl)
|
||||
return NULL;
|
||||
|
||||
return window->glDrawData;
|
||||
}
|
||||
|
||||
#endif // HAVE_OPENGL
|
||||
|
||||
static void icvRemoveWindow(const std::shared_ptr<CvWindow>& window_)
|
||||
@@ -1179,6 +1226,8 @@ static void icvRemoveWindow(const std::shared_ptr<CvWindow>& window_)
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENGL
|
||||
if (window.glFreeCallback && window.glDrawData)
|
||||
window.glFreeCallback(window.glDrawData);
|
||||
if (window.useGl)
|
||||
releaseGlContext(window);
|
||||
#endif
|
||||
@@ -1800,7 +1849,11 @@ static LRESULT CALLBACK HighGUIProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM
|
||||
pt.x = GET_X_LPARAM(lParam);
|
||||
pt.y = GET_Y_LPARAM(lParam);
|
||||
|
||||
#ifdef HAVE_OPENGL
|
||||
if ((window.flags & cv::WINDOW_AUTOSIZE) || window.glFreeCallback)
|
||||
#else
|
||||
if (window.flags & cv::WINDOW_AUTOSIZE)
|
||||
#endif
|
||||
{
|
||||
// As user can't change window size, do not scale window coordinates. Underlying windowing system
|
||||
// may prevent full window from being displayed and in this case coordinates should not be scaled.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include <opencv2/core/opengl.hpp>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
using namespace cv;
|
||||
|
||||
// ogl wrappers need a current GL context; skip when none is available (headless CI).
|
||||
static void ensureGlContext(const String& w)
|
||||
{
|
||||
try
|
||||
{
|
||||
namedWindow(w, WINDOW_OPENGL);
|
||||
if (getWindowProperty(w, WND_PROP_OPENGL) <= 0)
|
||||
throw cvtest::SkipTestException("window created without an OpenGL context");
|
||||
setOpenGlContext(w);
|
||||
}
|
||||
catch (const cv::Exception&)
|
||||
{
|
||||
throw cvtest::SkipTestException("OpenGL/display not available");
|
||||
}
|
||||
}
|
||||
|
||||
// A grown sole-owner buffer must keep the same GL id (in-place resize).
|
||||
TEST(OpenGL_Buffer, resize_keeps_id)
|
||||
{
|
||||
const String w = "ogl_buf_test";
|
||||
ensureGlContext(w);
|
||||
|
||||
ogl::Buffer buf;
|
||||
buf.create(4, 3, CV_32F, ogl::Buffer::ARRAY_BUFFER, false);
|
||||
unsigned int id1 = buf.bufId();
|
||||
EXPECT_NE(id1, 0u);
|
||||
|
||||
buf.create(16, 3, CV_32F, ogl::Buffer::ARRAY_BUFFER, false); // grow -> resize in place
|
||||
EXPECT_EQ(buf.bufId(), id1);
|
||||
|
||||
destroyWindow(w);
|
||||
}
|
||||
|
||||
// Shader compile + type().
|
||||
TEST(OpenGL_Shader, compile_and_type)
|
||||
{
|
||||
const String w = "ogl_shader_test";
|
||||
ensureGlContext(w);
|
||||
|
||||
const char* vs = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }";
|
||||
ogl::Shader sh(vs, ogl::Shader::VERTEX, false);
|
||||
EXPECT_NE(sh.shaderId(), 0u);
|
||||
EXPECT_EQ(sh.type(), ogl::Shader::VERTEX);
|
||||
|
||||
destroyWindow(w);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -169,13 +169,13 @@ void icvCvt_Gray2BGR_16u_C1C3R( const ushort* gray, int gray_step,
|
||||
ushort* bgr, int bgr_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step/sizeof(gray[0]) )
|
||||
for( ; size.height--; gray += gray_step/(int)sizeof(gray[0]) )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3 )
|
||||
{
|
||||
bgr[0] = bgr[1] = bgr[2] = gray[i];
|
||||
}
|
||||
bgr += bgr_step/sizeof(bgr[0]) - size.width*3;
|
||||
bgr += bgr_step/(int)sizeof(bgr[0]) - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +214,8 @@ void icvCvt_BGRA2BGR_16u_C4C3R( const ushort* bgra, int bgra_step,
|
||||
bgr[0] = t0; bgr[1] = t1;
|
||||
t0 = bgra[swap_rb^2]; bgr[2] = t0;
|
||||
}
|
||||
bgr += bgr_step/sizeof(bgr[0]) - size.width*3;
|
||||
bgra += bgra_step/sizeof(bgra[0]) - size.width*4;
|
||||
bgr += bgr_step/(int)sizeof(bgr[0]) - size.width*3;
|
||||
bgra += bgra_step/(int)sizeof(bgra[0]) - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,8 +252,8 @@ void icvCvt_BGRA2RGBA_16u_C4R( const ushort* bgra, int bgra_step,
|
||||
rgba[0] = t2; rgba[1] = t1;
|
||||
rgba[2] = t0; rgba[3] = t3;
|
||||
}
|
||||
bgra += bgra_step/sizeof(bgra[0]) - size.width*4;
|
||||
rgba += rgba_step/sizeof(rgba[0]) - size.width*4;
|
||||
bgra += bgra_step/(int)sizeof(bgra[0]) - size.width*4;
|
||||
rgba += rgba_step/(int)sizeof(rgba[0]) - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -689,6 +689,28 @@ TEST(Imgcodecs_Tiff, readWrite_unsigned)
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/29615
|
||||
// Decoding a 16-bit 4-channel TIFF used to form an out of range pointer in
|
||||
// icvCvt_BGRA2RGBA_16u_C4R because the byte step was divided by an unsigned
|
||||
// sizeof and then had size.width*4 subtracted, which wraps around for a zero
|
||||
// step. This just checks that a 16UC4 TIFF round-trips correctly; the value is
|
||||
// mainly that the sanitizer builds no longer report the pointer overflow.
|
||||
TEST(Imgcodecs_Tiff, regression_29615_16UC4)
|
||||
{
|
||||
Mat img(4, 3, CV_16UC4);
|
||||
randu(img, Scalar::all(0), Scalar::all(65535));
|
||||
|
||||
vector<uchar> buf;
|
||||
ASSERT_NO_THROW(ASSERT_TRUE(imencode(".tiff", img, buf)));
|
||||
|
||||
Mat decoded;
|
||||
ASSERT_NO_THROW(decoded = imdecode(buf, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(decoded.empty());
|
||||
ASSERT_EQ(CV_16UC4, decoded.type());
|
||||
ASSERT_EQ(img.size(), decoded.size());
|
||||
EXPECT_EQ(0, cvtest::norm(img, decoded, NORM_INF));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Tiff, readWrite_32FC1)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
|
||||
@@ -5659,7 +5659,7 @@ namespace cv{
|
||||
const char *currentParallelFramework = cv::currentParallelFramework();
|
||||
const int nThreads = cv::getNumThreads();
|
||||
|
||||
CV_Assert(iDepth == CV_8U || iDepth == CV_8S);
|
||||
CV_Assert(iDepth == CV_8U || iDepth == CV_8S || iDepth == CV_Bool);
|
||||
|
||||
//Run parallel labeling only if the rows of the image are at least twice the number of available threads
|
||||
const bool is_parallel = currentParallelFramework != NULL && nThreads > 1 && L.rows / nThreads >= 2;
|
||||
|
||||
@@ -830,7 +830,7 @@ trueDistTrans( const Mat& src, Mat& dst )
|
||||
|
||||
CV_Assert( src.size() == dst.size() );
|
||||
|
||||
CV_Assert( src.type() == CV_8UC1 && dst.type() == CV_32FC1 );
|
||||
CV_Assert( (src.type() == CV_8UC1 || src.type() == CV_BoolC1) && dst.type() == CV_32FC1 );
|
||||
int i, m = src.rows, n = src.cols;
|
||||
|
||||
cv::AutoBuffer<uchar> _buf(std::max(m*2*sizeof(int) + (m*3+1)*sizeof(int), n*2*sizeof(float)));
|
||||
@@ -886,7 +886,7 @@ distanceATS_L1_8u( const Mat& src, Mat& dst )
|
||||
int srcstep = (int)src.step;
|
||||
int dststep = (int)dst.step;
|
||||
|
||||
CV_Assert( src.type() == CV_8UC1 && dst.type() == CV_8UC1 );
|
||||
CV_Assert( (src.type() == CV_8UC1 || src.type() == CV_BoolC1) && dst.type() == CV_8UC1 );
|
||||
CV_Assert( src.size() == dst.size() );
|
||||
|
||||
////////////////////// forward scan ////////////////////////
|
||||
@@ -957,7 +957,7 @@ static void distanceTransform_L1_8U(InputArray _src, OutputArray _dst)
|
||||
|
||||
Mat src = _src.getMat();
|
||||
|
||||
CV_Assert( src.type() == CV_8UC1);
|
||||
CV_Assert( src.type() == CV_8UC1 || src.type() == CV_BoolC1);
|
||||
|
||||
_dst.create( src.size(), CV_8UC1);
|
||||
Mat dst = _dst.getMat();
|
||||
@@ -978,7 +978,7 @@ void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labe
|
||||
Mat src = _src.getMat(), labels;
|
||||
bool need_labels = _labels.needed();
|
||||
|
||||
CV_Assert( src.type() == CV_8UC1);
|
||||
CV_Assert( src.type() == CV_8UC1 || src.type() == CV_BoolC1);
|
||||
|
||||
_dst.create( src.size(), CV_32F);
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
@@ -743,6 +743,73 @@ TEST(Imgproc_ConnectedComponents, single_column)
|
||||
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/29593
|
||||
// cv::Mat_<bool>::depth() returns CV_Bool in 5.0, where it was CV_8U in 4.x, so
|
||||
// connected components should keep accepting boolean masks as binary input.
|
||||
TEST(Imgproc_ConnectedComponents, bool_mask_29593)
|
||||
{
|
||||
Mat_<bool> input_bool(5, 6, false);
|
||||
input_bool(0, 0) = true;
|
||||
input_bool(0, 1) = true;
|
||||
input_bool(1, 1) = true;
|
||||
input_bool(2, 4) = true;
|
||||
input_bool(3, 4) = true;
|
||||
input_bool(3, 5) = true;
|
||||
input_bool(4, 2) = true;
|
||||
|
||||
Mat1b input_uchar(input_bool.size(), uchar(0));
|
||||
for (int r = 0; r < input_bool.rows; ++r)
|
||||
{
|
||||
for (int c = 0; c < input_bool.cols; ++c)
|
||||
{
|
||||
input_uchar(r, c) = input_bool(r, c) ? uchar(255) : uchar(0);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(input_bool.depth(), CV_Bool);
|
||||
|
||||
const int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI,
|
||||
cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
|
||||
|
||||
for (int connectivity : {4, 8})
|
||||
{
|
||||
for (size_t cclt = 0; cclt < sizeof(ccltype) / sizeof(ccltype[0]); ++cclt)
|
||||
{
|
||||
Mat1i labels_bool, labels_uchar;
|
||||
int nlabels_bool = 0;
|
||||
ASSERT_NO_THROW(nlabels_bool = connectedComponents(
|
||||
input_bool, labels_bool, connectivity, CV_32S, ccltype[cclt]));
|
||||
const int nlabels_uchar = connectedComponents(
|
||||
input_uchar, labels_uchar, connectivity, CV_32S, ccltype[cclt]);
|
||||
|
||||
normalizeLabels(labels_bool, nlabels_bool);
|
||||
normalizeLabels(labels_uchar, nlabels_uchar);
|
||||
|
||||
EXPECT_EQ(nlabels_bool, nlabels_uchar)
|
||||
<< "connectivity = " << connectivity << ", ccltype = " << ccltype[cclt];
|
||||
EXPECT_EQ(countNonZero(labels_bool != labels_uchar), 0)
|
||||
<< "connectivity = " << connectivity << ", ccltype = " << ccltype[cclt];
|
||||
|
||||
Mat stats_bool, centroids_bool, stats_uchar, centroids_uchar;
|
||||
ASSERT_NO_THROW(nlabels_bool = connectedComponentsWithStats(
|
||||
input_bool, labels_bool, stats_bool, centroids_bool, connectivity, CV_32S, ccltype[cclt]));
|
||||
const int nlabels_uchar_with_stats = connectedComponentsWithStats(
|
||||
input_uchar, labels_uchar, stats_uchar, centroids_uchar, connectivity, CV_32S, ccltype[cclt]);
|
||||
|
||||
normalizeLabels(labels_bool, nlabels_bool);
|
||||
normalizeLabels(labels_uchar, nlabels_uchar_with_stats);
|
||||
|
||||
EXPECT_EQ(nlabels_bool, nlabels_uchar_with_stats)
|
||||
<< "connectivity = " << connectivity << ", ccltype = " << ccltype[cclt];
|
||||
EXPECT_EQ(countNonZero(labels_bool != labels_uchar), 0)
|
||||
<< "connectivity = " << connectivity << ", ccltype = " << ccltype[cclt];
|
||||
EXPECT_EQ(countNonZero(stats_bool != stats_uchar), 0)
|
||||
<< "connectivity = " << connectivity << ", ccltype = " << ccltype[cclt];
|
||||
EXPECT_EQ(cv::norm(centroids_bool, centroids_uchar, NORM_INF), 0)
|
||||
<< "connectivity = " << connectivity << ", ccltype = " << ccltype[cclt];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgproc_ConnectedComponents, 4conn_regression_21366)
|
||||
{
|
||||
|
||||
@@ -209,4 +209,49 @@ TEST(Imgproc_DistanceTransform, ipp_deterministic)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/29596
|
||||
// cv::Mat_<bool>::depth() returns CV_Bool in 5.0, where it was CV_8U in 4.x, so
|
||||
// distanceTransform started rejecting boolean masks. CV_Bool is a 1-byte type
|
||||
// whose values are only ever zero-tested here, so the result must match the one
|
||||
// produced by an equivalent CV_8UC1 mask.
|
||||
TEST(Imgproc_DistanceTransform, bool_mask_29596)
|
||||
{
|
||||
Mat_<bool> mask(7, 7, true);
|
||||
mask(3, 3) = false;
|
||||
mask(0, 6) = false;
|
||||
|
||||
Mat_<uchar> ref(7, 7, (uchar)255);
|
||||
ref(3, 3) = 0;
|
||||
ref(0, 6) = 0;
|
||||
|
||||
ASSERT_EQ(mask.depth(), CV_Bool);
|
||||
|
||||
// CV_32F output: 3x3 and 5x5 kernels, plus the precise (trueDistTrans) path
|
||||
const int maskSizes[] = { DIST_MASK_3, DIST_MASK_5, DIST_MASK_PRECISE };
|
||||
for (size_t i = 0; i < sizeof(maskSizes)/sizeof(maskSizes[0]); i++)
|
||||
{
|
||||
Mat dist_bool, dist_ref;
|
||||
ASSERT_NO_THROW(distanceTransform(mask, dist_bool, DIST_L2, maskSizes[i]));
|
||||
distanceTransform(ref, dist_ref, DIST_L2, maskSizes[i]);
|
||||
EXPECT_EQ(cv::norm(dist_bool, dist_ref, NORM_INF), 0) << "maskSize = " << maskSizes[i];
|
||||
}
|
||||
|
||||
// CV_8U output path (distanceTransform_L1_8U -> distanceATS_L1_8u)
|
||||
{
|
||||
Mat dist_bool, dist_ref;
|
||||
ASSERT_NO_THROW(distanceTransform(mask, dist_bool, DIST_L1, DIST_MASK_3, CV_8U));
|
||||
distanceTransform(ref, dist_ref, DIST_L1, DIST_MASK_3, CV_8U);
|
||||
EXPECT_EQ(cv::norm(dist_bool, dist_ref, NORM_INF), 0);
|
||||
}
|
||||
|
||||
// labels overload
|
||||
{
|
||||
Mat dist_bool, labels_bool, dist_ref, labels_ref;
|
||||
ASSERT_NO_THROW(distanceTransform(mask, dist_bool, labels_bool, DIST_L2, DIST_MASK_5));
|
||||
distanceTransform(ref, dist_ref, labels_ref, DIST_L2, DIST_MASK_5);
|
||||
EXPECT_EQ(cv::norm(dist_bool, dist_ref, NORM_INF), 0);
|
||||
EXPECT_EQ(cv::norm(labels_bool, labels_ref, NORM_INF), 0);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -4,8 +4,8 @@ set(debug_modules "")
|
||||
if(DEBUG_opencv_ptcloud)
|
||||
list(APPEND debug_modules opencv_highgui)
|
||||
endif()
|
||||
ocv_define_module(ptcloud opencv_geometry opencv_imgproc opencv_flann ${debug_modules}
|
||||
WRAP java objc python js
|
||||
ocv_define_module(ptcloud opencv_geometry opencv_imgproc opencv_features opencv_video ${debug_modules}
|
||||
WRAP java objc python js
|
||||
)
|
||||
ocv_target_link_libraries(${the_module} ${LAPACK_LIBRARIES})
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "opencv2/ptcloud/odometry.hpp"
|
||||
#include "opencv2/ptcloud/odometry_frame.hpp"
|
||||
#include "opencv2/ptcloud/odometry_settings.hpp"
|
||||
#include "opencv2/ptcloud/slam.hpp"
|
||||
|
||||
/**
|
||||
@defgroup ptcloud Point Cloud Processing
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_PTCLOUD_SLAM_HPP
|
||||
#define OPENCV_PTCLOUD_SLAM_HPP
|
||||
|
||||
/**
|
||||
@defgroup slam SLAM and Visual Odometry
|
||||
|
||||
Monocular visual odometry pipeline. Entry point is @ref cv::slam::VisualOdometry.
|
||||
Bootstraps an initial map from two-view geometry, then tracks subsequent frames
|
||||
with PnP, growing the map at keyframe promotions.
|
||||
*/
|
||||
|
||||
#include "opencv2/ptcloud/slam/types.hpp"
|
||||
#include "opencv2/ptcloud/slam/map.hpp"
|
||||
#include "opencv2/ptcloud/slam/odometry_params.hpp"
|
||||
#include "opencv2/ptcloud/slam/visual_odometry.hpp"
|
||||
|
||||
#endif // OPENCV_PTCLOUD_SLAM_HPP
|
||||
@@ -0,0 +1,78 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_SLAM_MAP_HPP
|
||||
#define OPENCV_SLAM_MAP_HPP
|
||||
|
||||
#include "opencv2/ptcloud/slam/types.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
//! @addtogroup slam
|
||||
//! @{
|
||||
|
||||
/** @brief Owns all persistent SLAM state (KeyFrames and MapPoints).
|
||||
Pointers from addKeyframe / addMapPoint are valid until removeMapPoint / clear. */
|
||||
class CV_EXPORTS Map
|
||||
{
|
||||
public:
|
||||
Map();
|
||||
~Map();
|
||||
|
||||
Map(const Map&) = delete;
|
||||
Map& operator=(const Map&) = delete;
|
||||
|
||||
// Keyframes
|
||||
|
||||
KeyFrame* addKeyframe(KeyFrame* kf); //!< takes ownership; assigns id if < 0
|
||||
KeyFrame* getKeyframe(int id) const;
|
||||
|
||||
const std::set<KeyFrame*>& keyframes() const;
|
||||
int numKeyframes() const;
|
||||
|
||||
// Map points
|
||||
|
||||
MapPoint* addMapPoint(MapPoint* mp); //!< takes ownership; assigns id if < 0
|
||||
MapPoint* getMapPoint(int id) const;
|
||||
|
||||
const std::set<MapPoint*>& mapPoints() const;
|
||||
int numMapPoints() const;
|
||||
|
||||
void addObservation(KeyFrame* kf, size_t kpIdx, MapPoint* mp);
|
||||
void removeObservation(KeyFrame* kf, MapPoint* mp);
|
||||
void removeMapPoint(MapPoint* mp);
|
||||
|
||||
// Reference / current keyframes
|
||||
|
||||
void setRefKeyframe(KeyFrame* kf);
|
||||
KeyFrame* getRefKeyframe() const;
|
||||
|
||||
void setCurrentKeyframe(KeyFrame* kf);
|
||||
KeyFrame* getCurrentKeyframe() const;
|
||||
|
||||
// Trajectory
|
||||
|
||||
void appendPose(const Matx44d& T_cw);
|
||||
const std::vector<Matx44d>& trajectory() const;
|
||||
|
||||
// Lifecycle
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
Ptr<Impl> impl;
|
||||
};
|
||||
|
||||
//! @}
|
||||
|
||||
}} // namespace cv::slam
|
||||
|
||||
#endif // OPENCV_SLAM_MAP_HPP
|
||||
@@ -0,0 +1,65 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_SLAM_ODOMETRY_PARAMS_HPP
|
||||
#define OPENCV_SLAM_ODOMETRY_PARAMS_HPP
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
//! @addtogroup slam
|
||||
//! @{
|
||||
|
||||
/** @brief Tunable parameters for visual odometry: initialization, tracking, keyframe selection, and local-map refinement. */
|
||||
struct CV_EXPORTS_W_SIMPLE OdometryParams
|
||||
{
|
||||
CV_WRAP OdometryParams() {}
|
||||
|
||||
// Bootstrap (two-view map initialization)
|
||||
CV_PROP_RW int minInitInliers = 40; //!< Minimum match/inlier count at each bootstrap stage.
|
||||
CV_PROP_RW double minInitParallaxDeg = 1.5; //!< Minimum parallax (deg) to trigger initialization.
|
||||
CV_PROP_RW int minInitPoints = 50; //!< Minimum triangulated points to seed the map.
|
||||
CV_PROP_RW double hfRatioThresh = 0.45; //!< Homography/fundamental score ratio above which homography is chosen.
|
||||
CV_PROP_RW double minGrowthParallaxDeg = 0.1; //!< Minimum parallax (deg) to triangulate new points during map growth.
|
||||
CV_PROP_RW double essentialRansacThresh = 1.0; //!< RANSAC reprojection threshold (px) for essential-matrix/homography estimation.
|
||||
CV_PROP_RW double essentialRansacConfidence = 0.999; //!< RANSAC confidence for essential-matrix estimation.
|
||||
|
||||
// Tracking (PnP)
|
||||
CV_PROP_RW double pnpReprojThresh = 4.0; //!< PnP RANSAC reprojection threshold (px).
|
||||
CV_PROP_RW int pnpMinInliers = 6; //!< Minimum PnP inliers to accept a pose.
|
||||
CV_PROP_RW int pnpRansacIters = 500; //!< Maximum PnP RANSAC iterations.
|
||||
CV_PROP_RW double pnpConfidence = 0.99; //!< PnP RANSAC confidence.
|
||||
|
||||
// Motion model (guided match search)
|
||||
CV_PROP_RW double motionModelRadius = 15.0; //!< Guided-match search radius (px).
|
||||
CV_PROP_RW double motionModelRadiusWide = 30.0; //!< Wider fallback search radius (px) when the narrow search finds too few.
|
||||
CV_PROP_RW int motionModelMinMatches = 20; //!< Matches below which the wider search runs.
|
||||
CV_PROP_RW double descProjThresh = 1.0; //!< Descriptor-distance cutoff for a projected map-point match.
|
||||
|
||||
// Optical flow fallback
|
||||
CV_PROP_RW int opticalFlowMinInliers = 10; //!< Minimum correspondences for the optical-flow fallback.
|
||||
|
||||
// Keyframe promotion
|
||||
CV_PROP_RW int kfMinFrames = 1; //!< Minimum frames since last keyframe before inserting one.
|
||||
CV_PROP_RW int kfMaxFrames = 30; //!< Frames since last keyframe after which one is forced.
|
||||
CV_PROP_RW double kfInlierRatio = 0.70; //!< Insert a keyframe when inliers drop below this fraction of the last keyframe's.
|
||||
CV_PROP_RW int kfMinInliers = 40; //!< Absolute inlier floor: max(kfMinInliers, kfInlierRatio * lastKfInliers).
|
||||
CV_PROP_RW double kfRotThreshDeg = 5.0; //!< Rotation (deg) from last keyframe that forces a new one.
|
||||
CV_PROP_RW double kfTransThresh = 0.5; //!< Translation from last keyframe that forces a new one.
|
||||
|
||||
// Local map refinement
|
||||
CV_PROP_RW int localMapTopK = 10; //!< Top co-visible keyframes forming the local map.
|
||||
CV_PROP_RW int localMapNeighborK = 5; //!< Covisibility neighbors expanded per local-map keyframe.
|
||||
CV_PROP_RW double localMapRadius = 7.0; //!< Reprojection search radius (px) for local-map points.
|
||||
};
|
||||
|
||||
//! @}
|
||||
|
||||
}} // namespace cv::slam
|
||||
|
||||
#endif // OPENCV_SLAM_ODOMETRY_PARAMS_HPP
|
||||
@@ -0,0 +1,71 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_SLAM_TYPES_HPP
|
||||
#define OPENCV_SLAM_TYPES_HPP
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/core/types.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
//! @addtogroup slam
|
||||
//! @{
|
||||
|
||||
// Pipeline lifecycle state
|
||||
enum OdometryState
|
||||
{
|
||||
NOT_INITIALIZED = 0,
|
||||
INITIALIZING = 1,
|
||||
TRACKING = 2
|
||||
};
|
||||
|
||||
struct MapPoint;
|
||||
struct KeyFrame;
|
||||
|
||||
// 3D landmark, owned by Map
|
||||
struct CV_EXPORTS MapPoint
|
||||
{
|
||||
int id = -1;
|
||||
Point3d pos { 0, 0, 0 };
|
||||
Mat refDesc;
|
||||
|
||||
std::map<KeyFrame*, size_t> observations; // kf -> keypoint index
|
||||
|
||||
int visibleCount = 0;
|
||||
int foundCount = 0;
|
||||
bool bad = false; // soft-delete; check before use
|
||||
};
|
||||
|
||||
// Keyframe: pose + keypoints + covisibility graph, owned by Map
|
||||
struct CV_EXPORTS KeyFrame
|
||||
{
|
||||
int id = -1;
|
||||
Matx44d poseCw = Matx44d::eye(); // world->camera
|
||||
|
||||
std::vector<KeyPoint> keypoints;
|
||||
Mat descriptors;
|
||||
std::vector<Point2f> undistKpts; // parallel to keypoints
|
||||
Size imageSize;
|
||||
|
||||
std::vector<MapPoint*> mapPoints; // parallel to keypoints; null = unmatched
|
||||
|
||||
std::map<KeyFrame*, int> covisibility;
|
||||
std::vector<std::pair<KeyFrame*, int>> orderedCovisibility; // sorted descending by count
|
||||
|
||||
KeyFrame* parent = nullptr;
|
||||
Mat globalDesc;
|
||||
};
|
||||
|
||||
//! @}
|
||||
|
||||
}} // namespace cv::slam
|
||||
|
||||
#endif // OPENCV_SLAM_TYPES_HPP
|
||||
@@ -0,0 +1,79 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_SLAM_VISUAL_ODOMETRY_HPP
|
||||
#define OPENCV_SLAM_VISUAL_ODOMETRY_HPP
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/features.hpp"
|
||||
|
||||
#include "opencv2/ptcloud/slam/types.hpp"
|
||||
#include "opencv2/ptcloud/slam/map.hpp"
|
||||
#include "opencv2/ptcloud/slam/odometry_params.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
//! @addtogroup slam
|
||||
//! @{
|
||||
|
||||
/** @brief Monocular visual odometry pipeline.
|
||||
|
||||
State machine: NOT_INITIALIZED → INITIALIZING (H/F two-view bootstrap) → TRACKING
|
||||
(per-frame PnP + local-map refinement). Tracking failure rewinds to INITIALIZING.
|
||||
|
||||
Purely in-memory: feed images with @ref processFrame and read back the trajectory
|
||||
and map with @ref getTrajectory / @ref getMap. See samples/slam for driving this
|
||||
from a directory of images and writing the result to disk (e.g. COLMAP format).
|
||||
*/
|
||||
class CV_EXPORTS_W VisualOdometry
|
||||
{
|
||||
public:
|
||||
virtual ~VisualOdometry();
|
||||
|
||||
CV_WRAP static Ptr<VisualOdometry> create(
|
||||
const Ptr<Feature2D>& detector,
|
||||
const Ptr<DescriptorMatcher>& matcher,
|
||||
InputArray cameraMatrix,
|
||||
InputArray distCoeffs = noArray(),
|
||||
const OdometryParams& params = OdometryParams());
|
||||
|
||||
/** @brief Feed one image. Returns true if a pose was emitted. */
|
||||
CV_WRAP virtual bool processFrame(InputArray image) = 0;
|
||||
|
||||
/** @brief Reset to NOT_INITIALIZED, clearing map and trajectory. */
|
||||
CV_WRAP virtual void reset() = 0;
|
||||
|
||||
CV_WRAP virtual OdometryState getState() const = 0;
|
||||
CV_WRAP virtual Matx44d getLastPose() const = 0;
|
||||
|
||||
//! @note Not exposed to Python: Map holds raw pointers / non-convertible containers.
|
||||
virtual const Map& getMap() const = 0;
|
||||
|
||||
//! @brief Number of keyframes in the map. Convenience for callers (e.g. Python) that
|
||||
//! can't use @ref getMap directly.
|
||||
CV_WRAP virtual int getNumKeyframes() const = 0;
|
||||
|
||||
//! @brief Number of map points in the map. Convenience for callers (e.g. Python) that
|
||||
//! can't use @ref getMap directly.
|
||||
CV_WRAP virtual int getNumMapPoints() const = 0;
|
||||
|
||||
CV_WRAP virtual const std::vector<Matx44d>& getTrajectory() const = 0;
|
||||
|
||||
CV_WRAP virtual const OdometryParams& getParams() const = 0;
|
||||
CV_WRAP virtual void setParams(const OdometryParams& params) = 0;
|
||||
|
||||
protected:
|
||||
VisualOdometry();
|
||||
};
|
||||
|
||||
//! @}
|
||||
|
||||
}} // namespace cv::slam
|
||||
|
||||
#endif // OPENCV_SLAM_VISUAL_ODOMETRY_HPP
|
||||
@@ -0,0 +1,148 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
struct Map::Impl
|
||||
{
|
||||
std::set<KeyFrame*> keyframes;
|
||||
std::set<MapPoint*> mapPoints;
|
||||
std::unordered_map<int, KeyFrame*> kfIndex;
|
||||
std::unordered_map<int, MapPoint*> mpIndex;
|
||||
|
||||
KeyFrame* refKf = nullptr;
|
||||
KeyFrame* currentKf = nullptr;
|
||||
|
||||
std::vector<Matx44d> trajectory;
|
||||
|
||||
int nextKfId = 0;
|
||||
int nextMpId = 0;
|
||||
};
|
||||
|
||||
Map::Map() : impl(makePtr<Impl>()) {}
|
||||
|
||||
Map::~Map()
|
||||
{
|
||||
for (KeyFrame* kf : impl->keyframes) delete kf;
|
||||
for (MapPoint* mp : impl->mapPoints) delete mp;
|
||||
}
|
||||
|
||||
// Keyframes
|
||||
|
||||
KeyFrame* Map::addKeyframe(KeyFrame* kf)
|
||||
{
|
||||
CV_Assert(kf);
|
||||
if (kf->id < 0)
|
||||
kf->id = impl->nextKfId++;
|
||||
else if (kf->id >= impl->nextKfId)
|
||||
impl->nextKfId = kf->id + 1;
|
||||
impl->keyframes.insert(kf);
|
||||
impl->kfIndex[kf->id] = kf;
|
||||
return kf;
|
||||
}
|
||||
|
||||
KeyFrame* Map::getKeyframe(int id) const
|
||||
{
|
||||
auto it = impl->kfIndex.find(id);
|
||||
return (it != impl->kfIndex.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
const std::set<KeyFrame*>& Map::keyframes() const { return impl->keyframes; }
|
||||
int Map::numKeyframes() const { return (int)impl->keyframes.size(); }
|
||||
|
||||
// Map points
|
||||
|
||||
MapPoint* Map::addMapPoint(MapPoint* mp)
|
||||
{
|
||||
CV_Assert(mp);
|
||||
if (mp->id < 0)
|
||||
mp->id = impl->nextMpId++;
|
||||
else if (mp->id >= impl->nextMpId)
|
||||
impl->nextMpId = mp->id + 1;
|
||||
impl->mapPoints.insert(mp);
|
||||
impl->mpIndex[mp->id] = mp;
|
||||
return mp;
|
||||
}
|
||||
|
||||
MapPoint* Map::getMapPoint(int id) const
|
||||
{
|
||||
auto it = impl->mpIndex.find(id);
|
||||
return (it != impl->mpIndex.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
const std::set<MapPoint*>& Map::mapPoints() const { return impl->mapPoints; }
|
||||
int Map::numMapPoints() const { return (int)impl->mapPoints.size(); }
|
||||
|
||||
// Observations
|
||||
|
||||
void Map::addObservation(KeyFrame* kf, size_t kpIdx, MapPoint* mp)
|
||||
{
|
||||
CV_Assert(kf && mp);
|
||||
CV_Assert(kpIdx < kf->mapPoints.size());
|
||||
if (kf->mapPoints[kpIdx] != nullptr) return;
|
||||
kf->mapPoints[kpIdx] = mp;
|
||||
mp->observations[kf] = kpIdx;
|
||||
}
|
||||
|
||||
void Map::removeObservation(KeyFrame* kf, MapPoint* mp)
|
||||
{
|
||||
if (!kf || !mp) return;
|
||||
auto it = mp->observations.find(kf);
|
||||
if (it == mp->observations.end()) return;
|
||||
size_t kpIdx = it->second;
|
||||
if (kpIdx < kf->mapPoints.size())
|
||||
kf->mapPoints[kpIdx] = nullptr;
|
||||
mp->observations.erase(it);
|
||||
}
|
||||
|
||||
void Map::removeMapPoint(MapPoint* mp)
|
||||
{
|
||||
if (!mp) return;
|
||||
for (auto& [kf, kpIdx] : mp->observations)
|
||||
if (kpIdx < kf->mapPoints.size())
|
||||
kf->mapPoints[kpIdx] = nullptr;
|
||||
impl->mapPoints.erase(mp);
|
||||
impl->mpIndex.erase(mp->id);
|
||||
delete mp;
|
||||
}
|
||||
|
||||
// Reference / current keyframes
|
||||
|
||||
void Map::setRefKeyframe(KeyFrame* kf) { impl->refKf = kf; }
|
||||
KeyFrame* Map::getRefKeyframe() const { return impl->refKf; }
|
||||
|
||||
void Map::setCurrentKeyframe(KeyFrame* kf) { impl->currentKf = kf; }
|
||||
KeyFrame* Map::getCurrentKeyframe() const { return impl->currentKf; }
|
||||
|
||||
// Trajectory
|
||||
|
||||
void Map::appendPose(const Matx44d& T_cw) { impl->trajectory.push_back(T_cw); }
|
||||
const std::vector<Matx44d>& Map::trajectory() const { return impl->trajectory; }
|
||||
|
||||
// Lifecycle
|
||||
|
||||
void Map::clear()
|
||||
{
|
||||
for (KeyFrame* kf : impl->keyframes) delete kf;
|
||||
for (MapPoint* mp : impl->mapPoints) delete mp;
|
||||
impl->keyframes.clear();
|
||||
impl->mapPoints.clear();
|
||||
impl->kfIndex.clear();
|
||||
impl->mpIndex.clear();
|
||||
impl->refKf = nullptr;
|
||||
impl->currentKf = nullptr;
|
||||
impl->trajectory.clear();
|
||||
impl->nextKfId = 0;
|
||||
impl->nextMpId = 0;
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
@@ -0,0 +1,79 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_SLAM_FRAME_HPP
|
||||
#define OPENCV_SLAM_FRAME_HPP
|
||||
|
||||
#include "../precomp.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
/** @brief Per-frame scratch pad. Created per incoming image, never stored in the Map. */
|
||||
struct Frame
|
||||
{
|
||||
Mat image;
|
||||
std::vector<KeyPoint> keypoints;
|
||||
Mat descriptors;
|
||||
std::vector<Point2f> undistKpts; //!< undistorted, parallel to keypoints
|
||||
Size imageSize;
|
||||
|
||||
Matx44d poseCw = Matx44d::eye();
|
||||
std::vector<MapPoint*> mapPoints; //!< parallel to keypoints; nullptr = unmatched
|
||||
std::vector<bool> outliers; //!< parallel to keypoints; true = inlier check failed
|
||||
|
||||
// Grid cell size in pixels — dimensions adapt to each image resolution.
|
||||
static constexpr int CELL_SIZE_PX = 20;
|
||||
|
||||
int gridRows = 0;
|
||||
int gridCols = 0;
|
||||
std::unordered_map<int, std::vector<size_t>> grid; //!< cell key -> keypoint indices
|
||||
|
||||
int cellKey(int row, int col) const { return row * gridCols + col; }
|
||||
|
||||
void buildGrid()
|
||||
{
|
||||
grid.clear();
|
||||
if (imageSize.width <= 0 || imageSize.height <= 0) return;
|
||||
gridCols = std::max(1, (imageSize.width + CELL_SIZE_PX - 1) / CELL_SIZE_PX);
|
||||
gridRows = std::max(1, (imageSize.height + CELL_SIZE_PX - 1) / CELL_SIZE_PX);
|
||||
for (size_t i = 0; i < undistKpts.size(); ++i)
|
||||
{
|
||||
int col = std::min(gridCols - 1, std::max(0, (int)(undistKpts[i].x / CELL_SIZE_PX)));
|
||||
int row = std::min(gridRows - 1, std::max(0, (int)(undistKpts[i].y / CELL_SIZE_PX)));
|
||||
grid[cellKey(row, col)].push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<size_t> getKeypointsInRadius(float x, float y, float r) const
|
||||
{
|
||||
std::vector<size_t> result;
|
||||
if (gridRows <= 0 || gridCols <= 0 || grid.empty()) return result;
|
||||
const int colMin = std::max(0, (int)((x - r) / CELL_SIZE_PX));
|
||||
const int colMax = std::min(gridCols-1, (int)((x + r) / CELL_SIZE_PX));
|
||||
const int rowMin = std::max(0, (int)((y - r) / CELL_SIZE_PX));
|
||||
const int rowMax = std::min(gridRows-1, (int)((y + r) / CELL_SIZE_PX));
|
||||
const float r2 = r * r;
|
||||
for (int row = rowMin; row <= rowMax; ++row)
|
||||
for (int col = colMin; col <= colMax; ++col)
|
||||
{
|
||||
auto it = grid.find(cellKey(row, col));
|
||||
if (it == grid.end()) continue;
|
||||
for (size_t idx : it->second)
|
||||
{
|
||||
float dx = undistKpts[idx].x - x;
|
||||
float dy = undistKpts[idx].y - y;
|
||||
if (dx * dx + dy * dy <= r2)
|
||||
result.push_back(idx);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace cv::slam
|
||||
|
||||
#endif // OPENCV_SLAM_FRAME_HPP
|
||||
@@ -0,0 +1,146 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "vo_impl.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
// Factory
|
||||
|
||||
VisualOdometry::VisualOdometry() = default;
|
||||
VisualOdometry::~VisualOdometry() = default;
|
||||
|
||||
Ptr<VisualOdometry> VisualOdometry::create(
|
||||
const Ptr<Feature2D>& detector,
|
||||
const Ptr<DescriptorMatcher>& matcher,
|
||||
InputArray cameraMatrix,
|
||||
InputArray distCoeffs,
|
||||
const OdometryParams& params)
|
||||
{
|
||||
CV_Assert(detector && "VisualOdometry::create: detector must not be null");
|
||||
CV_Assert(matcher && "VisualOdometry::create: matcher must not be null");
|
||||
|
||||
Mat K = cameraMatrix.getMat();
|
||||
CV_Assert(!K.empty() && K.rows == 3 && K.cols == 3);
|
||||
Mat dist = distCoeffs.empty() ? Mat() : distCoeffs.getMat();
|
||||
|
||||
return makePtr<VisualOdometryImpl>(detector, matcher, K, dist, params);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
|
||||
VisualOdometryImpl::VisualOdometryImpl(
|
||||
const Ptr<Feature2D>& detector_,
|
||||
const Ptr<DescriptorMatcher>& matcher_,
|
||||
const Mat& cameraMatrix,
|
||||
const Mat& distCoeffs,
|
||||
const OdometryParams& params_)
|
||||
: detector(detector_), matcher(matcher_), params(params_)
|
||||
{
|
||||
cameraMatrix.convertTo(K, CV_64F);
|
||||
if (!distCoeffs.empty())
|
||||
distCoeffs.convertTo(dist, CV_64F);
|
||||
}
|
||||
|
||||
// reset / processFrame
|
||||
|
||||
void VisualOdometryImpl::reset()
|
||||
{
|
||||
state = NOT_INITIALIZED;
|
||||
lastPoseCw = Matx44d::eye();
|
||||
refFrame = Frame();
|
||||
lastKf = nullptr;
|
||||
framesSinceKf = 0;
|
||||
lastKfInliers = 0;
|
||||
velocity = Matx44d::eye();
|
||||
hasVelocity = false;
|
||||
prevFrame = Frame();
|
||||
hasPrevFrame = false;
|
||||
lastEvent.clear();
|
||||
map.clear();
|
||||
}
|
||||
|
||||
bool VisualOdometryImpl::processFrame(InputArray image)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
if (image.empty()) return false;
|
||||
lastEvent.clear();
|
||||
|
||||
Frame currentFrame;
|
||||
extractFeatures(image, currentFrame);
|
||||
if (currentFrame.keypoints.empty() || currentFrame.descriptors.empty()) return false;
|
||||
|
||||
currentFrame.mapPoints.assign(currentFrame.keypoints.size(), nullptr);
|
||||
currentFrame.outliers.assign(currentFrame.keypoints.size(), false);
|
||||
currentFrame.buildGrid();
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case NOT_INITIALIZED:
|
||||
refFrame = currentFrame;
|
||||
state = INITIALIZING;
|
||||
return false;
|
||||
|
||||
case INITIALIZING:
|
||||
return bootstrap(currentFrame);
|
||||
|
||||
case TRACKING:
|
||||
return track(currentFrame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Feature extraction
|
||||
|
||||
void VisualOdometryImpl::extractFeatures(InputArray image, Frame& out) const
|
||||
{
|
||||
Mat img = image.getMat();
|
||||
out.imageSize = img.size();
|
||||
out.keypoints.clear();
|
||||
|
||||
// Detect and compute on the original image (color/grey is up to the detector).
|
||||
detector->detectAndCompute(img, noArray(), out.keypoints, out.descriptors);
|
||||
|
||||
// Store a greyscale copy for the optical-flow fallback.
|
||||
if (img.channels() > 1)
|
||||
cvtColor(img, out.image, COLOR_BGR2GRAY);
|
||||
else
|
||||
out.image = img.clone();
|
||||
|
||||
// Pre-compute undistorted pixel coordinates used by every stage.
|
||||
if (!out.keypoints.empty())
|
||||
{
|
||||
std::vector<Point2f> raw;
|
||||
raw.reserve(out.keypoints.size());
|
||||
for (const auto& kp : out.keypoints)
|
||||
raw.push_back(kp.pt);
|
||||
|
||||
if (!dist.empty())
|
||||
undistortPoints(raw, out.undistKpts, K, dist, noArray(), K);
|
||||
else
|
||||
out.undistKpts = raw;
|
||||
}
|
||||
}
|
||||
|
||||
// Frame matching helper
|
||||
|
||||
void VisualOdometryImpl::matchFrames(
|
||||
const std::vector<KeyPoint>& qKp, const Mat& qDesc, Size qSz,
|
||||
const std::vector<KeyPoint>& tKp, const Mat& tDesc, Size tSz,
|
||||
std::vector<DMatch>& matches) const
|
||||
{
|
||||
matches.clear();
|
||||
if (qDesc.empty() || tDesc.empty()) return;
|
||||
if (qKp.empty() || tKp.empty()) return;
|
||||
|
||||
matcher->setImagePairInfo(qKp, tKp, qSz, tSz);
|
||||
matcher->match(qDesc, tDesc, matches);
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
@@ -0,0 +1,252 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "vo_impl.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns the homography candidate index with the most cheirality-consistent triangulations.
|
||||
int bestHomographyCandidate(
|
||||
const std::vector<Mat>& Rs,
|
||||
const std::vector<Mat>& ts,
|
||||
const std::vector<Point2f>& pts1,
|
||||
const std::vector<Point2f>& pts2,
|
||||
const Mat& P1)
|
||||
{
|
||||
int bestIdx = 0;
|
||||
int bestCount = -1;
|
||||
const int nSol = (int)Rs.size();
|
||||
|
||||
for (int s = 0; s < nSol; ++s)
|
||||
{
|
||||
Mat Rt(3, 4, CV_64F);
|
||||
Rs[s].copyTo(Rt(Rect(0, 0, 3, 3)));
|
||||
ts[s].reshape(1, 3).copyTo(Rt(Rect(3, 0, 1, 3)));
|
||||
|
||||
Mat Klocal = P1(Rect(0, 0, 3, 3)).clone();
|
||||
Mat P2_s = Klocal * Rt;
|
||||
|
||||
Mat pts4D;
|
||||
triangulatePoints(P1, P2_s, pts1, pts2, pts4D);
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < pts4D.cols; ++i)
|
||||
{
|
||||
float w = pts4D.at<float>(3, i);
|
||||
if (std::abs(w) < 1e-9f) continue;
|
||||
float X = pts4D.at<float>(0, i) / w;
|
||||
float Y = pts4D.at<float>(1, i) / w;
|
||||
float Z = pts4D.at<float>(2, i) / w;
|
||||
float Z2 = (float)(Rs[s].at<double>(2,0)*X + Rs[s].at<double>(2,1)*Y
|
||||
+ Rs[s].at<double>(2,2)*Z
|
||||
+ ts[s].reshape(1,3).at<double>(2,0));
|
||||
if (Z > 0 && Z2 > 0) ++count;
|
||||
}
|
||||
if (count > bestCount) { bestCount = count; bestIdx = s; }
|
||||
}
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool VisualOdometryImpl::bootstrap(Frame& cur)
|
||||
{
|
||||
std::vector<DMatch> matches;
|
||||
matchFrames(refFrame.keypoints, refFrame.descriptors, refFrame.imageSize,
|
||||
cur.keypoints, cur.descriptors, cur.imageSize, matches);
|
||||
|
||||
if ((int)matches.size() < params.minInitInliers)
|
||||
{
|
||||
// slide ref forward so we don't keep trying against a frame that's too far
|
||||
refFrame = std::move(cur);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Point2f> refU, curU;
|
||||
refU.reserve(matches.size());
|
||||
curU.reserve(matches.size());
|
||||
for (const auto& m : matches)
|
||||
{
|
||||
refU.push_back(refFrame.undistKpts[m.queryIdx]);
|
||||
curU.push_back(cur.undistKpts[m.trainIdx]);
|
||||
}
|
||||
|
||||
Mat maskE, maskH;
|
||||
Mat E = findEssentialMat(refU, curU, K, RANSAC,
|
||||
params.essentialRansacConfidence,
|
||||
params.essentialRansacThresh, 1000, maskE);
|
||||
Mat H = findHomography(refU, curU, RANSAC,
|
||||
params.essentialRansacThresh, maskH);
|
||||
|
||||
const int nE = (!E.empty() && !maskE.empty()) ? countNonZero(maskE) : 0;
|
||||
const int nH = (!H.empty() && !maskH.empty()) ? countNonZero(maskH) : 0;
|
||||
|
||||
if (nE < params.minInitInliers && nH < params.minInitInliers)
|
||||
return false;
|
||||
|
||||
const double RH = (double)nH / ((double)nH + (double)nE + 1e-9);
|
||||
|
||||
Mat R, t, modelMask;
|
||||
Mat P1 = K * Mat::eye(3, 4, CV_64F);
|
||||
|
||||
if (RH > params.hfRatioThresh && !H.empty())
|
||||
{
|
||||
std::vector<Mat> Rs, ts, normals;
|
||||
int nSol = decomposeHomographyMat(H, K, Rs, ts, normals);
|
||||
if (nSol <= 0) return false;
|
||||
|
||||
std::vector<Point2f> p1In, p2In;
|
||||
for (size_t i = 0; i < matches.size(); ++i)
|
||||
if (!maskH.empty() && maskH.at<uchar>((int)i))
|
||||
{ p1In.push_back(refU[i]); p2In.push_back(curU[i]); }
|
||||
if (p1In.empty()) return false;
|
||||
|
||||
int best = bestHomographyCandidate(Rs, ts, p1In, p2In, P1);
|
||||
R = Rs[best].clone();
|
||||
t = ts[best].clone();
|
||||
modelMask = maskH;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (E.empty() || nE < params.minInitInliers) return false;
|
||||
Mat recoverMask = maskE.clone();
|
||||
int nPose = recoverPose(E, refU, curU, K, R, t, recoverMask);
|
||||
if (nPose < params.minInitInliers) return false;
|
||||
modelMask = recoverMask;
|
||||
}
|
||||
|
||||
std::vector<Point2f> refIn, curIn;
|
||||
std::vector<int> matchIn;
|
||||
for (size_t i = 0; i < matches.size(); ++i)
|
||||
if (!modelMask.empty() && modelMask.at<uchar>((int)i))
|
||||
{
|
||||
refIn.push_back(refU[i]);
|
||||
curIn.push_back(curU[i]);
|
||||
matchIn.push_back((int)i);
|
||||
}
|
||||
|
||||
if ((int)refIn.size() < params.minInitInliers)
|
||||
return false;
|
||||
|
||||
Mat Rt(3, 4, CV_64F);
|
||||
R.copyTo(Rt(Rect(0, 0, 3, 3)));
|
||||
t.reshape(1, 3).copyTo(Rt(Rect(3, 0, 1, 3)));
|
||||
Mat P2 = K * Rt;
|
||||
|
||||
Mat pts4D;
|
||||
triangulatePoints(P1, P2, refIn, curIn, pts4D);
|
||||
|
||||
Matx44d T_ref = Matx44d::eye();
|
||||
Matx44d T_cur = detail::makePose(R, t);
|
||||
|
||||
std::vector<Point3d> goodPts;
|
||||
std::vector<int> goodMatch;
|
||||
int nValid = 0;
|
||||
int nPos = 0;
|
||||
|
||||
for (int i = 0; i < pts4D.cols; ++i)
|
||||
{
|
||||
double w = pts4D.at<float>(3, i);
|
||||
if (std::abs(w) < 1e-9) continue;
|
||||
++nValid;
|
||||
|
||||
double X = pts4D.at<float>(0, i) / w;
|
||||
double Y = pts4D.at<float>(1, i) / w;
|
||||
double Z = pts4D.at<float>(2, i) / w;
|
||||
|
||||
if (Z <= 0) continue;
|
||||
double Z2 = R.at<double>(2,0)*X + R.at<double>(2,1)*Y
|
||||
+ R.at<double>(2,2)*Z + t.reshape(1,3).at<double>(2,0);
|
||||
if (Z2 <= 0) continue;
|
||||
++nPos;
|
||||
|
||||
Point3d Xw(X, Y, Z);
|
||||
if (detail::parallaxDeg(Xw, T_ref, T_cur) < params.minInitParallaxDeg)
|
||||
continue;
|
||||
|
||||
goodPts.push_back(Xw);
|
||||
goodMatch.push_back(matchIn[i]);
|
||||
}
|
||||
|
||||
// reject degenerate decompositions (< 90% positive-depth ratio)
|
||||
if (nValid > 0 && (double)nPos / nValid < 0.9)
|
||||
return false;
|
||||
if ((int)goodPts.size() < params.minInitPoints)
|
||||
return false;
|
||||
|
||||
// normalize scale: set median scene depth to 1.0
|
||||
std::vector<double> depths;
|
||||
depths.reserve(goodPts.size());
|
||||
for (const auto& p : goodPts) depths.push_back(p.z);
|
||||
std::nth_element(depths.begin(), depths.begin() + depths.size()/2, depths.end());
|
||||
double med = depths[depths.size()/2];
|
||||
if (med < 1e-9) return false;
|
||||
|
||||
double scale = 1.0 / med;
|
||||
for (auto& p : goodPts) { p.x *= scale; p.y *= scale; p.z *= scale; }
|
||||
|
||||
Mat tSc;
|
||||
t.reshape(1, 3).convertTo(tSc, CV_64F);
|
||||
tSc = tSc * scale;
|
||||
T_cur = detail::makePose(R, tSc);
|
||||
|
||||
auto makeKF = [](const Frame& f) -> KeyFrame* {
|
||||
KeyFrame* kf = new KeyFrame();
|
||||
kf->poseCw = Matx44d::eye();
|
||||
kf->keypoints = f.keypoints;
|
||||
kf->descriptors = f.descriptors.clone();
|
||||
kf->undistKpts = f.undistKpts;
|
||||
kf->imageSize = f.imageSize;
|
||||
kf->mapPoints.assign(f.keypoints.size(), nullptr);
|
||||
return kf;
|
||||
};
|
||||
|
||||
KeyFrame* kfRef = makeKF(refFrame);
|
||||
kfRef->poseCw = T_ref;
|
||||
|
||||
KeyFrame* kfCur = makeKF(cur);
|
||||
kfCur->poseCw = T_cur;
|
||||
kfCur->parent = kfRef;
|
||||
|
||||
map.addKeyframe(kfRef);
|
||||
map.addKeyframe(kfCur);
|
||||
|
||||
for (size_t i = 0; i < goodPts.size(); ++i)
|
||||
{
|
||||
MapPoint* mp = new MapPoint();
|
||||
mp->pos = goodPts[i];
|
||||
const DMatch& m = matches[goodMatch[i]];
|
||||
mp->refDesc = refFrame.descriptors.row(m.queryIdx).clone();
|
||||
|
||||
map.addMapPoint(mp);
|
||||
map.addObservation(kfRef, (size_t)m.queryIdx, mp);
|
||||
map.addObservation(kfCur, (size_t)m.trainIdx, mp);
|
||||
}
|
||||
|
||||
detail::updateCovisibility(kfRef);
|
||||
|
||||
map.setRefKeyframe(kfRef);
|
||||
map.setCurrentKeyframe(kfCur);
|
||||
|
||||
lastKf = kfCur;
|
||||
framesSinceKf = 0;
|
||||
lastKfInliers = (int)goodPts.size();
|
||||
lastPoseCw = T_cur;
|
||||
state = TRACKING;
|
||||
hasVelocity = false;
|
||||
|
||||
map.appendPose(T_ref);
|
||||
map.appendPose(T_cur);
|
||||
|
||||
refFrame = Frame();
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
@@ -0,0 +1,110 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_SLAM_VO_IMPL_HPP
|
||||
#define OPENCV_SLAM_VO_IMPL_HPP
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "frame.hpp"
|
||||
#include "../optimizer/pose_optimizer.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
/** @brief Concrete VisualOdometry implementation (pimpl target).
|
||||
|
||||
Stage logic is split across:
|
||||
- vo_bootstrap.cpp : two-view H/F initialisation
|
||||
- vo_tracking.cpp : per-frame localisation (motion model, fallback 1/2, local map)
|
||||
- vo_keyframe.cpp : keyframe promotion decision + covisibility helpers
|
||||
- vo_map_growth.cpp : triangulation of new map points at promotion time
|
||||
- visual_odometry.cpp : factory, processFrame()
|
||||
*/
|
||||
class VisualOdometryImpl CV_FINAL : public VisualOdometry
|
||||
{
|
||||
public:
|
||||
VisualOdometryImpl(const Ptr<Feature2D>& detector,
|
||||
const Ptr<DescriptorMatcher>& matcher,
|
||||
const Mat& cameraMatrix,
|
||||
const Mat& distCoeffs,
|
||||
const OdometryParams& params);
|
||||
|
||||
// --- VisualOdometry interface -------------------------------------------
|
||||
|
||||
bool processFrame(InputArray image) CV_OVERRIDE;
|
||||
void reset() CV_OVERRIDE;
|
||||
|
||||
OdometryState getState() const CV_OVERRIDE { return state; }
|
||||
Matx44d getLastPose() const CV_OVERRIDE { return lastPoseCw; }
|
||||
const Map& getMap() const CV_OVERRIDE { return map; }
|
||||
int getNumKeyframes() const CV_OVERRIDE { return map.numKeyframes(); }
|
||||
int getNumMapPoints() const CV_OVERRIDE { return map.numMapPoints(); }
|
||||
const std::vector<Matx44d>& getTrajectory() const CV_OVERRIDE { return map.trajectory(); }
|
||||
const OdometryParams& getParams() const CV_OVERRIDE { return params; }
|
||||
void setParams(const OdometryParams& p) CV_OVERRIDE { params = p; }
|
||||
|
||||
// --- Stage entry points -------------------------------------------------
|
||||
|
||||
bool bootstrap(Frame& cur);
|
||||
bool track(Frame& cur);
|
||||
|
||||
bool trackWithMotionModel(Frame& cur); // motion model
|
||||
bool trackWithReferenceKF(Frame& cur); // fallback 1
|
||||
bool trackWithOpticalFlow(Frame& cur); // fallback 2
|
||||
void trackLocalMap(Frame& cur);
|
||||
|
||||
bool shouldPromoteKeyframe(int nInliers, const Matx44d& T_cw, String& reason) const;
|
||||
void promoteKeyframeAndGrowMap(Frame& cur);
|
||||
|
||||
// --- Shared helpers (visual_odometry.cpp) --------------------------------
|
||||
|
||||
void extractFeatures(InputArray image, Frame& out) const;
|
||||
|
||||
void matchFrames(const std::vector<KeyPoint>& qKp, const Mat& qDesc, Size qSz,
|
||||
const std::vector<KeyPoint>& tKp, const Mat& tDesc, Size tSz,
|
||||
std::vector<DMatch>& matches) const;
|
||||
|
||||
// --- Owned state ---------------------------------------------------------
|
||||
|
||||
Ptr<Feature2D> detector;
|
||||
Ptr<DescriptorMatcher> matcher;
|
||||
Mat K; // 3×3 CV_64F
|
||||
Mat dist; // distortion coefficients (may be empty)
|
||||
OdometryParams params;
|
||||
|
||||
OdometryState state = NOT_INITIALIZED;
|
||||
Matx44d lastPoseCw = Matx44d::eye();
|
||||
|
||||
Frame refFrame;
|
||||
KeyFrame* lastKf = nullptr;
|
||||
int framesSinceKf = 0;
|
||||
int lastKfInliers = 0;
|
||||
|
||||
Matx44d velocity = Matx44d::eye();
|
||||
bool hasVelocity = false;
|
||||
|
||||
Frame prevFrame;
|
||||
bool hasPrevFrame = false;
|
||||
|
||||
String lastEvent;
|
||||
|
||||
Map map;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
double rotationAngleDeg(const Matx44d& A_cw, const Matx44d& B_cw);
|
||||
double parallaxDeg(const Point3d& X_world, const Matx44d& A_cw, const Matx44d& B_cw);
|
||||
Matx34d projectionFromPose(const Matx44d& T_cw);
|
||||
Matx44d makePose(const Mat& R, const Mat& t);
|
||||
Point3d cameraCenterWorld(const Matx44d& T_cw);
|
||||
void updateCovisibility(KeyFrame* kf);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
}} // namespace cv::slam
|
||||
|
||||
#endif // OPENCV_SLAM_VO_IMPL_HPP
|
||||
@@ -0,0 +1,145 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "vo_impl.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
namespace detail {
|
||||
|
||||
double rotationAngleDeg(const Matx44d& A, const Matx44d& B)
|
||||
{
|
||||
Matx33d Ra(A(0,0),A(0,1),A(0,2), A(1,0),A(1,1),A(1,2), A(2,0),A(2,1),A(2,2));
|
||||
Matx33d Rb(B(0,0),B(0,1),B(0,2), B(1,0),B(1,1),B(1,2), B(2,0),B(2,1),B(2,2));
|
||||
Matx33d D = Rb * Ra.t();
|
||||
double tr = D(0,0) + D(1,1) + D(2,2);
|
||||
double c = std::max(-1.0, std::min(1.0, (tr - 1.0) * 0.5));
|
||||
return std::acos(c) * 180.0 / CV_PI;
|
||||
}
|
||||
|
||||
Point3d cameraCenterWorld(const Matx44d& T_cw)
|
||||
{
|
||||
Matx33d R(T_cw(0,0),T_cw(0,1),T_cw(0,2),
|
||||
T_cw(1,0),T_cw(1,1),T_cw(1,2),
|
||||
T_cw(2,0),T_cw(2,1),T_cw(2,2));
|
||||
Matx31d t(T_cw(0,3),T_cw(1,3),T_cw(2,3));
|
||||
Matx31d C = -R.t() * t;
|
||||
return Point3d(C(0),C(1),C(2));
|
||||
}
|
||||
|
||||
double parallaxDeg(const Point3d& Xw, const Matx44d& A_cw, const Matx44d& B_cw)
|
||||
{
|
||||
Point3d CA = cameraCenterWorld(A_cw), CB = cameraCenterWorld(B_cw);
|
||||
Point3d vA = Xw - CA, vB = Xw - CB;
|
||||
double nA = std::sqrt(vA.dot(vA)), nB = std::sqrt(vB.dot(vB));
|
||||
if (nA < 1e-9 || nB < 1e-9) return 0.0;
|
||||
double c = std::max(-1.0, std::min(1.0, vA.dot(vB) / (nA * nB)));
|
||||
return std::acos(c) * 180.0 / CV_PI;
|
||||
}
|
||||
|
||||
Matx34d projectionFromPose(const Matx44d& T)
|
||||
{
|
||||
return Matx34d(T(0,0),T(0,1),T(0,2),T(0,3),
|
||||
T(1,0),T(1,1),T(1,2),T(1,3),
|
||||
T(2,0),T(2,1),T(2,2),T(2,3));
|
||||
}
|
||||
|
||||
Matx44d makePose(const Mat& R, const Mat& t)
|
||||
{
|
||||
CV_Assert(R.rows == 3 && R.cols == 3 && t.total() == 3);
|
||||
Matx44d T = Matx44d::eye();
|
||||
Mat Rd, td;
|
||||
R.convertTo(Rd, CV_64F);
|
||||
t.reshape(1,3).convertTo(td, CV_64F);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
for (int j = 0; j < 3; ++j) T(i,j) = Rd.at<double>(i,j);
|
||||
T(i,3) = td.at<double>(i,0);
|
||||
}
|
||||
return T;
|
||||
}
|
||||
|
||||
static void rebuildOrderedCovisibility(KeyFrame* target)
|
||||
{
|
||||
target->orderedCovisibility.clear();
|
||||
for (auto& [other, cnt] : target->covisibility)
|
||||
target->orderedCovisibility.push_back({other, cnt});
|
||||
std::sort(target->orderedCovisibility.begin(),
|
||||
target->orderedCovisibility.end(),
|
||||
[](const auto& a, const auto& b){ return a.second > b.second; });
|
||||
}
|
||||
|
||||
void updateCovisibility(KeyFrame* kf)
|
||||
{
|
||||
if (!kf) return;
|
||||
|
||||
kf->covisibility.clear();
|
||||
|
||||
for (MapPoint* mp : kf->mapPoints)
|
||||
{
|
||||
if (!mp || mp->bad) continue;
|
||||
for (auto& [obsKf, kpIdx] : mp->observations)
|
||||
{
|
||||
if (obsKf == kf) continue;
|
||||
kf->covisibility[obsKf]++;
|
||||
obsKf->covisibility[kf]++;
|
||||
}
|
||||
}
|
||||
|
||||
rebuildOrderedCovisibility(kf);
|
||||
for (auto& [nb, cnt] : kf->covisibility)
|
||||
rebuildOrderedCovisibility(nb);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
bool VisualOdometryImpl::shouldPromoteKeyframe(int nInliers, const Matx44d& T_cw,
|
||||
String& reason) const
|
||||
{
|
||||
if (framesSinceKf <= params.kfMinFrames) return false;
|
||||
|
||||
if (framesSinceKf > params.kfMaxFrames)
|
||||
{
|
||||
reason = format("timeout(%d)", framesSinceKf);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!lastKf) return false;
|
||||
|
||||
// Motion-based triggers: rotation then translation (geometric, frame-count independent)
|
||||
double rot = detail::rotationAngleDeg(lastKf->poseCw, T_cw);
|
||||
if (rot > params.kfRotThreshDeg)
|
||||
{
|
||||
reason = format("rot=%.1fdeg", rot);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Tracking quality drop
|
||||
int minInliers = (lastKfInliers > 0)
|
||||
? std::max(params.kfMinInliers, (int)(params.kfInlierRatio * lastKfInliers))
|
||||
: params.kfMinInliers;
|
||||
if (nInliers < minInliers)
|
||||
{
|
||||
reason = format("inliers=%d<%d", nInliers, minInliers);
|
||||
return true;
|
||||
}
|
||||
|
||||
Point3d cCur = detail::cameraCenterWorld(T_cw);
|
||||
Point3d cLast = detail::cameraCenterWorld(lastKf->poseCw);
|
||||
Point3d d = cCur - cLast;
|
||||
double transDist = std::sqrt(d.dot(d));
|
||||
if (transDist > params.kfTransThresh)
|
||||
{
|
||||
reason = format("trans=%.3f", transDist);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
@@ -0,0 +1,135 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "vo_impl.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
namespace {
|
||||
|
||||
inline Point2d projectThrough(const Mat& P, double X, double Y, double Z)
|
||||
{
|
||||
double u = P.at<double>(0,0)*X + P.at<double>(0,1)*Y + P.at<double>(0,2)*Z + P.at<double>(0,3);
|
||||
double v = P.at<double>(1,0)*X + P.at<double>(1,1)*Y + P.at<double>(1,2)*Z + P.at<double>(1,3);
|
||||
double w = P.at<double>(2,0)*X + P.at<double>(2,1)*Y + P.at<double>(2,2)*Z + P.at<double>(2,3);
|
||||
if (std::abs(w) < 1e-12) return Point2d(0, 0);
|
||||
return Point2d(u / w, v / w);
|
||||
}
|
||||
|
||||
inline double cameraDepth(const Matx44d& T_cw, double X, double Y, double Z)
|
||||
{
|
||||
return T_cw(2,0)*X + T_cw(2,1)*Y + T_cw(2,2)*Z + T_cw(2,3);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void VisualOdometryImpl::promoteKeyframeAndGrowMap(Frame& cur)
|
||||
{
|
||||
KeyFrame* newKf = new KeyFrame();
|
||||
newKf->poseCw = cur.poseCw;
|
||||
newKf->keypoints = cur.keypoints;
|
||||
newKf->descriptors = cur.descriptors.clone();
|
||||
newKf->undistKpts = cur.undistKpts;
|
||||
newKf->imageSize = cur.imageSize;
|
||||
newKf->mapPoints.assign(cur.keypoints.size(), nullptr);
|
||||
newKf->parent = lastKf;
|
||||
|
||||
map.addKeyframe(newKf);
|
||||
|
||||
for (size_t i = 0; i < cur.mapPoints.size(); ++i)
|
||||
{
|
||||
MapPoint* mp = cur.mapPoints[i];
|
||||
if (!mp || mp->bad || cur.outliers[i]) continue;
|
||||
map.addObservation(newKf, i, mp);
|
||||
}
|
||||
|
||||
std::vector<DMatch> kfToCur;
|
||||
matchFrames(lastKf->keypoints, lastKf->descriptors, lastKf->imageSize,
|
||||
cur.keypoints, cur.descriptors, cur.imageSize, kfToCur);
|
||||
|
||||
Mat Rt1(3, 4, CV_64F), Rt2(3, 4, CV_64F);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
Rt1.at<double>(i,j) = lastKf->poseCw(i,j);
|
||||
Rt2.at<double>(i,j) = cur.poseCw(i,j);
|
||||
}
|
||||
Mat P1 = K * Rt1;
|
||||
Mat P2 = K * Rt2;
|
||||
|
||||
std::vector<Point2f> pts1, pts2;
|
||||
std::vector<int> triMatchIdx;
|
||||
for (size_t i = 0; i < kfToCur.size(); ++i)
|
||||
{
|
||||
const DMatch& m = kfToCur[i];
|
||||
if ((size_t)m.queryIdx >= lastKf->mapPoints.size()) continue;
|
||||
if (lastKf->mapPoints[m.queryIdx] != nullptr) continue;
|
||||
if ((size_t)m.trainIdx >= newKf->mapPoints.size()) continue;
|
||||
if (newKf->mapPoints[m.trainIdx] != nullptr) continue;
|
||||
pts1.push_back(lastKf->undistKpts[m.queryIdx]);
|
||||
pts2.push_back(cur.undistKpts[m.trainIdx]);
|
||||
triMatchIdx.push_back((int)i);
|
||||
}
|
||||
|
||||
if (pts1.empty())
|
||||
{
|
||||
detail::updateCovisibility(newKf);
|
||||
map.setCurrentKeyframe(newKf);
|
||||
lastKf = newKf;
|
||||
lastKfInliers = 0;
|
||||
framesSinceKf = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
Mat pts4D;
|
||||
triangulatePoints(P1, P2, pts1, pts2, pts4D);
|
||||
|
||||
int nNew = 0;
|
||||
for (int i = 0; i < pts4D.cols; ++i)
|
||||
{
|
||||
double w = pts4D.at<float>(3, i);
|
||||
if (std::abs(w) < 1e-9) continue;
|
||||
double X = pts4D.at<float>(0, i) / w;
|
||||
double Y = pts4D.at<float>(1, i) / w;
|
||||
double Z = pts4D.at<float>(2, i) / w;
|
||||
|
||||
if (cameraDepth(lastKf->poseCw, X, Y, Z) <= 0) continue;
|
||||
if (cameraDepth(cur.poseCw, X, Y, Z) <= 0) continue;
|
||||
|
||||
Point2d p1p = projectThrough(P1, X, Y, Z);
|
||||
Point2d p2p = projectThrough(P2, X, Y, Z);
|
||||
double e1 = std::hypot(p1p.x - pts1[i].x, p1p.y - pts1[i].y);
|
||||
double e2 = std::hypot(p2p.x - pts2[i].x, p2p.y - pts2[i].y);
|
||||
if (e1 > params.pnpReprojThresh || e2 > params.pnpReprojThresh) continue;
|
||||
|
||||
Point3d Xw(X, Y, Z);
|
||||
if (detail::parallaxDeg(Xw, lastKf->poseCw, cur.poseCw)
|
||||
< params.minGrowthParallaxDeg) continue;
|
||||
|
||||
MapPoint* mp = new MapPoint();
|
||||
mp->pos = Xw;
|
||||
const DMatch& dm = kfToCur[triMatchIdx[i]];
|
||||
mp->refDesc = cur.descriptors.row(dm.trainIdx).clone();
|
||||
|
||||
map.addMapPoint(mp);
|
||||
map.addObservation(lastKf, (size_t)dm.queryIdx, mp);
|
||||
map.addObservation(newKf, (size_t)dm.trainIdx, mp);
|
||||
++nNew;
|
||||
}
|
||||
|
||||
detail::updateCovisibility(newKf);
|
||||
|
||||
map.setCurrentKeyframe(newKf);
|
||||
lastKf = newKf;
|
||||
lastKfInliers = 0;
|
||||
framesSinceKf = 0;
|
||||
|
||||
(void)nNew;
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
@@ -0,0 +1,401 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "vo_impl.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace slam {
|
||||
|
||||
namespace {
|
||||
|
||||
bool projectPoint(const Matx44d& T_cw, const Mat& K,
|
||||
double Xw, double Yw, double Zw,
|
||||
double& u, double& v)
|
||||
{
|
||||
const double Xc = T_cw(0,0)*Xw + T_cw(0,1)*Yw + T_cw(0,2)*Zw + T_cw(0,3);
|
||||
const double Yc = T_cw(1,0)*Xw + T_cw(1,1)*Yw + T_cw(1,2)*Zw + T_cw(1,3);
|
||||
const double Zc = T_cw(2,0)*Xw + T_cw(2,1)*Yw + T_cw(2,2)*Zw + T_cw(2,3);
|
||||
if (Zc <= 0.0) return false;
|
||||
u = K.at<double>(0,0) * Xc / Zc + K.at<double>(0,2);
|
||||
v = K.at<double>(1,1) * Yc / Zc + K.at<double>(1,2);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline double descDist(const Mat& a, const Mat& b)
|
||||
{
|
||||
if (a.empty() || b.empty()) return std::numeric_limits<double>::max();
|
||||
int normType = (a.type() == CV_8U) ? NORM_HAMMING : NORM_L2;
|
||||
return norm(a, b, normType);
|
||||
}
|
||||
|
||||
void buildLocalMapPoints(const KeyFrame* kf, int topK,
|
||||
std::set<MapPoint*>& localMps)
|
||||
{
|
||||
if (!kf) return;
|
||||
for (MapPoint* mp : kf->mapPoints)
|
||||
if (mp && !mp->bad) localMps.insert(mp);
|
||||
|
||||
int k = 0;
|
||||
for (const auto& [nbKf, cnt] : kf->orderedCovisibility)
|
||||
{
|
||||
if (k++ >= topK) break;
|
||||
for (MapPoint* mp : nbKf->mapPoints)
|
||||
if (mp && !mp->bad) localMps.insert(mp);
|
||||
}
|
||||
}
|
||||
|
||||
int runPnP(const std::vector<Point3f>& obj, const std::vector<Point2f>& img,
|
||||
Frame& frame,
|
||||
const Mat& K, double reprojThresh, int maxIters, double confidence,
|
||||
int minInliers)
|
||||
{
|
||||
if ((int)obj.size() < minInliers) return -1;
|
||||
|
||||
Mat rvec, tvec, inlierIdx;
|
||||
bool ok = solvePnPRansac(obj, img, K, Mat(),
|
||||
rvec, tvec, false,
|
||||
maxIters, (float)reprojThresh, confidence,
|
||||
inlierIdx, SOLVEPNP_AP3P);
|
||||
if (!ok || inlierIdx.rows < minInliers) return -1;
|
||||
|
||||
std::vector<Point3f> objIn;
|
||||
std::vector<Point2f> imgIn;
|
||||
objIn.reserve(inlierIdx.rows); imgIn.reserve(inlierIdx.rows);
|
||||
for (int i = 0; i < inlierIdx.rows; ++i)
|
||||
{
|
||||
int idx = inlierIdx.at<int>(i);
|
||||
objIn.push_back(obj[idx]);
|
||||
imgIn.push_back(img[idx]);
|
||||
}
|
||||
solvePnPRefineLM(objIn, imgIn, K, Mat(), rvec, tvec);
|
||||
|
||||
Mat R;
|
||||
Rodrigues(rvec, R);
|
||||
frame.poseCw = detail::makePose(R, tvec);
|
||||
return inlierIdx.rows;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Motion model: constant-velocity pose prediction + projected map point search
|
||||
bool VisualOdometryImpl::trackWithMotionModel(Frame& cur)
|
||||
{
|
||||
if (!lastKf) return false;
|
||||
|
||||
cur.poseCw = velocity * lastPoseCw;
|
||||
|
||||
std::set<MapPoint*> localMps;
|
||||
buildLocalMapPoints(lastKf, params.localMapTopK, localMps);
|
||||
|
||||
std::fill(cur.mapPoints.begin(), cur.mapPoints.end(), nullptr);
|
||||
std::fill(cur.outliers.begin(), cur.outliers.end(), false);
|
||||
|
||||
auto doSearch = [&](float radius) -> int {
|
||||
int n = 0;
|
||||
for (MapPoint* mp : localMps)
|
||||
{
|
||||
double u, v;
|
||||
if (!projectPoint(cur.poseCw, K, mp->pos.x, mp->pos.y, mp->pos.z, u, v))
|
||||
continue;
|
||||
if (u < 0 || u >= cur.imageSize.width ||
|
||||
v < 0 || v >= cur.imageSize.height) continue;
|
||||
|
||||
mp->visibleCount++;
|
||||
|
||||
auto cands = cur.getKeypointsInRadius((float)u, (float)v, radius);
|
||||
double bestD = std::numeric_limits<double>::max();
|
||||
size_t bestI = std::numeric_limits<size_t>::max();
|
||||
for (size_t idx : cands)
|
||||
{
|
||||
if (cur.mapPoints[idx]) continue;
|
||||
double d = descDist(mp->refDesc, cur.descriptors.row((int)idx));
|
||||
if (d < bestD) { bestD = d; bestI = idx; }
|
||||
}
|
||||
if (bestI != std::numeric_limits<size_t>::max() &&
|
||||
bestD < params.descProjThresh)
|
||||
{
|
||||
cur.mapPoints[bestI] = mp;
|
||||
mp->foundCount++;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
int n = doSearch((float)params.motionModelRadius);
|
||||
if (n < params.motionModelMinMatches)
|
||||
{
|
||||
std::fill(cur.mapPoints.begin(), cur.mapPoints.end(), nullptr);
|
||||
n = doSearch((float)params.motionModelRadiusWide);
|
||||
}
|
||||
|
||||
if (n < params.pnpMinInliers) return false;
|
||||
|
||||
std::vector<Point3f> obj; std::vector<Point2f> img;
|
||||
obj.reserve(n); img.reserve(n);
|
||||
for (size_t i = 0; i < cur.mapPoints.size(); ++i)
|
||||
{
|
||||
if (!cur.mapPoints[i]) continue;
|
||||
const MapPoint* mp = cur.mapPoints[i];
|
||||
obj.push_back(Point3f((float)mp->pos.x,(float)mp->pos.y,(float)mp->pos.z));
|
||||
img.push_back(cur.undistKpts[i]);
|
||||
}
|
||||
|
||||
int nInliers = runPnP(obj, img, cur, K,
|
||||
params.pnpReprojThresh,
|
||||
params.pnpRansacIters,
|
||||
params.pnpConfidence,
|
||||
params.pnpMinInliers);
|
||||
if (nInliers < 0) return false;
|
||||
|
||||
int nOpt = poseInlierCheck(cur, K, params.pnpReprojThresh);
|
||||
return nOpt >= params.pnpMinInliers;
|
||||
}
|
||||
|
||||
// Fallback 1: descriptor match against the reference keyframe
|
||||
bool VisualOdometryImpl::trackWithReferenceKF(Frame& cur)
|
||||
{
|
||||
if (!lastKf) return false;
|
||||
|
||||
std::fill(cur.mapPoints.begin(), cur.mapPoints.end(), nullptr);
|
||||
std::fill(cur.outliers.begin(), cur.outliers.end(), false);
|
||||
|
||||
std::vector<DMatch> matches;
|
||||
matchFrames(lastKf->keypoints, lastKf->descriptors, lastKf->imageSize,
|
||||
cur.keypoints, cur.descriptors, cur.imageSize, matches);
|
||||
|
||||
std::vector<Point3f> obj;
|
||||
std::vector<Point2f> img;
|
||||
std::vector<MapPoint*> corrMps;
|
||||
std::vector<int> corrKp;
|
||||
obj.reserve(matches.size()); img.reserve(matches.size());
|
||||
corrMps.reserve(matches.size()); corrKp.reserve(matches.size());
|
||||
|
||||
for (const auto& m : matches)
|
||||
{
|
||||
if ((size_t)m.queryIdx >= lastKf->mapPoints.size()) continue;
|
||||
MapPoint* mp = lastKf->mapPoints[m.queryIdx];
|
||||
if (!mp || mp->bad) continue;
|
||||
obj.push_back(Point3f((float)mp->pos.x,(float)mp->pos.y,(float)mp->pos.z));
|
||||
img.push_back(cur.undistKpts[m.trainIdx]);
|
||||
corrMps.push_back(mp);
|
||||
corrKp.push_back(m.trainIdx);
|
||||
}
|
||||
|
||||
if ((int)obj.size() < params.pnpMinInliers)
|
||||
{
|
||||
lastEvent = format("refKF: 2d3d=%d < %d", (int)obj.size(), params.pnpMinInliers);
|
||||
return false;
|
||||
}
|
||||
|
||||
int nInliers = runPnP(obj, img, cur, K,
|
||||
params.pnpReprojThresh, params.pnpRansacIters,
|
||||
params.pnpConfidence, params.pnpMinInliers);
|
||||
if (nInliers < 0)
|
||||
{
|
||||
lastEvent = "refKF: PnP failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < corrMps.size(); ++k)
|
||||
{
|
||||
int kpIdx = corrKp[k];
|
||||
if ((size_t)kpIdx < cur.mapPoints.size() && !cur.mapPoints[kpIdx])
|
||||
cur.mapPoints[kpIdx] = corrMps[k];
|
||||
}
|
||||
|
||||
int nOpt = poseInlierCheck(cur, K, params.pnpReprojThresh);
|
||||
return nOpt >= params.pnpMinInliers;
|
||||
}
|
||||
|
||||
// Fallback 2: optical flow when descriptor match also fails
|
||||
bool VisualOdometryImpl::trackWithOpticalFlow(Frame& cur)
|
||||
{
|
||||
if (!hasPrevFrame || prevFrame.image.empty()) return false;
|
||||
|
||||
const Frame& prev = prevFrame;
|
||||
if (prev.keypoints.empty()) return false;
|
||||
|
||||
// LK runs on the raw imgs
|
||||
std::vector<Point2f> prevPts;
|
||||
prevPts.reserve(prev.keypoints.size());
|
||||
for (const auto& kp : prev.keypoints) prevPts.push_back(kp.pt);
|
||||
|
||||
std::vector<Point2f> curPts;
|
||||
std::vector<uchar> status;
|
||||
std::vector<float> err;
|
||||
|
||||
calcOpticalFlowPyrLK(prev.image, cur.image, prevPts, curPts,
|
||||
status, err, Size(21, 21), 3,
|
||||
TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 30, 0.01));
|
||||
|
||||
// PnP uses K with no distortion
|
||||
std::vector<Point2f> curUndist;
|
||||
if (!dist.empty())
|
||||
undistortPoints(curPts, curUndist, K, dist, noArray(), K);
|
||||
else
|
||||
curUndist = curPts;
|
||||
|
||||
std::vector<Point3f> obj; std::vector<Point2f> img;
|
||||
obj.reserve(prevPts.size()); img.reserve(prevPts.size());
|
||||
|
||||
for (size_t i = 0; i < prevPts.size(); ++i)
|
||||
{
|
||||
if (!status[i]) continue;
|
||||
if (i >= prev.mapPoints.size()) continue;
|
||||
MapPoint* mp = prev.mapPoints[i];
|
||||
if (!mp || mp->bad) continue;
|
||||
obj.push_back(Point3f((float)mp->pos.x,(float)mp->pos.y,(float)mp->pos.z));
|
||||
img.push_back(curUndist[i]);
|
||||
}
|
||||
|
||||
if ((int)obj.size() < params.opticalFlowMinInliers)
|
||||
{
|
||||
lastEvent = format("optflow: corr=%d < %d", (int)obj.size(), params.opticalFlowMinInliers);
|
||||
return false;
|
||||
}
|
||||
|
||||
int nInliers = runPnP(obj, img, cur, K,
|
||||
params.pnpReprojThresh, params.pnpRansacIters,
|
||||
params.pnpConfidence, params.opticalFlowMinInliers);
|
||||
if (nInliers < 0)
|
||||
{
|
||||
lastEvent = "optflow: PnP failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Local map refinement: expand coverage + recheck inliers
|
||||
void VisualOdometryImpl::trackLocalMap(Frame& cur)
|
||||
{
|
||||
if (!lastKf) return;
|
||||
|
||||
std::set<MapPoint*> localMps;
|
||||
buildLocalMapPoints(lastKf, params.localMapTopK, localMps);
|
||||
|
||||
int nbK = 0;
|
||||
for (const auto& [nbKf, cnt] : lastKf->orderedCovisibility)
|
||||
{
|
||||
if (nbK++ >= params.localMapTopK) break;
|
||||
int nb2K = 0;
|
||||
for (const auto& [nb2Kf, cnt2] : nbKf->orderedCovisibility)
|
||||
{
|
||||
if (nb2K++ >= params.localMapNeighborK) break;
|
||||
for (MapPoint* mp : nb2Kf->mapPoints)
|
||||
if (mp && !mp->bad) localMps.insert(mp);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<MapPoint*> alreadyMatched;
|
||||
for (MapPoint* mp : cur.mapPoints)
|
||||
if (mp) alreadyMatched.insert(mp);
|
||||
|
||||
bool anyNew = false;
|
||||
const float r = (float)params.localMapRadius;
|
||||
|
||||
for (MapPoint* mp : localMps)
|
||||
{
|
||||
if (alreadyMatched.count(mp)) continue;
|
||||
|
||||
double u, v;
|
||||
if (!projectPoint(cur.poseCw, K, mp->pos.x, mp->pos.y, mp->pos.z, u, v))
|
||||
continue;
|
||||
if (u < 0 || u >= cur.imageSize.width ||
|
||||
v < 0 || v >= cur.imageSize.height) continue;
|
||||
|
||||
mp->visibleCount++;
|
||||
|
||||
auto cands = cur.getKeypointsInRadius((float)u, (float)v, r);
|
||||
double bestD = std::numeric_limits<double>::max();
|
||||
size_t bestI = std::numeric_limits<size_t>::max();
|
||||
for (size_t idx : cands)
|
||||
{
|
||||
if (cur.mapPoints[idx]) continue;
|
||||
double d = descDist(mp->refDesc, cur.descriptors.row((int)idx));
|
||||
if (d < bestD) { bestD = d; bestI = idx; }
|
||||
}
|
||||
if (bestI != std::numeric_limits<size_t>::max() &&
|
||||
bestD < params.descProjThresh)
|
||||
{
|
||||
cur.mapPoints[bestI] = mp;
|
||||
cur.outliers[bestI] = false;
|
||||
mp->foundCount++;
|
||||
alreadyMatched.insert(mp);
|
||||
anyNew = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyNew)
|
||||
poseInlierCheck(cur, K, params.pnpReprojThresh);
|
||||
}
|
||||
|
||||
bool VisualOdometryImpl::track(Frame& cur)
|
||||
{
|
||||
if (!lastKf)
|
||||
{
|
||||
refFrame = cur;
|
||||
state = INITIALIZING;
|
||||
return false;
|
||||
}
|
||||
|
||||
// motion model tracking
|
||||
bool ok = false;
|
||||
if (hasVelocity)
|
||||
ok = trackWithMotionModel(cur);
|
||||
|
||||
// fallback 1: descriptor match against reference keyframe
|
||||
if (!ok)
|
||||
ok = trackWithReferenceKF(cur);
|
||||
|
||||
// fallback 2: optical flow
|
||||
if (!ok)
|
||||
ok = trackWithOpticalFlow(cur);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
lastEvent = "track lost: all stages failed";
|
||||
refFrame = cur;
|
||||
state = INITIALIZING;
|
||||
return false;
|
||||
}
|
||||
|
||||
trackLocalMap(cur);
|
||||
|
||||
int nInliers = 0;
|
||||
for (size_t i = 0; i < cur.mapPoints.size(); ++i)
|
||||
if (cur.mapPoints[i] && !cur.outliers[i]) ++nInliers;
|
||||
|
||||
{
|
||||
Matx44d Tcw_last_inv = lastPoseCw.inv();
|
||||
velocity = cur.poseCw * Tcw_last_inv;
|
||||
hasVelocity = true;
|
||||
}
|
||||
|
||||
lastPoseCw = cur.poseCw;
|
||||
map.appendPose(cur.poseCw);
|
||||
++framesSinceKf;
|
||||
|
||||
prevFrame = cur;
|
||||
hasPrevFrame = true;
|
||||
|
||||
String kf_reason;
|
||||
if (shouldPromoteKeyframe(nInliers, cur.poseCw, kf_reason))
|
||||
{
|
||||
int mpBefore = map.numMapPoints();
|
||||
promoteKeyframeAndGrowMap(cur);
|
||||
lastPoseCw = lastKf->poseCw;
|
||||
lastEvent = format("keyframe: %s, +%d mp",
|
||||
kf_reason.c_str(),
|
||||
map.numMapPoints() - mpBefore);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
@@ -0,0 +1,49 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "pose_optimizer.hpp"
|
||||
|
||||
namespace cv { namespace slam {
|
||||
|
||||
// pose is not optimized yet, only inlier classification is done.
|
||||
// adding bundle adjustment based pose optimization in a later commit.
|
||||
|
||||
int poseInlierCheck(Frame& frame, const Mat& K, double reprojThresh)
|
||||
{
|
||||
const double fx = K.at<double>(0, 0);
|
||||
const double fy = K.at<double>(1, 1);
|
||||
const double cx = K.at<double>(0, 2);
|
||||
const double cy = K.at<double>(1, 2);
|
||||
const Matx44d& T = frame.poseCw;
|
||||
|
||||
int nInliers = 0;
|
||||
for (size_t i = 0; i < frame.mapPoints.size(); ++i)
|
||||
{
|
||||
frame.outliers[i] = true;
|
||||
MapPoint* mp = frame.mapPoints[i];
|
||||
if (!mp || mp->bad) continue;
|
||||
|
||||
const double Xc = T(0,0)*mp->pos.x + T(0,1)*mp->pos.y + T(0,2)*mp->pos.z + T(0,3);
|
||||
const double Yc = T(1,0)*mp->pos.x + T(1,1)*mp->pos.y + T(1,2)*mp->pos.z + T(1,3);
|
||||
const double Zc = T(2,0)*mp->pos.x + T(2,1)*mp->pos.y + T(2,2)*mp->pos.z + T(2,3);
|
||||
if (Zc <= 0.0) continue;
|
||||
|
||||
const double u = fx * Xc / Zc + cx;
|
||||
const double v = fy * Yc / Zc + cy;
|
||||
const double dx = u - static_cast<double>(frame.undistKpts[i].x);
|
||||
const double dy = v - static_cast<double>(frame.undistKpts[i].y);
|
||||
|
||||
if (std::sqrt(dx * dx + dy * dy) <= reprojThresh)
|
||||
{
|
||||
frame.outliers[i] = false;
|
||||
++nInliers;
|
||||
}
|
||||
}
|
||||
return nInliers;
|
||||
}
|
||||
|
||||
}} // namespace cv::slam
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user