Skip to content

Commit

Permalink
Merge pull request #1319 from Rylern/extractMaskedPixels
Browse files Browse the repository at this point in the history
Extract masked pixels
  • Loading branch information
petebankhead committed Sep 9, 2023
2 parents 67a1ed7 + 5b7cc09 commit 9a86e32
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
*/
public class OpenCVTools {

private static Logger logger = LoggerFactory.getLogger(OpenCVTools.class);
private static final Logger logger = LoggerFactory.getLogger(OpenCVTools.class);

/**
* Convert a BufferedImage to an OpenCV Mat.
Expand Down Expand Up @@ -2651,6 +2651,144 @@ public static List<IndexedPixel> getMaskedPixels(Mat mat, Mat mask) {
}
return pixels;
}

/**
* <p>Extract pixels from an image using a mask, limited to the specified channel index.</p>
* <p>The mask can have one or multiple channels:</p>
* <ul>
* <li>If the mask has only one channel, this channel is used.</li>
* <li>If the mask is multichannel, only the specified channel is used.</li>
* </ul>
* <p>The input and the mask must have the same dimensions.</p>
*
* @param input the image from where the pixels should be extracted
* @param mask the mask to apply to the input
* @param channel the channel index (zero-based) to consider
* @return a float array containing the values of the input filtered by the mask
* @throws IllegalArgumentException when the input and the mask don't have the same dimensions
*/
public static float[] extractMaskedFloats(Mat input, Mat mask, int channel) {
try (PointerScope scope = new PointerScope()) {
Mat inputChannel;
if (input.channels() == 1) {
inputChannel = input;
} else {
inputChannel = new Mat();
opencv_core.extractChannel(input, inputChannel, channel);
}

Mat maskChannel;
if (mask.channels() == 1) {
maskChannel = mask;
} else {
maskChannel = new Mat();
opencv_core.extractChannel(mask, maskChannel, channel);
}

if (inputChannel.rows() != maskChannel.rows()) {
throw new IllegalArgumentException("The input and the mask don't have the same number of rows");
}
if (inputChannel.cols() != maskChannel.cols()) {
throw new IllegalArgumentException("The input and the mask don't have the same number of columns");
}

int height = inputChannel.rows();
int width = inputChannel.cols();
float[] output = new float[height*width];

long[] indices = new long[2]; // this array is used to avoid calling Indexer.getDouble() with varargs arguments (which is slower)
int outputIndex = 0;
try (var idx = inputChannel.createIndexer()) {
try (var idxMask = maskChannel.createIndexer()) {
for (int y = 0; y < height; y++) {
indices[0] = y;
for (int x = 0; x < width; x++) {
indices[1] = x;
if (idxMask.getDouble(indices) != 0) {
output[outputIndex] = (float) idx.getDouble(indices);
outputIndex++;
}
}
}
}
}

if (outputIndex < output.length) {
return Arrays.copyOf(output, outputIndex);
} else {
return output;
}
}
}

/**
* <p>Extract pixels from an image using a mask, limited to the specified channel index.</p>
* <p>The mask can have one or multiple channels:</p>
* <ul>
* <li>If the mask has only one channel, this channel is used.</li>
* <li>If the mask is multichannel, only the specified channel is used.</li>
* </ul>
* <p>The input and the mask must have the same dimensions.</p>
*
* @param input the image from where the pixels should be extracted
* @param mask the mask to apply to the input
* @param channel the channel index (zero-based) to consider
* @return a double array containing the values of the input filtered by the mask
* @throws IllegalArgumentException when the input and the mask don't have the same dimensions
*/
public static double[] extractMaskedDoubles(Mat input, Mat mask, int channel) {
try (PointerScope scope = new PointerScope()) {
Mat inputChannel;
if (input.channels() == 1) {
inputChannel = input;
} else {
inputChannel = new Mat();
opencv_core.extractChannel(input, inputChannel, channel);
}

Mat maskChannel;
if (mask.channels() == 1) {
maskChannel = mask;
} else {
maskChannel = new Mat();
opencv_core.extractChannel(mask, maskChannel, channel);
}

if (inputChannel.rows() != maskChannel.rows()) {
throw new IllegalArgumentException("The input and the mask don't have the same number of rows");
}
if (inputChannel.cols() != maskChannel.cols()) {
throw new IllegalArgumentException("The input and the mask don't have the same number of columns");
}

int height = inputChannel.rows();
int width = inputChannel.cols();
double[] output = new double[height*width];

long[] indices = new long[2]; // this array is used to avoid calling Indexer.getDouble() with varargs arguments (which is slower)
int outputIndex = 0;
try (var idx = inputChannel.createIndexer()) {
try (var idxMask = maskChannel.createIndexer()) {
for (int y = 0; y < height; y++) {
indices[0] = y;
for (int x = 0; x < width; x++) {
indices[1] = x;
if (idxMask.getDouble(indices) != 0) {
output[outputIndex] = idx.getDouble(indices);
outputIndex++;
}
}
}
}
}

if (outputIndex < output.length) {
return Arrays.copyOf(output, outputIndex);
} else {
return output;
}
}
}


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import java.util.ArrayList;
Expand Down Expand Up @@ -699,6 +700,98 @@ void testMerge() {
}
}
}

@Test
public void testFloatPixelExtractionWithMaskOnSingleChannelImage() {
try (PointerScope scope = new PointerScope()) {
int circleRadius = 200;
Mat mat = OpenCVTools.createDisk(circleRadius, false);
Mat mask = mat.clone();

float[] maskedPixels = OpenCVTools.extractMaskedFloats(mat, mask, 0);

assertTrue(mean(OpenCVTools.extractFloats(mat)) < 1);
assertEquals(1, mean(maskedPixels));
}
}

@Test
public void testFloatPixelExtractionWithMaskOnMultiChannelImage() {
try (PointerScope scope = new PointerScope()) {
var image = new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB);
var graphics = image.createGraphics();
graphics.setColor(Color.GREEN);
graphics.fillOval(0, 0, image.getWidth(), image.getHeight());
graphics.dispose();
Mat mat = OpenCVTools.imageToMat(image);
Mat mask = mat.clone();

float[] maskedRedPixels = OpenCVTools.extractMaskedFloats(mat, mask, 0);
float[] maskedGreenPixels = OpenCVTools.extractMaskedFloats(mat, mask, 1);

assertTrue(mean(OpenCVTools.extractFloats(mat)) < 255);
assertEquals(0, mean(maskedRedPixels));
assertEquals(255, mean(maskedGreenPixels));
}
}

private float mean(float[] array) {
if (array.length == 0) {
return 0;
} else {
float sum = 0;
for (float element: array) {
sum += element;
}
return sum / array.length;
}
}

@Test
public void testDoublePixelExtractionWithMaskOnSingleChannelImage() {
try (PointerScope scope = new PointerScope()) {
int circleRadius = 200;
Mat mat = OpenCVTools.createDisk(circleRadius, false);
Mat mask = mat.clone();

double[] maskedPixels = OpenCVTools.extractMaskedDoubles(mat, mask, 0);

assertTrue(mean(OpenCVTools.extractDoubles(mat)) < 1);
assertEquals(1, mean(maskedPixels));
}
}

@Test
public void testDoublePixelExtractionWithMaskOnMultiChannelImage() {
try (PointerScope scope = new PointerScope()) {
var image = new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB);
var graphics = image.createGraphics();
graphics.setColor(Color.GREEN);
graphics.fillOval(0, 0, image.getWidth(), image.getHeight());
graphics.dispose();
Mat mat = OpenCVTools.imageToMat(image);
Mat mask = mat.clone();

double[] maskedRedPixels = OpenCVTools.extractMaskedDoubles(mat, mask, 0);
double[] maskedGreenPixels = OpenCVTools.extractMaskedDoubles(mat, mask, 1);

assertTrue(mean(OpenCVTools.extractDoubles(mat)) < 255);
assertEquals(0, mean(maskedRedPixels));
assertEquals(255, mean(maskedGreenPixels));
}
}

private double mean(double[] array) {
if (array.length == 0) {
return 0;
} else {
double sum = 0;
for (double element: array) {
sum += element;
}
return sum / array.length;
}
}


}

0 comments on commit 9a86e32

Please sign in to comment.