Skip to content

imgproc: fix calcBackProject() ignoring the 3rd and further channels - #29941

Open
ShresthSamyak wants to merge 1 commit into
opencv:4.xfrom
ShresthSamyak:fix-29938-backproject-3d
Open

imgproc: fix calcBackProject() ignoring the 3rd and further channels#29941
ShresthSamyak wants to merge 1 commit into
opencv:4.xfrom
ShresthSamyak:fix-29938-backproject-3d

Conversation

@ShresthSamyak

Copy link
Copy Markdown

Fixes #29938

Problem

cv2.calcBackProject() returns the same value for pixels that differ only in
the 3rd channel, so a 3D (BGR) back-projection is wrong. From the issue:

roi  = np.array([[[10, 20, 30]]], dtype=np.uint8)
hist = cv2.calcHist([roi], [0,1,2], None, [8,8,8], [0,256,0,256,0,256])

img = np.array([[[10, 20, 30], [10, 20, 130]]], dtype=np.uint8)
bp  = cv2.calcBackProject([img], [0,1,2], hist, [0,256,0,256,0,256], 255.0)
# bp[0,0] = 255 (correct), bp[0,1] = 255 -- should be 0

Cause

Not in the back-projection kernel: calcBackProj_8u() handles dims == 3
correctly (tab[*p0] + tab[*p1 + 256] + tab[*p2 + 512]). That branch is simply
never reached.

A (8, 8, 8) ndarray is converted to an 8x8 CV_32FC8 Mat, not a
3-dimensional one (cv2_convert.cpp, ismultichannel = ndims == 3). The
std::vector<> overload of calcBackProject() already compensates: when
hcn > 1 it rebuilds the histogram as H, carrying the extra dimension. It
then validates against H -- which is why dims == 3 and the rsz == dims*2
assert pass -- but forwards the original hist to the implementation, which
re-reads dims from it, sees 2, and takes the 2D path. The third channel is
never read.

This also explains why the call does not simply throw: the checks run against
the corrected histogram while the computation runs against the uncorrected one.

Fix

Forward H rather than hist. H exists for exactly this purpose, and when
hcn == 1 it aliases hist, so single-channel histograms are bit-identical to
before.

Verification

Built imgproc + Python bindings, GCC 16.1 (MSYS2 UCRT64), Release, Windows.

Reproducer from the issue, before -> after:

case before after expected
bp[0,1], 3rd channel differs 255 0 0
3rd channel swept over all 8 bins 255 (all) 255 for bin 0, 0 elsewhere same
same test, CV_32F histogram 255 0 0
1D and 2D histograms unchanged unchanged unchanged

opencv_test_imgproc, same binary before and after the one-line change:

unpatched  tests=2539  passed=2259  failed=257
patched    tests=2539  passed=2260  failed=256

Comparing the two failure sets, the only difference is the new test moving from
failed to passed; nothing else changed. (The 256 remaining failures are
pre-existing here for lack of a local opencv_extra checkout, and are identical
in both runs.)

The new C++ test was confirmed to fail without the fix and pass with it.

Tests added

  • Imgproc_Hist_Calc.calcBackProject_3d_regression_29938 in
    modules/imgproc/test/test_histograms.cpp
  • test_calcBackProject_3d in
    modules/imgproc/misc/python/test/test_imgproc.py (the issue's own case)

Notes

  • Targeted at 4.x; 5.x carries byte-identical code and is affected the same way.
  • modules/imgproc public API and ABI are unchanged.
  • The OpenCL path is unaffected: CV_OCL_RUN is gated on hist.dims() <= 2 and
    returns before the changed line.

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
  • The PR is proposed to the proper branch
  • There is a reference to the original bug report and related work
  • There is accuracy test, performance test and test data in opencv_extra repository, if applicable
  • The feature is well documented and sample code can be built with the project CMake

The std::vector<> overload of calcBackProject() rebuilds a multi-channel
histogram into an equivalent Mat carrying one extra dimension (H), so that a
3D histogram arriving from the bindings as a 2D multi-channel Mat is
interpreted correctly. H was used for the argument checks, but the original
`hist` was forwarded to the implementation, which then saw dims == 2 and
looked values up using only the first two channels.

Forward H instead. For single-channel histograms H aliases hist, so the
behaviour there is unchanged.

Fixes opencv#29938
Copilot AI lite review requested due to automatic review settings September 13, 2026 16:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The fix is narrowly scoped and covered by regression tests.

Pull request overview

Fixes 3D histogram back-projection by forwarding the corrected multidimensional histogram.

Changes:

  • Corrects calcBackProject() handling of multi-channel histograms.
  • Adds C++ and Python regression tests for issue #29938.
File summaries
File Description
modules/imgproc/test/test_histograms.cpp Adds C++ 3D regression coverage.
modules/imgproc/src/histogram.cpp Forwards the corrected histogram representation.
modules/imgproc/misc/python/test/test_imgproc.py Adds Python reproduction coverage.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@asmorkalov asmorkalov self-assigned this Sep 14, 2026
@asmorkalov
asmorkalov self-requested a review September 14, 2026 06:55
@asmorkalov asmorkalov added this to the 4.15.0 milestone Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants