From 6e02030cbcef27d6a8e5793417828cc3376fadd5 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 1 Apr 2018 15:33:30 -0400 Subject: [PATCH 1/5] Add contrast limited adaptive histogram equalization. This method is fast and super useful. --- src/Makefile | 1 + src/omv/Makefile | 1 + src/omv/img/clahe.c | 419 ++++++++++++++++++ src/omv/img/filter.c | 86 ++-- src/omv/img/imlib.h | 4 +- src/omv/py/py_image.c | 17 +- src/omv/py/qstrdefsomv.h | 3 + .../adaptive_histogram_equalization.py | 29 ++ .../histogram_equalization.py | 19 + 9 files changed, 537 insertions(+), 42 deletions(-) create mode 100644 src/omv/img/clahe.c create mode 100644 usr/examples/04-Image-Filters/adaptive_histogram_equalization.py create mode 100644 usr/examples/04-Image-Filters/histogram_equalization.py diff --git a/src/Makefile b/src/Makefile index fd5aed12b..4ad59ddbe 100755 --- a/src/Makefile +++ b/src/Makefile @@ -154,6 +154,7 @@ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/, \ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ binary.o \ blob.o \ + clahe.o \ draw.o \ qrcode.o \ apriltag.o \ diff --git a/src/omv/Makefile b/src/omv/Makefile index f59950946..017b4df3d 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -24,6 +24,7 @@ SRCS += $(addprefix , \ SRCS += $(addprefix img/, \ binary.c \ blob.c \ + clahe.c \ draw.c \ qrcode.c \ apriltag.c \ diff --git a/src/omv/img/clahe.c b/src/omv/img/clahe.c new file mode 100644 index 000000000..aed241e50 --- /dev/null +++ b/src/omv/img/clahe.c @@ -0,0 +1,419 @@ +/* This file is part of the OpenMV project. + * Copyright (c) 2013-2018 Ibrahim Abdelkader & Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + */ + +#include "imlib.h" +#define BYTE_IMAGE + +/* + * ANSI C code from the article + * "Contrast Limited Adaptive Histogram Equalization" + * by Karel Zuiderveld, karel@cv.ruu.nl + * in "Graphics Gems IV", Academic Press, 1994 + * + * + * These functions implement Contrast Limited Adaptive Histogram Equalization. + * The main routine (CLAHE) expects an input image that is stored contiguously in + * memory; the CLAHE output image overwrites the original input image and has the + * same minimum and maximum values (which must be provided by the user). + * This implementation assumes that the X- and Y image resolutions are an integer + * multiple of the X- and Y sizes of the contextual regions. A check on various other + * error conditions is performed. + * + * #define the symbol BYTE_IMAGE to make this implementation suitable for + * 8-bit images. The maximum number of contextual regions can be redefined + * by changing uiMAX_REG_X and/or uiMAX_REG_Y; the use of more than 256 + * contextual regions is not recommended. + * + * The code is ANSI-C and is also C++ compliant. + * + * Author: Karel Zuiderveld, Computer Vision Research Group, + * Utrecht, The Netherlands (karel@cv.ruu.nl) + */ + +#ifdef BYTE_IMAGE +typedef unsigned char kz_pixel_t; /* for 8 bit-per-pixel images */ +#define uiNR_OF_GREY (256) +#else +typedef unsigned short kz_pixel_t; /* for 12 bit-per-pixel images (default) */ +# define uiNR_OF_GREY (4096) +#endif + +/******** Prototype of CLAHE function. Put this in a separate include file. *****/ +int CLAHE(kz_pixel_t* pImage, unsigned int uiXRes, unsigned int uiYRes, kz_pixel_t Min, + kz_pixel_t Max, unsigned int uiNrX, unsigned int uiNrY, + unsigned int uiNrBins, float fCliplimit); + +/*********************** Local prototypes ************************/ +static void ClipHistogram (unsigned long*, unsigned int, unsigned long); +static void MakeHistogram (kz_pixel_t*, unsigned int, unsigned int, unsigned int, + unsigned long*, unsigned int, kz_pixel_t*); +static void MapHistogram (unsigned long*, kz_pixel_t, kz_pixel_t, + unsigned int, unsigned long); +static void MakeLut (kz_pixel_t*, kz_pixel_t, kz_pixel_t, unsigned int); +static void Interpolate (kz_pixel_t*, int, unsigned long*, unsigned long*, + unsigned long*, unsigned long*, unsigned int, unsigned int, kz_pixel_t*); + +/************** Start of actual code **************/ +const unsigned int uiMAX_REG_X = 16; /* max. # contextual regions in x-direction */ +const unsigned int uiMAX_REG_Y = 16; /* max. # contextual regions in y-direction */ + +/************************** main function CLAHE ******************/ +int CLAHE (kz_pixel_t* pImage, unsigned int uiXRes, unsigned int uiYRes, + kz_pixel_t Min, kz_pixel_t Max, unsigned int uiNrX, unsigned int uiNrY, + unsigned int uiNrBins, float fCliplimit) +/* pImage - Pointer to the input/output image + * uiXRes - Image resolution in the X direction + * uiYRes - Image resolution in the Y direction + * Min - Minimum greyvalue of input image (also becomes minimum of output image) + * Max - Maximum greyvalue of input image (also becomes maximum of output image) + * uiNrX - Number of contextial regions in the X direction (min 2, max uiMAX_REG_X) + * uiNrY - Number of contextial regions in the Y direction (min 2, max uiMAX_REG_Y) + * uiNrBins - Number of greybins for histogram ("dynamic range") + * float fCliplimit - Normalized cliplimit (higher values give more contrast) + * The number of "effective" greylevels in the output image is set by uiNrBins; selecting + * a small value (eg. 128) speeds up processing and still produce an output image of + * good quality. The output image will have the same minimum and maximum value as the input + * image. A clip limit smaller than 1 results in standard (non-contrast limited) AHE. + */ +{ + unsigned int uiX, uiY; /* counters */ + unsigned int uiXSize, uiYSize, uiSubX, uiSubY; /* size of context. reg. and subimages */ + unsigned int uiXL, uiXR, uiYU, uiYB; /* auxiliary variables interpolation routine */ + unsigned long ulClipLimit, ulNrPixels;/* clip limit and region pixel count */ + kz_pixel_t* pImPointer; /* pointer to image */ + kz_pixel_t aLUT[uiNR_OF_GREY]; /* lookup table used for scaling of input image */ + unsigned long* pulHist, *pulMapArray; /* pointer to histogram and mappings*/ + unsigned long* pulLU, *pulLB, *pulRU, *pulRB; /* auxiliary pointers interpolation */ + + if (uiNrX > uiMAX_REG_X) return -1; /* # of regions x-direction too large */ + if (uiNrY > uiMAX_REG_Y) return -2; /* # of regions y-direction too large */ + if (uiXRes % uiNrX) return -3; /* x-resolution no multiple of uiNrX */ + if (uiYRes % uiNrY) return -4; /* y-resolution no multiple of uiNrY */ + if (Max >= uiNR_OF_GREY) return -5; /* maximum too large */ + if (Min >= Max) return -6; /* minimum equal or larger than maximum */ + if (uiNrX < 2 || uiNrY < 2) return -7;/* at least 4 contextual regions required */ + if (fCliplimit == 1.0) return 0; /* is OK, immediately returns original image. */ + if (uiNrBins == 0) uiNrBins = 128; /* default value when not specified */ + + pulMapArray=(unsigned long *)fb_alloc(sizeof(unsigned long)*uiNrX*uiNrY*uiNrBins); + if (pulMapArray == 0) return -8; /* Not enough memory! (try reducing uiNrBins) */ + + uiXSize = uiXRes/uiNrX; uiYSize = uiYRes/uiNrY; /* Actual size of contextual regions */ + ulNrPixels = (unsigned long)uiXSize * (unsigned long)uiYSize; + + if(fCliplimit > 0.0) { /* Calculate actual cliplimit */ + ulClipLimit = (unsigned long) (fCliplimit * (uiXSize * uiYSize) / uiNrBins); + ulClipLimit = (ulClipLimit < 1UL) ? 1UL : ulClipLimit; + } + else ulClipLimit = 1UL<<14; /* Large value, do not clip (AHE) */ + MakeLut(aLUT, Min, Max, uiNrBins); /* Make lookup table for mapping of greyvalues */ + /* Calculate greylevel mappings for each contextual region */ + for (uiY = 0, pImPointer = pImage; uiY < uiNrY; uiY++) { + for (uiX = 0; uiX < uiNrX; uiX++, pImPointer += uiXSize) { + pulHist = &pulMapArray[uiNrBins * (uiY * uiNrX + uiX)]; + MakeHistogram(pImPointer,uiXRes,uiXSize,uiYSize,pulHist,uiNrBins,aLUT); + ClipHistogram(pulHist, uiNrBins, ulClipLimit); + MapHistogram(pulHist, Min, Max, uiNrBins, ulNrPixels); + } + pImPointer += (uiYSize - 1) * uiXRes; /* skip lines, set pointer */ + } + + /* Interpolate greylevel mappings to get CLAHE image */ + for (pImPointer = pImage, uiY = 0; uiY <= uiNrY; uiY++) { + if (uiY == 0) { /* special case: top row */ + uiSubY = uiYSize >> 1; uiYU = 0; uiYB = 0; + } + else { + if (uiY == uiNrY) { /* special case: bottom row */ + uiSubY = (uiYSize+1) >> 1; uiYU = uiNrY-1; uiYB = uiYU; + } + else { /* default values */ + uiSubY = uiYSize; uiYU = uiY - 1; uiYB = uiYU + 1; + } + } + for (uiX = 0; uiX <= uiNrX; uiX++) { + if (uiX == 0) { /* special case: left column */ + uiSubX = uiXSize >> 1; uiXL = 0; uiXR = 0; + } + else { + if (uiX == uiNrX) { /* special case: right column */ + uiSubX = (uiXSize+1) >> 1; uiXL = uiNrX - 1; uiXR = uiXL; + } + else { /* default values */ + uiSubX = uiXSize; uiXL = uiX - 1; uiXR = uiXL + 1; + } + } + + pulLU = &pulMapArray[uiNrBins * (uiYU * uiNrX + uiXL)]; + pulRU = &pulMapArray[uiNrBins * (uiYU * uiNrX + uiXR)]; + pulLB = &pulMapArray[uiNrBins * (uiYB * uiNrX + uiXL)]; + pulRB = &pulMapArray[uiNrBins * (uiYB * uiNrX + uiXR)]; + Interpolate(pImPointer,uiXRes,pulLU,pulRU,pulLB,pulRB,uiSubX,uiSubY,aLUT); + pImPointer += uiSubX; /* set pointer on next matrix */ + } + pImPointer += (uiSubY - 1) * uiXRes; + } + fb_free(); /* free space for histograms */ + return 0; /* return status OK */ +} + +void ClipHistogram (unsigned long* pulHistogram, unsigned int + uiNrGreylevels, unsigned long ulClipLimit) +/* This function performs clipping of the histogram and redistribution of bins. + * The histogram is clipped and the number of excess pixels is counted. Afterwards + * the excess pixels are equally redistributed across the whole histogram (providing + * the bin count is smaller than the cliplimit). + */ +{ + unsigned long* pulBinPointer, *pulEndPointer, *pulHisto; + unsigned long ulNrExcess, ulUpper, ulBinIncr, ulStepSize, i; + long lBinExcess; + + ulNrExcess = 0; pulBinPointer = pulHistogram; + for (i = 0; i < uiNrGreylevels; i++) { /* calculate total number of excess pixels */ + lBinExcess = (long) pulBinPointer[i] - (long) ulClipLimit; + if (lBinExcess > 0) ulNrExcess += lBinExcess; /* excess in current bin */ + }; + + /* Second part: clip histogram and redistribute excess pixels in each bin */ + ulBinIncr = ulNrExcess / uiNrGreylevels; /* average binincrement */ + ulUpper = ulClipLimit - ulBinIncr; /* Bins larger than ulUpper set to cliplimit */ + + for (i = 0; i < uiNrGreylevels; i++) { + if (pulHistogram[i] > ulClipLimit) pulHistogram[i] = ulClipLimit; /* clip bin */ + else { + if (pulHistogram[i] > ulUpper) { /* high bin count */ + ulNrExcess -= pulHistogram[i] - ulUpper; pulHistogram[i]=ulClipLimit; + } + else { /* low bin count */ + ulNrExcess -= ulBinIncr; pulHistogram[i] += ulBinIncr; + } + } + } + + while (ulNrExcess) { /* Redistribute remaining excess */ + pulEndPointer = &pulHistogram[uiNrGreylevels]; pulHisto = pulHistogram; + + while (ulNrExcess && pulHisto < pulEndPointer) { + ulStepSize = uiNrGreylevels / ulNrExcess; + if (ulStepSize < 1) ulStepSize = 1; /* stepsize at least 1 */ + for (pulBinPointer=pulHisto; pulBinPointer < pulEndPointer && ulNrExcess; + pulBinPointer += ulStepSize) { + if (*pulBinPointer < ulClipLimit) { + (*pulBinPointer)++; ulNrExcess--; /* reduce excess */ + } + } + pulHisto++; /* restart redistributing on other bin location */ + } + } +} + +void MakeHistogram (kz_pixel_t* pImage, unsigned int uiXRes, + unsigned int uiSizeX, unsigned int uiSizeY, + unsigned long* pulHistogram, + unsigned int uiNrGreylevels, kz_pixel_t* pLookupTable) +/* This function classifies the greylevels present in the array image into + * a greylevel histogram. The pLookupTable specifies the relationship + * between the greyvalue of the pixel (typically between 0 and 4095) and + * the corresponding bin in the histogram (usually containing only 128 bins). + */ +{ + kz_pixel_t* pImagePointer; + unsigned int i; + + for (i = 0; i < uiNrGreylevels; i++) pulHistogram[i] = 0L; /* clear histogram */ + + for (i = 0; i < uiSizeY; i++) { + pImagePointer = &pImage[uiSizeX]; + while (pImage < pImagePointer) pulHistogram[pLookupTable[*pImage++]]++; + pImagePointer += uiXRes; + pImage = &pImagePointer[-(int)uiSizeX]; /* go to bdeginning of next row */ + } +} + +void MapHistogram (unsigned long* pulHistogram, kz_pixel_t Min, kz_pixel_t Max, + unsigned int uiNrGreylevels, unsigned long ulNrOfPixels) +/* This function calculates the equalized lookup table (mapping) by + * cumulating the input histogram. Note: lookup table is rescaled in range [Min..Max]. + */ +{ + unsigned int i; unsigned long ulSum = 0; + const float fScale = ((float)(Max - Min)) / ulNrOfPixels; + const unsigned long ulMin = (unsigned long) Min; + + for (i = 0; i < uiNrGreylevels; i++) { + ulSum += pulHistogram[i]; pulHistogram[i]=(unsigned long)(ulMin+ulSum*fScale); + if (pulHistogram[i] > Max) pulHistogram[i] = Max; + } +} + +void MakeLut (kz_pixel_t * pLUT, kz_pixel_t Min, kz_pixel_t Max, unsigned int uiNrBins) +/* To speed up histogram clipping, the input image [Min,Max] is scaled down to + * [0,uiNrBins-1]. This function calculates the LUT. + */ +{ + int i; + const kz_pixel_t BinSize = (kz_pixel_t) (1 + (Max - Min) / uiNrBins); + + for (i = Min; i <= Max; i++) pLUT[i] = (i - Min) / BinSize; +} + +void Interpolate (kz_pixel_t * pImage, int uiXRes, unsigned long * pulMapLU, + unsigned long * pulMapRU, unsigned long * pulMapLB, unsigned long * pulMapRB, + unsigned int uiXSize, unsigned int uiYSize, kz_pixel_t * pLUT) +/* pImage - pointer to input/output image + * uiXRes - resolution of image in x-direction + * pulMap* - mappings of greylevels from histograms + * uiXSize - uiXSize of image submatrix + * uiYSize - uiYSize of image submatrix + * pLUT - lookup table containing mapping greyvalues to bins + * This function calculates the new greylevel assignments of pixels within a submatrix + * of the image with size uiXSize and uiYSize. This is done by a bilinear interpolation + * between four different mappings in order to eliminate boundary artifacts. + * It uses a division; since division is often an expensive operation, I added code to + * perform a logical shift instead when feasible. + */ +{ + const unsigned int uiIncr = uiXRes-uiXSize; /* Pointer increment after processing row */ + kz_pixel_t GreyValue; unsigned int uiNum = uiXSize*uiYSize; /* Normalization factor */ + + unsigned int uiXCoef, uiYCoef, uiXInvCoef, uiYInvCoef, uiShift = 0; + + if (uiNum & (uiNum - 1)) /* If uiNum is not a power of two, use division */ + for (uiYCoef = 0, uiYInvCoef = uiYSize; uiYCoef < uiYSize; + uiYCoef++, uiYInvCoef--,pImage+=uiIncr) { + for (uiXCoef = 0, uiXInvCoef = uiXSize; uiXCoef < uiXSize; + uiXCoef++, uiXInvCoef--) { + GreyValue = pLUT[*pImage]; /* get histogram bin value */ + *pImage++ = (kz_pixel_t ) ((uiYInvCoef * (uiXInvCoef*pulMapLU[GreyValue] + + uiXCoef * pulMapRU[GreyValue]) + + uiYCoef * (uiXInvCoef * pulMapLB[GreyValue] + + uiXCoef * pulMapRB[GreyValue])) / uiNum); + } + } + else { /* avoid the division and use a right shift instead */ + while (uiNum >>= 1) uiShift++; /* Calculate 2log of uiNum */ + for (uiYCoef = 0, uiYInvCoef = uiYSize; uiYCoef < uiYSize; + uiYCoef++, uiYInvCoef--,pImage+=uiIncr) { + for (uiXCoef = 0, uiXInvCoef = uiXSize; uiXCoef < uiXSize; + uiXCoef++, uiXInvCoef--) { + GreyValue = pLUT[*pImage]; /* get histogram bin value */ + *pImage++ = (kz_pixel_t)((uiYInvCoef* (uiXInvCoef * pulMapLU[GreyValue] + + uiXCoef * pulMapRU[GreyValue]) + + uiYCoef * (uiXInvCoef * pulMapLB[GreyValue] + + uiXCoef * pulMapRB[GreyValue])) >> uiShift); + } + } + } +} + +void imlib_clahe_histeq(image_t *img, float clip_limit, image_t *mask) +{ + int xTileSize = IM_MAX(uiMAX_REG_X >> (10 - IM_MIN(IM_LOG2_32(img->w), 10)), 2); + int yTileSize = IM_MAX(uiMAX_REG_Y >> (10 - IM_MIN(IM_LOG2_32(img->h), 10)), 2); + int pImageW = img->w + ((img->w % xTileSize) ? (xTileSize - (img->w % xTileSize)) : 0); + int pImageH = img->h + ((img->h % yTileSize) ? (yTileSize - (img->h % yTileSize)) : 0); + int xOffset = (pImageW - img->w) / 2; + int yOffset = (pImageH - img->h) / 2; + + image_t temp; + temp.w = img->w; + temp.h = img->h; + temp.bpp = img->bpp; + temp.data = fb_alloc0(pImageW * pImageH * sizeof(kz_pixel_t)); + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x))); + } + } + break; + } + case IMAGE_BPP_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + case IMAGE_BPP_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))); + } + } + break; + } + default: { + break; + } + } + + CLAHE((kz_pixel_t *) temp.data, + pImageW, pImageH, + COLOR_GRAYSCALE_MIN, COLOR_GRAYSCALE_MAX, + xTileSize, yTileSize, + COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1, + clip_limit); + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, + COLOR_GRAYSCALE_TO_BINARY(IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset))); + } + } + break; + } + case IMAGE_BPP_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset)); + } + } + break; + } + case IMAGE_BPP_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, + imlib_yuv_to_rgb(IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset), + COLOR_RGB565_TO_U(pixel), + COLOR_RGB565_TO_V(pixel))); + } + } + break; + } + default: { + break; + } + } + + fb_free(); +} diff --git a/src/omv/img/filter.c b/src/omv/img/filter.c index f50b67d56..a3ad8efbe 100644 --- a/src/omv/img/filter.c +++ b/src/omv/img/filter.c @@ -6,29 +6,35 @@ #include "fsort.h" #include "imlib.h" -void imlib_histeq(image_t *img) +void imlib_histeq(image_t *img, image_t *mask) { switch(img->bpp) { case IMAGE_BPP_BINARY: { int a = img->w * img->h; - float s = (COLOR_BINARY_MAX-COLOR_BINARY_MIN) / ((float) a); - uint32_t *hist = fb_alloc0((COLOR_BINARY_MAX-COLOR_BINARY_MIN+1)*sizeof(uint32_t)); - uint32_t *pixels = (uint32_t *) img->data; + float s = (COLOR_BINARY_MAX - COLOR_BINARY_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(uint32_t)); - // Compute the image histogram - for (int i=0; ih); + start < end; start++) { + for (int i = 0; i < UINT32_T_BITS; i++) { + hist[IMAGE_GET_BINARY_PIXEL_FAST(start, i) - COLOR_BINARY_MIN] += 1; + } } - // Compute the CDF - for (int i=0, sum=0; i<(COLOR_BINARY_MAX-COLOR_BINARY_MIN+1); i++) { + for (int i = 0, sum = 0, ii = COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1; i < ii; i++) { sum += hist[i]; hist[i] = sum; } - for (int i=0; ih; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, + fast_roundf((s * hist[pixel - COLOR_BINARY_MIN]) + COLOR_BINARY_MIN)); + } } fb_free(); @@ -36,24 +42,28 @@ void imlib_histeq(image_t *img) } case IMAGE_BPP_GRAYSCALE: { int a = img->w * img->h; - float s = (COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN) / ((float) a); - uint32_t *hist = fb_alloc0((COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN+1)*sizeof(uint32_t)); - uint8_t *pixels = (uint8_t *) img->data; + float s = (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(uint32_t)); - // Compute the image histogram - for (int i=0; ih); + start < end; start++) { + hist[(*start) - COLOR_GRAYSCALE_MIN] += 1; } - // Compute the CDF - for (int i=0, sum=0; i<(COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN+1); i++) { + for (int i = 0, sum = 0, ii = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; i < ii; i++) { sum += hist[i]; hist[i] = sum; } - for (int i=0; ih; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + fast_roundf((s * hist[pixel - COLOR_GRAYSCALE_MIN]) + COLOR_GRAYSCALE_MIN)); + } } fb_free(); @@ -61,26 +71,30 @@ void imlib_histeq(image_t *img) } case IMAGE_BPP_RGB565: { int a = img->w * img->h; - float s = (COLOR_Y_MAX-COLOR_Y_MIN) / ((float) a); - uint32_t *hist = fb_alloc0((COLOR_Y_MAX-COLOR_Y_MIN+1)*sizeof(uint32_t)); - uint16_t *pixels = (uint16_t *) img->data; + float s = (COLOR_Y_MAX - COLOR_Y_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_Y_MAX - COLOR_Y_MIN + 1) * sizeof(uint32_t)); - // Compute image histogram - for (int i=0; ih); + start < end; start++) { + hist[COLOR_RGB565_TO_Y(*start) - COLOR_Y_MIN] += 1; } - // Compute the CDF - for (int i=0, sum=0; i<(COLOR_Y_MAX-COLOR_Y_MIN+1); i++) { + for (int i = 0, sum = 0, ii = COLOR_Y_MAX - COLOR_Y_MIN + 1; i < ii; i++) { sum += hist[i]; hist[i] = sum; } - for (int i=0; ih; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, + imlib_yuv_to_rgb(fast_roundf(s * hist[COLOR_RGB565_TO_Y(pixel) - COLOR_Y_MIN]), + COLOR_RGB565_TO_U(pixel), + COLOR_RGB565_TO_V(pixel))); + } } fb_free(); diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 7697651bb..e11f76b4f 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1244,7 +1244,8 @@ void imlib_max(image_t *img, const char *path, image_t *other, int scalar, image void imlib_difference(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); void imlib_blend(image_t *img, const char *path, image_t *other, int scalar, float alpha, image_t *mask); // Filtering Functions -void imlib_histeq(image_t *img); +void imlib_histeq(image_t *img, image_t *mask); +void imlib_clahe_histeq(image_t *img, float clip_limit, image_t *mask); void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); void imlib_median_filter(image_t *img, const int ksize, float percentile, bool threshold, int offset, bool invert, image_t *mask); void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); @@ -1257,7 +1258,6 @@ void imlib_logpolar(image_t *img, bool linear, bool reverse); void imlib_remove_shadows(image_t *img, const char *path, image_t *other, int scalar, bool single); void imlib_chrominvar(image_t *img); void imlib_illuminvar(image_t *img); -void imlib_histeq(image_t *img); // Lens/Rotation Correction void imlib_lens_corr(image_t *img, float strength, float zoom); void imlib_rotation_corr(image_t *img, float x_rotation, float y_rotation, diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 6ac248db3..a7cac73c0 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1718,14 +1718,23 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_blend_obj, 2, py_image_blend); // Filtering Methods //////////////////// -static mp_obj_t py_image_histeq(mp_obj_t img_obj) +static mp_obj_t py_image_histeq(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + bool arg_adaptive = + py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_adaptive), false); + float arg_clip_limit = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_clip_limit), -1); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + fb_alloc_mark(); - imlib_histeq(py_helper_arg_to_image_mutable(img_obj)); + if (arg_adaptive) imlib_clahe_histeq(arg_img, arg_clip_limit, arg_msk); else imlib_histeq(arg_img, arg_msk); fb_alloc_free_till_mark(); - return img_obj; + return args[0]; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_histeq_obj, py_image_histeq); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_histeq_obj, 1, py_image_histeq); STATIC mp_obj_t py_image_mean(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index ed08babfa..39c839da1 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -474,6 +474,9 @@ Q(blend) // Histogram Equalization Q(histeq) +Q(adaptive) +Q(clip_limit) +// duplicate Q(mask) // Mean Q(mean) diff --git a/usr/examples/04-Image-Filters/adaptive_histogram_equalization.py b/usr/examples/04-Image-Filters/adaptive_histogram_equalization.py new file mode 100644 index 000000000..a958b531e --- /dev/null +++ b/usr/examples/04-Image-Filters/adaptive_histogram_equalization.py @@ -0,0 +1,29 @@ +# Adaptive Histogram Equalization +# +# This example shows off how to use adaptive histogram equalization to improve +# the contrast in the image. Adaptive histogram equalization splits the image +# into regions and then equalizes the histogram in those regions to improve +# the image contrast versus a global histogram equalization. Additionally, +# you may specify a clip limit to prevent the contrast from going wild. + +import sensor, image, time + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) +sensor.set_framesize(sensor.QQVGA) +sensor.skip_frames(time = 2000) +clock = time.clock() + +while(True): + clock.tick() + + # A clip_limit of < 0 gives you normal adaptive histogram equalization + # which may result in huge amounts of contrast noise... + + # A clip_limit of 1 does nothing. For best results go slightly higher + # than 1 like below. The higher you go the closer you get back to + # standard adaptive histogram equalization with huge contrast swings. + + img = sensor.snapshot().histeq(adaptive=True, clip_limit=3) + + print(clock.fps()) diff --git a/usr/examples/04-Image-Filters/histogram_equalization.py b/usr/examples/04-Image-Filters/histogram_equalization.py new file mode 100644 index 000000000..2a3aece50 --- /dev/null +++ b/usr/examples/04-Image-Filters/histogram_equalization.py @@ -0,0 +1,19 @@ +# Histogram Equalization +# +# This example shows off how to use histogram equalization to improve +# the contrast in the image. + +import sensor, image, time + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) +sensor.set_framesize(sensor.QQVGA) +sensor.skip_frames(time = 2000) +clock = time.clock() + +while(True): + clock.tick() + + img = sensor.snapshot().histeq() + + print(clock.fps()) From 6436eb15dc9fadcd870b86f7eed949ec1e590af4 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 1 Apr 2018 15:54:43 -0400 Subject: [PATCH 2/5] Normalize bilteral filter sigma values. Its easy to pick sigma now and it works great. Features get nice and smooth. --- src/omv/img/filter.c | 28 ++++++++++++------- src/omv/py/py_image.c | 4 +-- .../color_bilateral_filter.py | 2 +- .../grayscale_bilateral_filter.py | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/omv/img/filter.c b/src/omv/img/filter.c index a3ad8efbe..aa3de9a89 100644 --- a/src/omv/img/filter.c +++ b/src/omv/img/filter.c @@ -1122,9 +1122,9 @@ void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, c } } -static float gaussian(int x, float sigma) +static float gaussian(float x, float sigma) { - return fast_expf((x * x) / (-2.0f * sigma * sigma)) / (sigma * 2.506628f); // sqrt(2 * PI) + return fast_expf((x * x) / (-2.0f * sigma * sigma)) / (fabsf(sigma) * 2.506628f); // sqrt(2 * PI) } static float distance(int x, int y) @@ -1146,16 +1146,18 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl float *gi_lut = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(float)); + float max_color = IM_DIV(1.0f, COLOR_BINARY_MAX - COLOR_BINARY_MIN); for (int i = COLOR_BINARY_MIN; i <= COLOR_BINARY_MAX; i++) { - gi_lut[i] = gaussian(i, color_sigma); + gi_lut[i] = gaussian(i * max_color, color_sigma); } int n = (ksize * 2) + 1; float *gs_lut = fb_alloc(n * n * sizeof(float)); + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); for (int y = -ksize; y <= ksize; y++) { for (int x = -ksize; x <= ksize; x++) { - gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); } } @@ -1222,16 +1224,18 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl float *gi_lut = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(float)); + float max_color = IM_DIV(1.0f, COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN); for (int i = COLOR_GRAYSCALE_MIN; i <= COLOR_GRAYSCALE_MAX; i++) { - gi_lut[i] = gaussian(i, color_sigma); + gi_lut[i] = gaussian(i * max_color, color_sigma); } int n = (ksize * 2) + 1; float *gs_lut = fb_alloc(n * n * sizeof(float)); + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); for (int y = -ksize; y <= ksize; y++) { for (int x = -ksize; x <= ksize; x++) { - gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); } } @@ -1300,24 +1304,28 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl float *g_gi_lut = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(float)); float *b_gi_lut = fb_alloc((COLOR_B5_MAX - COLOR_B5_MIN + 1) * sizeof(float)); + float r_max_color = IM_DIV(1.0f, COLOR_R5_MAX - COLOR_R5_MIN); for (int i = COLOR_R5_MIN; i <= COLOR_R5_MAX; i++) { - r_gi_lut[i] = gaussian(i, color_sigma); + r_gi_lut[i] = gaussian(i * r_max_color, color_sigma); } + float g_max_color = IM_DIV(1.0f, COLOR_G6_MAX - COLOR_G6_MIN); for (int i = COLOR_G6_MIN; i <= COLOR_G6_MAX; i++) { - g_gi_lut[i] = gaussian(i, color_sigma); + g_gi_lut[i] = gaussian(i * g_max_color, color_sigma); } + float b_max_color = IM_DIV(1.0f, COLOR_B5_MAX - COLOR_B5_MIN); for (int i = COLOR_B5_MIN; i <= COLOR_B5_MAX; i++) { - b_gi_lut[i] = gaussian(i, color_sigma); + b_gi_lut[i] = gaussian(i * b_max_color, color_sigma); } int n = (ksize * 2) + 1; float *gs_lut = fb_alloc(n * n * sizeof(float)); + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); for (int y = -ksize; y <= ksize; y++) { for (int x = -ksize; x <= ksize; x++) { - gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); } } diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index a7cac73c0..5d5d0d731 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1997,10 +1997,10 @@ STATIC mp_obj_t py_image_bilateral(uint n_args, const mp_obj_t *args, mp_map_t * int arg_ksize = py_helper_arg_to_ksize(args[1]); float arg_color_sigma = - py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_sigma), 6); + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_sigma), 0.1); PY_ASSERT_TRUE_MSG((0 <= arg_color_sigma), "Error: 0 <= color_sigma!"); float arg_space_sigma = - py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_space_sigma), 6); + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_space_sigma), 1); PY_ASSERT_TRUE_MSG((0 <= arg_space_sigma), "Error: 0 <= space_sigma!"); bool arg_threshold = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); diff --git a/usr/examples/04-Image-Filters/color_bilateral_filter.py b/usr/examples/04-Image-Filters/color_bilateral_filter.py index 1e1c24e6c..1bdbbb7eb 100644 --- a/usr/examples/04-Image-Filters/color_bilateral_filter.py +++ b/usr/examples/04-Image-Filters/color_bilateral_filter.py @@ -23,7 +23,7 @@ while(True): # A larger value is less strict. # Run the kernel on every pixel of the image. - img.bilateral(3, color_sigma=5, space_sigma=5) + img.bilateral(3, color_sigma=0.1, space_sigma=1) # Note that the bilateral filter can introduce image defects if you set # color_sigma/space_sigma to aggresively. Increase the sigma values until diff --git a/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py b/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py index 35a3f9427..6b3a67b21 100644 --- a/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py +++ b/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py @@ -23,7 +23,7 @@ while(True): # A larger value is less strict. # Run the kernel on every pixel of the image. - img.bilateral(3, color_sigma=20, space_sigma=20) + img.bilateral(3, color_sigma=0.1, space_sigma=1) # Note that the bilateral filter can introduce image defects if you set # color_sigma/space_sigma to aggresively. Increase the sigma values until From 311607f02bea43faabd3cfde09e55e3ce0b713a1 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 1 Apr 2018 16:55:42 -0400 Subject: [PATCH 3/5] Add masking support to binary. --- src/omv/img/binary.c | 80 +++++++++++++++++++++--------------- src/omv/img/edge.c | 2 +- src/omv/img/imlib.h | 2 +- src/omv/img/shadow_removal.c | 2 +- src/omv/py/py_image.c | 4 +- src/omv/py/qstrdefsomv.h | 3 +- 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/omv/img/binary.c b/src/omv/img/binary.c index 37163b90c..d467c2e42 100644 --- a/src/omv/img/binary.c +++ b/src/omv/img/binary.c @@ -5,7 +5,7 @@ #include "imlib.h" -void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero) +void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero, image_t *mask) { for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { color_thresholds_list_lnk_data_t lnk_data; @@ -14,22 +14,22 @@ void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero) switch(img->bpp) { case IMAGE_BPP_BINARY: { if (!zero) { - for (uint32_t *start = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - for (int i = 0; i < UINT32_T_BITS; i++) { - IMAGE_PUT_BINARY_PIXEL_FAST(start, i, - COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(start, i), &lnk_data, invert) - ? COLOR_BINARY_MAX : COLOR_BINARY_MIN); + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, + COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), &lnk_data, invert)); } } } else { - for (uint32_t *start = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - for (int i = 0; i < UINT32_T_BITS; i++) { - if (COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(start, i), &lnk_data, invert)) - IMAGE_PUT_BINARY_PIXEL_FAST(start, i, COLOR_BINARY_MIN); + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + if (COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + IMAGE_CLEAR_BINARY_PIXEL_FAST(row_ptr, x); + } } } } @@ -37,36 +37,48 @@ void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero) } case IMAGE_BPP_GRAYSCALE: { if (!zero) { - for (uint8_t *start = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - *start = COLOR_THRESHOLD_GRAYSCALE(*start, &lnk_data, invert) - ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN; + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), &lnk_data, invert) + ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + } } } else { - for (uint8_t *start = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - if (COLOR_THRESHOLD_GRAYSCALE(*start, &lnk_data, invert)) - *start = COLOR_GRAYSCALE_BINARY_MIN; + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + if (COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, COLOR_GRAYSCALE_BINARY_MIN); + } + } } } break; } case IMAGE_BPP_RGB565: { if (!zero) { - for (uint16_t *start = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - *start = COLOR_THRESHOLD_RGB565(*start, &lnk_data, invert) - ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN; + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, + COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), &lnk_data, invert) + ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN); + } } } else { - for (uint16_t *start = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - if (COLOR_THRESHOLD_RGB565(*start, &lnk_data, invert)) - *start = COLOR_RGB565_BINARY_MIN; + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + if (COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, COLOR_RGB565_BINARY_MIN); + } + } } } break; diff --git a/src/omv/img/edge.c b/src/omv/img/edge.c index 6e944d900..914927210 100644 --- a/src/omv/img/edge.c +++ b/src/omv/img/edge.c @@ -26,7 +26,7 @@ void imlib_edge_simple(image_t *src, rectangle_t *roi, int low_thresh, int high_ lnk_data.LMin=low_thresh; lnk_data.LMax=high_thresh; list_push_back(&thresholds, &lnk_data); - imlib_binary(src, &thresholds, false, false); + imlib_binary(src, &thresholds, false, false, NULL); list_free(&thresholds); imlib_erode(src, 1, 2, NULL); } diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index e11f76b4f..94c0fe844 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1218,7 +1218,7 @@ void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, i void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill); void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, int scale, int x_spacing, int y_spacing); // Binary Functions -void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero); +void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero, image_t *mask); void imlib_invert(image_t *img); void imlib_b_and(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); void imlib_b_nand(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); diff --git a/src/omv/img/shadow_removal.c b/src/omv/img/shadow_removal.c index 17aac2110..ef3957880 100644 --- a/src/omv/img/shadow_removal.c +++ b/src/omv/img/shadow_removal.c @@ -228,7 +228,7 @@ void imlib_remove_shadows(image_t *img, const char *path, image_t *other, int sc lnk_data.AMax = COLOR_A_MAX; lnk_data.BMax = COLOR_B_MAX; list_push_back(&thresholds, &lnk_data); - imlib_binary(&temp_image, &thresholds, false, false); + imlib_binary(&temp_image, &thresholds, false, false, NULL); list_free(&thresholds); imlib_erode(&temp_image, 3, 30, NULL); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 5d5d0d731..0a6dcf345 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1199,9 +1199,11 @@ STATIC mp_obj_t py_image_binary(uint n_args, const mp_obj_t *args, mp_map_t *kw_ py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); bool arg_zero = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_zero), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 4, kw_args); fb_alloc_mark(); - imlib_binary(py_helper_arg_to_image_mutable(args[0]), &arg_thresholds, arg_invert, arg_zero); + imlib_binary(py_helper_arg_to_image_mutable(args[0]), &arg_thresholds, arg_invert, arg_zero, arg_msk); fb_alloc_free_till_mark(); list_free(&arg_thresholds); return args[0]; diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 39c839da1..261c33da4 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -364,6 +364,7 @@ Q(draw_keypoints) Q(binary) Q(invert) Q(zero) +Q(mask) // Invert // duplicate Q(invert) @@ -371,7 +372,7 @@ Q(zero) // And Q(and) Q(b_and) -Q(mask) +// duplicate Q(mask) // Nand Q(nand) From c4e40f9a3692bbb08b9407b0692c38a0423c034c Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 1 Apr 2018 16:56:09 -0400 Subject: [PATCH 4/5] Fix image pixel access methods to be safer. --- src/omv/img/filter.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/omv/img/filter.c b/src/omv/img/filter.c index aa3de9a89..3af2a6e26 100644 --- a/src/omv/img/filter.c +++ b/src/omv/img/filter.c @@ -14,11 +14,10 @@ void imlib_histeq(image_t *img, image_t *mask) float s = (COLOR_BINARY_MAX - COLOR_BINARY_MIN) / ((float) a); uint32_t *hist = fb_alloc0((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(uint32_t)); - for (uint32_t *start = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - for (int i = 0; i < UINT32_T_BITS; i++) { - hist[IMAGE_GET_BINARY_PIXEL_FAST(start, i) - COLOR_BINARY_MIN] += 1; + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) - COLOR_BINARY_MIN] += 1; } } @@ -45,10 +44,11 @@ void imlib_histeq(image_t *img, image_t *mask) float s = (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / ((float) a); uint32_t *hist = fb_alloc0((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(uint32_t)); - for (uint8_t *start = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - hist[(*start) - COLOR_GRAYSCALE_MIN] += 1; + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) - COLOR_GRAYSCALE_MIN] += 1; + } } for (int i = 0, sum = 0, ii = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; i < ii; i++) { @@ -74,10 +74,11 @@ void imlib_histeq(image_t *img, image_t *mask) float s = (COLOR_Y_MAX - COLOR_Y_MIN) / ((float) a); uint32_t *hist = fb_alloc0((COLOR_Y_MAX - COLOR_Y_MIN + 1) * sizeof(uint32_t)); - for (uint16_t *start = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, 0), - *end = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, img->h); - start < end; start++) { - hist[COLOR_RGB565_TO_Y(*start) - COLOR_Y_MIN] += 1; + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)) - COLOR_Y_MIN] += 1; + } } for (int i = 0, sum = 0, ii = COLOR_Y_MAX - COLOR_Y_MIN + 1; i < ii; i++) { @@ -1143,7 +1144,6 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl switch(img->bpp) { case IMAGE_BPP_BINARY: { buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); - float *gi_lut = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(float)); float max_color = IM_DIV(1.0f, COLOR_BINARY_MAX - COLOR_BINARY_MIN); @@ -1221,7 +1221,6 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl } case IMAGE_BPP_GRAYSCALE: { buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows); - float *gi_lut = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(float)); float max_color = IM_DIV(1.0f, COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN); @@ -1299,7 +1298,6 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl } case IMAGE_BPP_RGB565: { buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows); - float *r_gi_lut = fb_alloc((COLOR_R5_MAX - COLOR_R5_MIN + 1) * sizeof(float)); float *g_gi_lut = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(float)); float *b_gi_lut = fb_alloc((COLOR_B5_MAX - COLOR_B5_MIN + 1) * sizeof(float)); From 7bd8fe45fd7e8ce55076cb5268a705b8097d70d4 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 1 Apr 2018 17:15:28 -0400 Subject: [PATCH 5/5] Add masking support to replace. --- src/omv/img/imlib.h | 2 +- src/omv/img/mathop.c | 44 +++++++++++++++++++++++++--------------- src/omv/py/py_image.c | 8 +++++--- src/omv/py/qstrdefsomv.h | 1 + 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 94c0fe844..c9ed1c411 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1234,7 +1234,7 @@ void imlib_top_hat(image_t *img, int ksize, int threshold, image_t *mask); void imlib_black_hat(image_t *img, int ksize, int threshold, image_t *mask); // Math Functions void imlib_negate(image_t *img); -void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip); +void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip, image_t *mask); void imlib_add(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); void imlib_sub(image_t *img, const char *path, image_t *other, int scalar, bool reverse, image_t *mask); void imlib_mul(image_t *img, const char *path, image_t *other, int scalar, bool invert, image_t *mask); diff --git a/src/omv/img/mathop.c b/src/omv/img/mathop.c index b2045d027..a41f7e33c 100644 --- a/src/omv/img/mathop.c +++ b/src/omv/img/mathop.c @@ -51,41 +51,52 @@ void imlib_negate(image_t *img) typedef struct imlib_replace_line_op_state { bool hmirror, vflip; + image_t *mask; } imlib_replace_line_op_state_t; static void imlib_replace_line_op(image_t *img, int line, void *other, void *data, bool vflipped) { bool hmirror = ((imlib_replace_line_op_state_t *) data)->hmirror; bool vflip = ((imlib_replace_line_op_state_t *) data)->vflip; + image_t *mask = ((imlib_replace_line_op_state_t *) data)->mask; switch(img->bpp) { case IMAGE_BPP_BINARY: { - uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, - vflip ? (img->h - line - 1) : line); + int v_line = vflip ? (img->h - line - 1) : line; + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, v_line); for (int i = 0, j = img->w; i < j; i++) { - int pixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), - hmirror ? (img->w - i - 1) : i); - IMAGE_PUT_BINARY_PIXEL_FAST(data, i, pixel); + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), h_i); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, pixel); + } } break; } case IMAGE_BPP_GRAYSCALE: { - uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, - vflip ? (img->h - line - 1) : line); + int v_line = vflip ? (img->h - line - 1) : line; + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, v_line); for (int i = 0, j = img->w; i < j; i++) { - int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), - hmirror ? (img->w - i - 1) : i); - IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, pixel); + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), h_i); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, pixel); + } } break; } case IMAGE_BPP_RGB565: { - uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, - vflip ? (img->h - line - 1) : line); + int v_line = vflip ? (img->h - line - 1) : line; + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, v_line); for (int i = 0, j = img->w; i < j; i++) { - int pixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), - hmirror ? (img->w - i - 1) : i); - IMAGE_PUT_RGB565_PIXEL_FAST(data, i, pixel); + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), h_i); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, pixel); + } } break; } @@ -95,11 +106,12 @@ static void imlib_replace_line_op(image_t *img, int line, void *other, void *dat } } -void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip) +void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip, image_t *mask) { imlib_replace_line_op_state_t state; state.hmirror = hmirror; state.vflip = vflip; + state.mask = mask; imlib_image_operation(img, path, other, scalar, imlib_replace_line_op, &state); } diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 0a6dcf345..ec3654f2a 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1488,17 +1488,19 @@ STATIC mp_obj_t py_image_replace(uint n_args, const mp_obj_t *args, mp_map_t *kw py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hmirror), false); bool arg_vflip = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_vflip), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 4, kw_args); fb_alloc_mark(); if (MP_OBJ_IS_STR(args[1])) { - imlib_replace(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_hmirror, arg_vflip); + imlib_replace(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_hmirror, arg_vflip, arg_msk); } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { - imlib_replace(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_hmirror, arg_vflip); + imlib_replace(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_hmirror, arg_vflip, arg_msk); } else { imlib_replace(arg_img, NULL, NULL, py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), - arg_hmirror, arg_vflip); + arg_hmirror, arg_vflip, arg_msk); } fb_alloc_free_till_mark(); diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 261c33da4..d333f4199 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -436,6 +436,7 @@ Q(negate) Q(replace) Q(hmirror) Q(vflip) +// duplicate Q(mask) // Add Op Q(add)