'Rolling ball background subtraction algorithm for OpenCV
Is there an OpenCV (android) implementation of "rolling ball" background subtraction algorithm found in ImageJ: Process->Subtract Background?
OpenCV has a BackgroundSubtractorMOG class, but it is used for video streams not single, independent images.
This is an example of what this method does: http://imgur.com/8SN2CFz
Here is a documentation of the process: http://imagejdocu.tudor.lu/doku.php?id=gui:process:subtract_background
Solution 1:[1]
There's no implementation in the OpenCV C libraries that I know of and the Android JNI wrappers are just that - wrappers around the main libraries.
Having said that the source code for the ImageJ implementation is available online here and so you should be able to incorporate this directly into your Android image processing pipeline.
There is some discussion about the relative merits of rolling ball vs. e.g. using a disk structuring element (which is available in OpenCV) here.
If you absolutely require Rolling Ball and OpenCV then unfortunately it's not available 'out of the box'.
Solution 2:[2]
There is a recent rolling-ball implementation in opencv that you can find here
https://pypi.org/project/opencv-rolling-ball/
In short
Install pip install opencv-rolling-ball
Example
import cv2
from cv2_rolling_ball import subtract_background_rolling_ball
img = cv2.imread(f'path/to/img.tif', 0)
img, background = subtract_background_rolling_ball(img, 30, light_background=True, use_paraboloid=False, do_presmooth=True)
Solution 3:[3]
Building on @Xenthor's answer this is what I came up with:
import numpy as np
import scipy.ndimage as ndi
from scipy.ndimage._ni_support import _normalize_sequence
def rolling_ball_filter(data, ball_radius, spacing=None, top=False, **kwargs):
"""Rolling ball filter implemented with morphology operations
This implenetation is very similar to that in ImageJ and uses a top hat transform
with a ball shaped structuring element
https://en.wikipedia.org/wiki/Top-hat_transform
Parameters
----------
data : ndarray
image data (assumed to be on a regular grid)
ball_radius : float
the radius of the ball to roll
spacing : int or sequence
the spacing of the image data
top : bool
whether to roll the ball on the top or bottom of the data
kwargs : key word arguments
these are passed to the ndimage morphological operations
Returns
-------
data_nb : ndarray
data with background subtracted
bg : ndarray
background that was subtracted from the data
"""
ndim = data.ndim
if spacing is None:
spacing = 1
spacing = _normalize_sequence(spacing, ndim)
radius = np.asarray(_normalize_sequence(ball_radius, ndim))
mesh = np.array(np.meshgrid(*[np.arange(-r, r + s, s) for r, s in zip(radius, spacing)], indexing="ij"))
structure = 2 * np.sqrt(1 - ((mesh / radius.reshape(-1, *((1,) * ndim)))**2).sum(0))
structure[~np.isfinite(structure)] = 0
if not top:
# ndi.white_tophat(data, structure=structure, output=background)
background = ndi.grey_erosion(data, structure=structure, **kwargs)
background = ndi.grey_dilation(background, structure=structure, **kwargs)
else:
# ndi.black_tophat(data, structure=structure, output=background)
background = ndi.grey_dilation(data, structure=structure, **kwargs)
background = ndi.grey_erosion(background, structure=structure, **kwargs)
return data - background, background
Solution 4:[4]
Edit: Before using the method in this post read the comments below and also consider the answers of @renat and @David Hoffman.
In case someone is still looking for rolling ball background correction in python. For me, the following worked out very well.
- Load the image and process each channel separately.
- Create a weighted ball structuring element
- Use white tophat transform
Here is some code for a monochrome image:
import scipy.ndimage as scim
from scipy.misc import imsave
from skimage.morphology import ball
# Read image
im = scim.imread("path")[:, :, 0].astype(int)
# Create 3D ball with radius of 50 and a diameter of 2*50+1
s = ball(50)
# Take only the upper half of the ball
h = s.shape[1] // 2 + 1 # 50 + 1
# Flatten the 3D ball to a weighted 2D disc
s = s[:h, :, :].sum(axis=0)
# Rescale weights into 0-255
s = (255 * (s - s.min())) / (s.max() - s.min())
# Use im-opening(im,ball) (i.e. white tophat transform) (see original publication)
im_corr = scim.white_tophat(im, structure=s)
# Save corrected image
imsave('outfile', im_corr)
This gives you not the exact same result as the imagej implementation but the results are quite similar. In my case there were both, better and worse corrected regions. Moreover the overall color intensity was higher.
Solution 5:[5]
The original algorithm that ImageJ implements comes from a 1983 paper https://www.computer.org/csdl/magazine/co/1983/01/01654163/13rRUwwJWBB. I took a look at it and it is actually a grayscale morphological white top-hat with a ball-shaped grayscale structuring element (see https://en.wikipedia.org/wiki/Top-hat_transform). In the ImageJ implementation (available here https://imagej.nih.gov/ij/developer/source/ij/plugin/filter/BackgroundSubtracter.java.html), the image is downsampled depending on the structuring elements' radius, then upsampled to the original resolution and, by default, a smoothing operation using a 3x3 mean filter is applied before computing the background to subtract. This likely explains the differences observed with the method proposed by Xenthor.
If you are working on Android, you have several options: 1) using the ImageJ library, since it is in Java, you will however need to implement an OpenCV-ImageJ image bridge; 2) if you work in C++ using the Android NDK and since OpenCV does not implement grayscale morphology for non-flat structuring elements, you can use ITK (https://itk.org/) instead to perform the graycale white top-hat; 3) still using the NDK, there is an OpenCV-based C++ port of the algorithm available here: https://github.com/folterj/BioImageOperation/tree/master/BioImageOperation, however it is still a work in progress.
Solution 6:[6]
I realize it's not opencv, but there is an implementation in scikit-image (version ? 0.18).
from skimage import data, restoration
image = data.coins()
background = restoration.rolling_ball(image, radius=100)
result = image - background
A more detailed walkthrough is provided in the documentation
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Community |
| Solution 2 | renat |
| Solution 3 | Night Train |
| Solution 4 | |
| Solution 5 | |
| Solution 6 |
