1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

Merge pull request #28990 from rmsalinas:fix-flann-radiussearch-output-size

flann: fix Index::radiusSearch output size mismatch #28990

## Problem

`Index::radiusSearch` returns `nn` — the number of neighbors found within the radius — but the output arrays `_indices` and `_dists` are always allocated with `maxResults` columns regardless of how many neighbors were actually found. This creates two inconsistencies:

1. When `nn < maxResults`, the output arrays contain stale entries beyond position `nn`, so `nn != indices.cols` and callers cannot rely on the output size to know how many results are valid.
2. When `nn > maxResults`, the return value exceeds the capacity of the output arrays, which have only `maxResults` columns. The extra neighbors were never stored, so the return value is misleading.

The following snippet reproduces both cases:

```cpp
int testFLANN(){
    std::vector<cv::Point2f> corners={{3679.83,1857.22},{3324.43,1850.67},{3278.83,1502.32},{3621.32,1508.69},{3662.26,1837.97},{3336.06,1839.28},{3291.74,1516.03},{3608.94,1518.72},{2980.66,1843.22},{2628.11,1837.14},{2607.73,1491.57},{2948.22,1496.76},{2289.31,1830.19},{1943.99,1824.17},{1945.51,1482.34},{2280.68,1487.39},{1607.47,1819.11},{1260.04,1813.26},{1283.94,1470.34},{1620.03,1475.85},{923.063,1808.08},{576.193,1802.4},{624.713,1459.78},{959.715,1464.93},{3270.25,1494.98},{2952.95,1492.19},{2922.75,1190.02},{3231.05,1194.81},{3284.82,1507.62},{2942.74,1502.59},{2912.32,1178.33},{3242.98,1183.19},{2612.82,1496.37},{2276.44,1491.63},{2267.61,1170.13},{2593.64,1174.43},{1949.73,1486.61},{1614.92,1480.64},{1626.77,1160.34},{1952,1165.51},{1289.38,1476.21},{953.709,1470.21},{986.779,1148.92},{1311.98,1154.89},{3567.28,1195.1},{3237.51,1189.03},{3197.63,884.15},{3518.61,888.76},{2918.31,1183.62},{2588.69,1179.37},{2570.64,875.672},{2889.87,878.902},{2271.97,1174.25},{1947.62,1169.61},{1948.56,868.197},{2263.8,872.533},{1630.99,1164.6},{1306.74,1159.52},{1327.81,858.358},{1642.69,862.765},{992.896,1155.52},{666.107,1149.96},{705.246,845.776},{1023.25,851.582},{3204.48,889.981},{2883.49,885.252},{2859.31,593.752},{3175.43,594.736},{2575.86,880.331},{2259.44,876.647},{2252.33,589.235},{2560.47,592.332},{1954.36,873.708},{1636.7,868.068},{1646.93,580.76},{1956.14,584.797},{1332.02,862.624},{1017.11,856.71},{1045.32,572.178},{1350.95,577.635},{3481.36,605.324},{3169.05,601.083},{3138.84,324.116},{3446.12,325.545},{2866.14,599.61},{2555.38,597.142},{2541.51,320.931},{2845.5,322.419},{2256.79,593.253},{1950.17,590.12},{1951.8,317.445},{2250.88,319.418},{1654.16,587.663},{1344.83,582.787},{1362.8,309.253},{1663.89,313.056},{1050.94,577.867},{741.476,571.648},{776.433,300.444},{1076.97,304.56},{3327.05,1847.65},{2977.74,1840.48},{2945.48,1499.68},{3281.82,1504.97},{2630.85,1834.22},{2287.2,1828.06},{2278.56,1489.51},{2610.64,1494.31},{1946.11,1822.05},{1604.75,1816.18},{1617.11,1478.59},{1947.62,1484.48},{1262.96,1810.53},{920.46,1805.05},{956.712,1467.57},{1286.66,1473.27},{3618.7,1511.71},{3281.82,1504.97},{3240.24,1186.11},{3564.22,1192.53},{2945.48,1499.68},{2610.64,1494.31},{2591.52,1176.55},{2915.32,1180.97},{2278.56,1489.51},{1947.62,1484.48},{1949.81,1167.56},{2269.79,1172.18},{1617.11,1478.59},{1286.66,1473.27},{1308.98,1157.53},{1628.88,1162.47},{956.712,1467.57},{627.315,1462.81},{669.943,1146.75},{989.494,1151.86},{3240.25,1186.11},{2915.32,1180.98},{2887.03,881.727},{3200.69,886.725},{2591.52,1176.55},{2269.79,1172.19},{2261.62,874.587},{2573.63,878.324},{1949.81,1167.55},{1628.88,1162.47},{1640.44,864.751},{1950.74,870.259},{1308.98,1157.53},{989.494,1151.86},{1019.41,854.787},{1329.92,860.49},{3515.88,891.68},{3200.69,886.725},{3171.89,598.263},{3478.26,602.785},{2887.04,881.725},{2573.63,878.324},{2558.28,594.392},{2863.1,597.005},{2261.62,874.587},{1950.74,870.259},{1952.4,588.114},{2254.56,591.241},{1640.44,864.751},{1329.92,860.49},{1348.65,579.559},{1650.55,584.209},{1019.41,854.787},{708.649,849.44},{745.388,568.534},{1047.42,574.313},{3171.89,598.263},{2863.1,597.005},{2842.6,325.169},{3141.93,326.654},{2558.28,594.392},{2254.56,591.241},{2248.65,321.425},{2544.55,323.537},{1952.4,588.113},{1650.55,584.209},{1660.07,316.284},{1954.02,319.457},{1348.65,579.559},{1047.43,574.312},{1073.06,307.674},{1366.42,312.707}};

    cv::flann::KDTreeIndexParams indexParams(1);
    cv::Mat data = cv::Mat(corners).reshape(1, static_cast<int>(corners.size()));
    auto index = std::make_shared<cv::flann::Index>(data, indexParams);

    int maxResults = 4;
    int nTimesError1 = 0;  // nn != indices.size()
    int nTimesError2 = 0;  // nn > maxResults
    for (size_t i = 0; i < corners.size(); i++) {
        std::vector<int> indices(4);
        std::vector<float> dists(4);
        int nn = index->radiusSearch(data.row(static_cast<int>(i)), indices, dists, 100, maxResults);
        if (nn != (int)indices.size()) { nTimesError1++; }
        if (nn > maxResults)           { nTimesError2++; }
    }
    std::cout << "nTimesError1: " << nTimesError1 << std::endl;  // > 0 before fix
    std::cout << "nTimesError2: " << nTimesError2 << std::endl;  // > 0 before fix

    return 1;
}
```

## Fix

- Capture the search result in `rsearch_ret` instead of returning early from each `switch` case (this also required adding the missing `break` statements that would otherwise have caused fall-through between type-incompatible distance specialisations).
- Cap `rsearch_ret` at `maxResults` so the return value never exceeds the capacity of the output arrays.
- When `rsearch_ret < maxResults`, trim `_indices` and `_dists` to `query.rows × rsearch_ret` via `colRange(0, rsearch_ret).copyTo(...)` so the output dimensions always match the returned count.
This commit is contained in:
Rafael Muñoz Salinas
2026-05-12 09:41:01 +02:00
committed by GitHub
parent 9929b5ceb9
commit b7ae67e8ec
2 changed files with 110 additions and 11 deletions
+29 -11
View File
@@ -663,33 +663,51 @@ int Index::radiusSearch(InputArray _query, OutputArray _indices,
if( algo == FLANN_INDEX_LSH )
CV_Error( Error::StsNotImplemented, "LSH index does not support radiusSearch operation" );
int rsearch_ret;
switch( distType )
{
case FLANN_DIST_HAMMING:
return runRadiusSearch< HammingDistance >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< HammingDistance >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_L2:
return runRadiusSearch< ::cvflann::L2<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::L2<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_L1:
return runRadiusSearch< ::cvflann::L1<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::L1<float> >(index, query, indices, dists, radius, params);
break;
#if MINIFLANN_SUPPORT_EXOTIC_DISTANCE_TYPES
case FLANN_DIST_DNAMMING:
return runRadiusSearch< DNAmmingDistance >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< DNAmmingDistance >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_MAX:
return runRadiusSearch< ::cvflann::MaxDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::MaxDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_HIST_INTERSECT:
return runRadiusSearch< ::cvflann::HistIntersectionDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::HistIntersectionDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_HELLINGER:
return runRadiusSearch< ::cvflann::HellingerDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::HellingerDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_CHI_SQUARE:
return runRadiusSearch< ::cvflann::ChiSquareDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::ChiSquareDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_KL:
return runRadiusSearch< ::cvflann::KL_Divergence<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::KL_Divergence<float> >(index, query, indices, dists, radius, params);
break;
#endif
default:
CV_Error(Error::StsBadArg, "Unknown/unsupported distance type");
return -1;
}
return -1;
if (rsearch_ret > maxResults)
rsearch_ret = maxResults;
if (rsearch_ret < maxResults) {
if (_indices.needed())
indices.colRange(0, rsearch_ret).copyTo(_indices);
if (_dists.needed())
dists.colRange(0, rsearch_ret).copyTo(_dists);
}
return rsearch_ret;
}
flann_distance_t Index::getDistance() const
+81
View File
@@ -0,0 +1,81 @@
// 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 {
// Regression test: radiusSearch returned nn < maxResults but left the output
// vectors sized to maxResults, so nn != indices.size() and nn > maxResults
// could both occur for queries with fewer actual neighbors than maxResults.
TEST(Flann_Index, radiusSearch_output_size_matches_returned_count)
{
std::vector<cv::Point2f> corners = {
{3679.83f,1857.22f},{3324.43f,1850.67f},{3278.83f,1502.32f},{3621.32f,1508.69f},
{3662.26f,1837.97f},{3336.06f,1839.28f},{3291.74f,1516.03f},{3608.94f,1518.72f},
{2980.66f,1843.22f},{2628.11f,1837.14f},{2607.73f,1491.57f},{2948.22f,1496.76f},
{2289.31f,1830.19f},{1943.99f,1824.17f},{1945.51f,1482.34f},{2280.68f,1487.39f},
{1607.47f,1819.11f},{1260.04f,1813.26f},{1283.94f,1470.34f},{1620.03f,1475.85f},
{923.063f,1808.08f},{576.193f,1802.4f},{624.713f,1459.78f},{959.715f,1464.93f},
{3270.25f,1494.98f},{2952.95f,1492.19f},{2922.75f,1190.02f},{3231.05f,1194.81f},
{3284.82f,1507.62f},{2942.74f,1502.59f},{2912.32f,1178.33f},{3242.98f,1183.19f},
{2612.82f,1496.37f},{2276.44f,1491.63f},{2267.61f,1170.13f},{2593.64f,1174.43f},
{1949.73f,1486.61f},{1614.92f,1480.64f},{1626.77f,1160.34f},{1952.0f,1165.51f},
{1289.38f,1476.21f},{953.709f,1470.21f},{986.779f,1148.92f},{1311.98f,1154.89f},
{3567.28f,1195.1f},{3237.51f,1189.03f},{3197.63f,884.15f},{3518.61f,888.76f},
{2918.31f,1183.62f},{2588.69f,1179.37f},{2570.64f,875.672f},{2889.87f,878.902f},
{2271.97f,1174.25f},{1947.62f,1169.61f},{1948.56f,868.197f},{2263.8f,872.533f},
{1630.99f,1164.6f},{1306.74f,1159.52f},{1327.81f,858.358f},{1642.69f,862.765f},
{992.896f,1155.52f},{666.107f,1149.96f},{705.246f,845.776f},{1023.25f,851.582f},
{3204.48f,889.981f},{2883.49f,885.252f},{2859.31f,593.752f},{3175.43f,594.736f},
{2575.86f,880.331f},{2259.44f,876.647f},{2252.33f,589.235f},{2560.47f,592.332f},
{1954.36f,873.708f},{1636.7f,868.068f},{1646.93f,580.76f},{1956.14f,584.797f},
{1332.02f,862.624f},{1017.11f,856.71f},{1045.32f,572.178f},{1350.95f,577.635f},
{3481.36f,605.324f},{3169.05f,601.083f},{3138.84f,324.116f},{3446.12f,325.545f},
{2866.14f,599.61f},{2555.38f,597.142f},{2541.51f,320.931f},{2845.5f,322.419f},
{2256.79f,593.253f},{1950.17f,590.12f},{1951.8f,317.445f},{2250.88f,319.418f},
{1654.16f,587.663f},{1344.83f,582.787f},{1362.8f,309.253f},{1663.89f,313.056f},
{1050.94f,577.867f},{741.476f,571.648f},{776.433f,300.444f},{1076.97f,304.56f},
{3327.05f,1847.65f},{2977.74f,1840.48f},{2945.48f,1499.68f},{3281.82f,1504.97f},
{2630.85f,1834.22f},{2287.2f,1828.06f},{2278.56f,1489.51f},{2610.64f,1494.31f},
{1946.11f,1822.05f},{1604.75f,1816.18f},{1617.11f,1478.59f},{1947.62f,1484.48f},
{1262.96f,1810.53f},{920.46f,1805.05f},{956.712f,1467.57f},{1286.66f,1473.27f},
{3618.7f,1511.71f},{3281.82f,1504.97f},{3240.24f,1186.11f},{3564.22f,1192.53f},
{2945.48f,1499.68f},{2610.64f,1494.31f},{2591.52f,1176.55f},{2915.32f,1180.97f},
{2278.56f,1489.51f},{1947.62f,1484.48f},{1949.81f,1167.56f},{2269.79f,1172.18f},
{1617.11f,1478.59f},{1286.66f,1473.27f},{1308.98f,1157.53f},{1628.88f,1162.47f},
{956.712f,1467.57f},{627.315f,1462.81f},{669.943f,1146.75f},{989.494f,1151.86f},
{3240.25f,1186.11f},{2915.32f,1180.98f},{2887.03f,881.727f},{3200.69f,886.725f},
{2591.52f,1176.55f},{2269.79f,1172.19f},{2261.62f,874.587f},{2573.63f,878.324f},
{1949.81f,1167.55f},{1628.88f,1162.47f},{1640.44f,864.751f},{1950.74f,870.259f},
{1308.98f,1157.53f},{989.494f,1151.86f},{1019.41f,854.787f},{1329.92f,860.49f},
{3515.88f,891.68f},{3200.69f,886.725f},{3171.89f,598.263f},{3478.26f,602.785f},
{2887.04f,881.725f},{2573.63f,878.324f},{2558.28f,594.392f},{2863.1f,597.005f},
{2261.62f,874.587f},{1950.74f,870.259f},{1952.4f,588.114f},{2254.56f,591.241f},
{1640.44f,864.751f},{1329.92f,860.49f},{1348.65f,579.559f},{1650.55f,584.209f},
{1019.41f,854.787f},{708.649f,849.44f},{745.388f,568.534f},{1047.42f,574.313f},
{3171.89f,598.263f},{2863.1f,597.005f},{2842.6f,325.169f},{3141.93f,326.654f},
{2558.28f,594.392f},{2254.56f,591.241f},{2248.65f,321.425f},{2544.55f,323.537f},
{1952.4f,588.113f},{1650.55f,584.209f},{1660.07f,316.284f},{1954.02f,319.457f},
{1348.65f,579.559f},{1047.43f,574.312f},{1073.06f,307.674f},{1366.42f,312.707f}
};
cv::flann::KDTreeIndexParams indexParams(1);
cv::Mat data = cv::Mat(corners).reshape(1, static_cast<int>(corners.size()));
cv::flann::Index index(data, indexParams);
const int maxResults = 4;
for (int i = 0; i < (int)corners.size(); i++)
{
SCOPED_TRACE(cv::format("Data row: %d", i));
std::vector<int> indices(maxResults);
std::vector<float> dists(maxResults);
int nn = index.radiusSearch(data.row(i), indices, dists, 100, maxResults);
EXPECT_EQ(nn, (int)indices.size());
EXPECT_LE(nn, maxResults);
}
}
}} // namespace