HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavaMinor

Calculating luma of a rect in an image of .yuv format

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
formatyuvimagelumarectcalculating

Problem

I'm trying to get the average luma of an area in an YUV image in the Android camera. I have come up with the following code which I think is correct and produces consistent results.

private static final int AVERAGE_LUMA_SAMPLE_SIZE = 2;

// sample the average luma
// yuvImage is the byte data return from a camera's previewFrame
public static int getAverageLuma(byte[] yuvImage, int imageWidth, int imageHeight, RectF sampleArea) {
    int totalLuma = 0;
    int totalSampled = 0;

    int top = (int)Math.max(sampleArea.top, 0);
    int left = (int)Math.max(sampleArea.left, 0);
    int right = (int)Math.min(sampleArea.right, imageWidth);
    int bottom = (int)Math.min(sampleArea.bottom, imageHeight);

    for(int i = top; i < bottom; i += AVERAGE_LUMA_SAMPLE_SIZE) {
        int rowStartPosition = (i * imageWidth) + left;

        for (int j = 0; j < right; j += AVERAGE_LUMA_SAMPLE_SIZE) {
            int bytePostion = rowStartPosition + j;             
            int y = (0xff & ((int) yuvImage[bytePostion])) - 16;

            totalLuma += Math.max(y, 0);                
            totalSampled++;
        }
    }

    return Math.round((float)totalLuma / totalSampled);
}

Solution

One of the reasons that large 2d data sets are often reduced to a single 1D array is so that you can process the data in a single loop and the memory is contiguous. The trick to processing this data efficiently in a single loop is to be able to fiddle the single pointer in a way that makes sense...... and to do that, we need some smart pointer skips...

Consider the following single loop. It starts at the right place, and then skips the areas outside the sample rectangle....

int startpos = top * imageWidth + left;
int lastpos = bottom * imageWidth + right;
// skip is the amount of unsampled data from the right-side of the sample
// rectangle to the left side of the next row.
int skip = imageWidth - right + left;

// limit is a moving limit that we use to break-process the samples.
// limit only applies if we actually need to skip data.
int limit = skip == 0 ? lastpos + 1 : (top * imageWidth) + right;
for (int i = startpos; i  limit) {
        // take the loop-increment in to consideration.
        i += skip - AVERAGE_LUMA_SAMPLE_SIZE;
        limit += imageWidth;
    } else {

        // Do the sampling work here.....

    }
}


Normally I am against updating a loop-counter index inside the loop, but there are times when it makes sense. This is one of those times.

Note, the skip variable is being calculated naively in this code. It should be smarter about taking the sample-size into consideration, and making sure it hits the beginning of the sample rectangle properly on the next line.... as it stands, if the rectangle is an odd-size, it may cause the start position of the next row to not be on the rectangle boundary. On average, I don't think this will make a difference, but that is your call.

Code Snippets

int startpos = top * imageWidth + left;
int lastpos = bottom * imageWidth + right;
// skip is the amount of unsampled data from the right-side of the sample
// rectangle to the left side of the next row.
int skip = imageWidth - right + left;

// limit is a moving limit that we use to break-process the samples.
// limit only applies if we actually need to skip data.
int limit = skip == 0 ? lastpos + 1 : (top * imageWidth) + right;
for (int i = startpos; i <= lastpos; i+= AVERAGE_LUMA_SAMPLE_SIZE ) {
    if (i > limit) {
        // take the loop-increment in to consideration.
        i += skip - AVERAGE_LUMA_SAMPLE_SIZE;
        limit += imageWidth;
    } else {

        // Do the sampling work here.....


    }
}

Context

StackExchange Code Review Q#40696, answer score: 2

Revisions (0)

No revisions yet.