!7 !include once #1010056 // OpenIMAJ import org.openimaj.image.Image; import org.openimaj.image.processor.*; import static org.openimaj.image.processor.SinglebandImageProcessor.Processable; replace Rectangle with RectangleO. abstract sclass SingleBandImage, I extends SingleBandImage> extends Image implements Processable, org.openimaj.image.processor.SinglebandKernelProcessor.Processable { private static final long serialVersionUID = 1L; public int height; public int width; public SingleBandImage() { } public int getHeight() { return this.height; } public int getWidth() { return this.width; } public I process(KernelProcessor p) { return this.process(p, false); } public I process(KernelProcessor p, boolean pad) { I newImage = (I) this.newInstance(this.width, this.height); I tmp = (I) this.newInstance(p.getKernelWidth(), p.getKernelHeight()); int hh = p.getKernelHeight() / 2; int hw = p.getKernelWidth() / 2; int y; int x; if (!pad) { for(y = hh; y < this.getHeight() - (p.getKernelHeight() - hh); ++y) { for(x = hw; x < this.getWidth() - (p.getKernelWidth() - hw); ++x) { newImage.setPixel(x, y, p.processKernel(this.extractROI(x, y, tmp))); } } } else { for(y = 0; y < this.getHeight(); ++y) { for(x = 0; x < this.getWidth(); ++x) { newImage.setPixel(x, y, p.processKernel(this.extractROI(x, y, tmp))); } } } return newImage; } public I process(SinglebandKernelProcessor p) { return this.process(p, false); } public I process(SinglebandKernelProcessor p, boolean pad) { I newImage = (SingleBandImage)this.newInstance(this.width, this.height); I tmp = (SingleBandImage)this.newInstance(p.getKernelWidth(), p.getKernelHeight()); int hh = p.getKernelHeight() / 2; int hw = p.getKernelWidth() / 2; int y; int x; if (!pad) { for(y = hh; y < this.getHeight() - (p.getKernelHeight() - hh); ++y) { for(x = hw; x < this.getWidth() - (p.getKernelWidth() - hw); ++x) { newImage.setPixel(x, y, p.processKernel(this.extractROI(x, y, tmp))); } } } else { for(y = 0; y < this.getHeight(); ++y) { for(x = 0; x < this.getWidth(); ++x) { newImage.setPixel(x, y, p.processKernel(this.extractROI(x, y, tmp))); } } } return newImage; } public I processInplace(SinglebandKernelProcessor p) { return this.processInplace(p, false); } public I processInplace(SinglebandKernelProcessor p, boolean pad) { I newImage = this.process(p, pad); this.internalAssign(newImage); return this; } public I process(SinglebandImageProcessor p) { I newImage = this.clone(); newImage.processInplace(p); return newImage; } public I processInplace(SinglebandImageProcessor p) { p.processImage(this); return this; } public I fill(Q colour) { for(int y = 0; y < this.getHeight(); ++y) { for(int x = 0; x < this.getWidth(); ++x) { this.setPixel(x, y, colour); } } return this; } public abstract I clone(); public boolean equals(Object obj) { I that = (SingleBandImage)obj; for(int y = 0; y < this.getHeight(); ++y) { for(int x = 0; x < this.getWidth(); ++x) { boolean fail = !((Comparable)this.getPixel(x, y)).equals(that.getPixel(x, y)); if (fail) { return false; } } } return true; } } sclass FImage extends SingleBandImage { private static final long serialVersionUID = 1L; protected static Logger logger = Logger.getLogger(FImage.class); protected static final float DEFAULT_GAUSS_TRUNCATE = 4.0F; public float[][] pixels; public FImage(float[] array, int width, int height) { assert array.length == width * height; this.pixels = new float[height][width]; this.height = height; this.width = width; for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { this.pixels[y][x] = array[y * width + x]; } } } public FImage(double[] array, int width, int height) { assert array.length == width * height; this.pixels = new float[height][width]; this.height = height; this.width = width; for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { this.pixels[y][x] = (float)array[y * width + x]; } } } public FImage(double[] array, int width, int height, int offset) { assert array.length == width * height; this.pixels = new float[height][width]; this.height = height; this.width = width; for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { this.pixels[y][x] = (float)array[offset + y * width + x]; } } } public FImage(float[][] array) { this.pixels = array; this.height = array.length; this.width = array[0].length; } public FImage(int width, int height) { this.pixels = new float[height][width]; this.height = height; this.width = width; } public FImage(int[] data, int width, int height) { this.internalAssign(data, width, height); } public FImage(int[] data, int width, int height, ARGBPlane plane) { this.width = width; this.height = height; this.pixels = new float[height][width]; for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { int rgb = data[x + y * width]; int colour = 0; switch(plane) { case RED: colour = rgb >> 16 & 255; break; case GREEN: colour = rgb >> 8 & 255; break; case BLUE: colour = rgb & 255; } this.pixels[y][x] = (float)colour; } } } public FImage abs() { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] = Math.abs(this.pixels[r][c]); } } return this; } public FImage add(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { FImage newImage = new FImage(im.width, im.height); for(int r = 0; r < im.height; ++r) { for(int c = 0; c < im.width; ++c) { newImage.pixels[r][c] = this.pixels[r][c] + im.pixels[r][c]; } } return newImage; } } public FImage add(Float num) { FImage newImage = new FImage(this.width, this.height); float fnum = num; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { newImage.pixels[r][c] = this.pixels[r][c] + fnum; } } return newImage; } public FImage add(Image im) { if (im instanceof FImage) { return this.add((FImage)im); } else { throw new UnsupportedOperationException("Unsupported Type"); } } public FImage addInplace(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { for(int r = 0; r < im.height; ++r) { for(int c = 0; c < im.width; ++c) { this.pixels[r][c] += im.pixels[r][c]; } } return this; } } public FImage addInplace(Float num) { float fnum = num; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] += fnum; } } return this; } public FImage addInplace(Image im) { if (im instanceof FImage) { return this.addInplace((FImage)im); } else { throw new UnsupportedOperationException("Unsupported Type"); } } public FImage clip(Float min, Float max) { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (this.pixels[r][c] < min) { this.pixels[r][c] = 0.0F; } if (this.pixels[r][c] > max) { this.pixels[r][c] = 1.0F; } } } return this; } public FImage clipMax(Float thresh) { float fthresh = thresh; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (this.pixels[r][c] > fthresh) { this.pixels[r][c] = 1.0F; } } } return this; } public FImage clipMin(Float thresh) { float fthresh = thresh; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (this.pixels[r][c] < fthresh) { this.pixels[r][c] = 0.0F; } } } return this; } public FImage clone() { FImage cpy = new FImage(this.width, this.height); for(int r = 0; r < this.height; ++r) { System.arraycopy(this.pixels[r], 0, cpy.pixels[r], 0, this.width); } return cpy; } public FImageRenderer createRenderer() { return new FImageRenderer(this); } public FImageRenderer createRenderer(RenderHints options) { return new FImageRenderer(this, options); } public FImage divide(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { FImage newImage = new FImage(im.width, im.height); for(int r = 0; r < im.height; ++r) { for(int c = 0; c < im.width; ++c) { newImage.pixels[r][c] = this.pixels[r][c] / im.pixels[r][c]; } } return newImage; } } public FImage divideInplace(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { this.pixels[y][x] /= im.pixels[y][x]; } } return this; } } public FImage divideInplace(Float val) { float fval = val; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { this.pixels[y][x] /= fval; } } return this; } public FImage divideInplace(float fval) { for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { this.pixels[y][x] /= fval; } } return this; } public FImage divideInplace(Image im) { if (im instanceof FImage) { return this.divideInplace((FImage)im); } else { throw new UnsupportedOperationException("Unsupported Type"); } } public FImage extractROI(int x, int y, FImage out) { int r = y; for(int rr = 0; rr < out.height; ++rr) { int c = x; for(int cc = 0; cc < out.width; ++cc) { if (r >= 0 && r < this.height && c >= 0 && c < this.width) { out.pixels[rr][cc] = this.pixels[r][c]; } else { out.pixels[rr][cc] = 0.0F; } ++c; } ++r; } return out; } public FImage extractROI(int x, int y, int w, int h) { FImage out = new FImage(w, h); int r = y; for(int rr = 0; rr < h; ++rr) { int c = x; for(int cc = 0; cc < w; ++cc) { if (r >= 0 && r < this.height && c >= 0 && c < this.width) { out.pixels[rr][cc] = this.pixels[r][c]; } else { out.pixels[rr][cc] = 0.0F; } ++c; } ++r; } return out; } public FImage fill(Float colour) { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] = colour; } } return this; } public FImage fill(float colour) { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] = colour; } } return this; } public Rectangle getContentArea() { int minc = this.width; int maxc = 0; int minr = this.height; int maxr = 0; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (this.pixels[r][c] > 0.0F) { if (c < minc) { minc = c; } if (c > maxc) { maxc = c; } if (r < minr) { minr = r; } if (r > maxr) { maxr = r; } } } } return new Rectangle((float)minc, (float)minr, (float)(maxc - minc + 1), (float)(maxr - minr + 1)); } public double[] getDoublePixelVector() { double[] f = new double[this.height * this.width]; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { f[x + y * this.width] = (double)this.pixels[y][x]; } } return f; } public FImage getField(Field f) { FImage img = new FImage(this.width, this.height / 2); int init = f.equals(Field.ODD) ? 1 : 0; int r = init; for(int r2 = 0; r < this.height && r2 < this.height / 2; ++r2) { for(int c = 0; c < this.width; ++c) { img.pixels[r2][c] = this.pixels[r][c]; } r += 2; } return img; } public FImage getFieldCopy(Field f) { FImage img = new FImage(this.width, this.height); for(int r = 0; r < this.height; r += 2) { for(int c = 0; c < this.width; ++c) { if (f.equals(Field.EVEN)) { img.pixels[r][c] = this.pixels[r][c]; img.pixels[r + 1][c] = this.pixels[r][c]; } else { img.pixels[r][c] = this.pixels[r + 1][c]; img.pixels[r + 1][c] = this.pixels[r + 1][c]; } } } return img; } public FImage getFieldInterpolate(Field f) { FImage img = new FImage(this.width, this.height); for(int r = 0; r < this.height; r += 2) { for(int c = 0; c < this.width; ++c) { if (f.equals(Field.EVEN)) { img.pixels[r][c] = this.pixels[r][c]; if (r + 2 == this.height) { img.pixels[r + 1][c] = this.pixels[r][c]; } else { img.pixels[r + 1][c] = 0.5F * (this.pixels[r][c] + this.pixels[r + 2][c]); } } else { img.pixels[r + 1][c] = this.pixels[r + 1][c]; if (r == 0) { img.pixels[r][c] = this.pixels[r + 1][c]; } else { img.pixels[r][c] = 0.5F * (this.pixels[r - 1][c] + this.pixels[r + 1][c]); } } } } return img; } public float[] getFloatPixelVector() { float[] f = new float[this.height * this.width]; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { f[x + y * this.width] = this.pixels[y][x]; } } return f; } public Float getPixel(int x, int y) { return this.pixels[y][x]; } public Comparator getPixelComparator() { return new Comparator() { public int compare(Float o1, Float o2) { return o1.compareTo(o2); } }; } public Float getPixelInterp(double x, double y) { int x0 = (int)Math.floor(x); int x1 = x0 + 1; int y0 = (int)Math.floor(y); int y1 = y0 + 1; if (x0 < 0) { x0 = 0; } if (x0 >= this.width) { x0 = this.width - 1; } if (y0 < 0) { y0 = 0; } if (y0 >= this.height) { y0 = this.height - 1; } if (x1 < 0) { x1 = 0; } if (x1 >= this.width) { x1 = this.width - 1; } if (y1 < 0) { y1 = 0; } if (y1 >= this.height) { y1 = this.height - 1; } float f00 = this.pixels[y0][x0]; float f01 = this.pixels[y1][x0]; float f10 = this.pixels[y0][x1]; float f11 = this.pixels[y1][x1]; float dx = (float)(x - (double)x0); float dy = (float)(y - (double)y0); if (dx < 0.0F) { ++dx; } if (dy < 0.0F) { ++dy; } return Interpolation.bilerp(dx, dy, f00, f01, f10, f11); } public Float getPixelInterp(double x, double y, Float background) { int x0 = (int)Math.floor(x); int x1 = x0 + 1; int y0 = (int)Math.floor(y); int y1 = y0 + 1; boolean ty1 = true; boolean tx1 = true; boolean ty0 = true; boolean tx0 = true; if (x0 < 0) { tx0 = false; } if (x0 >= this.width) { tx0 = false; } if (y0 < 0) { ty0 = false; } if (y0 >= this.height) { ty0 = false; } if (x1 < 0) { tx1 = false; } if (x1 >= this.width) { tx1 = false; } if (y1 < 0) { ty1 = false; } if (y1 >= this.height) { ty1 = false; } double f00 = (double)(ty0 && tx0 ? this.pixels[y0][x0] : background); double f01 = (double)(ty1 && tx0 ? this.pixels[y1][x0] : background); double f10 = (double)(ty0 && tx1 ? this.pixels[y0][x1] : background); double f11 = (double)(ty1 && tx1 ? this.pixels[y1][x1] : background); double dx = x - (double)x0; double dy = y - (double)y0; if (dx < 0.0D) { ++dx; } if (dy < 0.0D) { ++dy; } double interpVal = Interpolation.bilerp(dx, dy, f00, f01, f10, f11); return (float)interpVal; } public float getPixelInterpNative(float x, float y, float background) { int x0 = (int)Math.floor((double)x); int x1 = x0 + 1; int y0 = (int)Math.floor((double)y); int y1 = y0 + 1; boolean ty1 = true; boolean tx1 = true; boolean ty0 = true; boolean tx0 = true; if (x0 < 0) { tx0 = false; } if (x0 >= this.width) { tx0 = false; } if (y0 < 0) { ty0 = false; } if (y0 >= this.height) { ty0 = false; } if (x1 < 0) { tx1 = false; } if (x1 >= this.width) { tx1 = false; } if (y1 < 0) { ty1 = false; } if (y1 >= this.height) { ty1 = false; } float f00 = ty0 && tx0 ? this.pixels[y0][x0] : background; float f01 = ty1 && tx0 ? this.pixels[y1][x0] : background; float f10 = ty0 && tx1 ? this.pixels[y0][x1] : background; float f11 = ty1 && tx1 ? this.pixels[y1][x1] : background; float dx = x - (float)x0; float dy = y - (float)y0; if (dx < 0.0F) { ++dx; } if (dy < 0.0F) { ++dy; } float interpVal = Interpolation.bilerpf(dx, dy, f00, f01, f10, f11); return interpVal; } public FImage internalCopy(FImage im) { int h = im.height; int w = im.width; float[][] impixels = im.pixels; for(int r = 0; r < h; ++r) { System.arraycopy(impixels[r], 0, this.pixels[r], 0, w); } return this; } public FImage internalAssign(FImage im) { this.pixels = im.pixels; this.height = im.height; this.width = im.width; return this; } public FImage internalAssign(int[] data, int width, int height) { if (this.height != height || this.width != width) { this.height = height; this.width = width; this.pixels = new float[height][width]; } for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { int rgb = data[x + width * y]; int red = rgb >> 16 & 255; int green = rgb >> 8 & 255; int blue = rgb & 255; float fpix = 0.299F * (float)red + 0.587F * (float)green + 0.114F * (float)blue; this.pixels[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[(int)fpix]; } } return this; } public FImage inverse() { float max = this.max(); for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] = max - this.pixels[r][c]; } } return this; } public Float max() { float max = 1.4E-45F; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (max < this.pixels[r][c]) { max = this.pixels[r][c]; } } } return max; } public FValuePixel maxPixel() { FValuePixel max = new FValuePixel(-1, -1); max.value = -3.4028235E38F; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { if (max.value < this.pixels[y][x]) { max.value = this.pixels[y][x]; max.x = x; max.y = y; } } } return max; } public Float min() { float min = 3.4028235E38F; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (min > this.pixels[r][c]) { min = this.pixels[r][c]; } } } return min; } public FValuePixel minPixel() { FValuePixel min = new FValuePixel(-1, -1); min.value = 3.4028235E38F; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { if (min.value > this.pixels[y][x]) { min.value = this.pixels[y][x]; min.x = x; min.y = y; } } } return min; } public FImage multiply(Float num) { return (FImage)super.multiply(num); } public FImage multiplyInplace(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] *= im.pixels[r][c]; } } return this; } } public FImage multiplyInplace(Float num) { float fnum = num; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] *= fnum; } } return this; } public FImage multiplyInplace(float fnum) { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] *= fnum; } } return this; } public FImage multiplyInplace(Image im) { if (im instanceof FImage) { return this.multiplyInplace((FImage)im); } else { throw new UnsupportedOperationException("Unsupported Type"); } } public FImage newInstance(int width, int height) { return new FImage(width, height); } public FImage normalise() { float min = this.min(); float max = this.max(); if (max == min) { return this; } else { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] = (this.pixels[r][c] - min) / (max - min); } } return this; } } public FImage process(KernelProcessor p) { return this.process(p, false); } public FImage process(KernelProcessor p, boolean pad) { FImage newImage = new FImage(this.width, this.height); int kh = p.getKernelHeight(); int kw = p.getKernelWidth(); FImage tmp = new FImage(kw, kh); int hh = kh / 2; int hw = kw / 2; int y; int x; if (!pad) { for(y = hh; y < this.height - (kh - hh); ++y) { for(x = hw; x < this.width - (kw - hw); ++x) { newImage.pixels[y][x] = (Float)p.processKernel(this.extractROI(x - hw, y - hh, tmp)); } } } else { for(y = 0; y < this.height; ++y) { for(x = 0; x < this.width; ++x) { newImage.pixels[y][x] = (Float)p.processKernel(this.extractROI(x - hw, y - hh, tmp)); } } } return newImage; } public FImage processInplace(PixelProcessor p) { for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { this.pixels[y][x] = (Float)p.processPixel(this.pixels[y][x]); } } return this; } public void analyseWith(PixelAnalyser p) { p.reset(); for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { p.analysePixel(this.pixels[y][x]); } } } public void setPixel(int x, int y, Float val) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { this.pixels[y][x] = val; } } public FImage subtract(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { FImage newImage = new FImage(im.width, im.height); for(int r = 0; r < im.height; ++r) { for(int c = 0; c < im.width; ++c) { newImage.pixels[r][c] = this.pixels[r][c] - im.pixels[r][c]; } } return newImage; } } public FImage subtract(Float num) { FImage newImage = new FImage(this.width, this.height); for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { newImage.pixels[r][c] = this.pixels[r][c] - num; } } return newImage; } public FImage subtract(Image input) { if (input instanceof FImage) { return this.subtract((FImage)input); } else { throw new UnsupportedOperationException("Unsupported Type"); } } public FImage subtractInplace(FImage im) { if (!ImageUtilities.checkSameSize(new Image[]{this, im})) { throw new AssertionError("images must be the same size"); } else { float[][] pix1 = this.pixels; float[][] pix2 = im.pixels; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { pix1[r][c] -= pix2[r][c]; } } return this; } } public FImage subtractInplace(Float num) { float fnum = num; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] -= fnum; } } return this; } public FImage subtractInplace(Image im) { if (im instanceof FImage) { return this.subtractInplace((FImage)im); } else { throw new UnsupportedOperationException("Unsupported Type"); } } public FImage threshold(Float thresh) { float fthresh = thresh; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (this.pixels[r][c] <= fthresh) { this.pixels[r][c] = 0.0F; } else { this.pixels[r][c] = 1.0F; } } } return this; } public byte[] toByteImage() { byte[] pgmData = new byte[this.height * this.width]; for(int j = 0; j < this.height; ++j) { for(int i = 0; i < this.width; ++i) { int v = (int)(255.0F * this.pixels[j][i]); v = Math.max(0, Math.min(255, v)); pgmData[i + j * this.width] = (byte)(v & 255); } } return pgmData; } public int[] toPackedARGBPixels() { int[] bimg = new int[this.width * this.height]; for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { int v = Math.max(0, Math.min(255, (int)(this.pixels[r][c] * 255.0F))); int rgb = -16777216 | v << 16 | v << 8 | v; bimg[c + this.width * r] = rgb; } } return bimg; } public String toString() { String imageString = ""; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { imageString = imageString + String.format("%+.3f ", this.pixels[y][x]); if (x == 16 && this.width - 16 > x) { imageString = imageString + "... "; x = this.width - 16; } } imageString = imageString + "\n"; if (y == 16 && this.height - 16 > y) { y = this.height - 16; imageString = imageString + "... \n"; } } return imageString; } public String toString(String format) { String imageString = ""; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < this.width; ++x) { imageString = imageString + String.format(format, this.pixels[y][x]); } imageString = imageString + "\n"; } return imageString; } public FImage transform(Matrix transform) { return (FImage)super.transform(transform); } public FImage zero() { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { this.pixels[r][c] = 0.0F; } } return this; } public boolean equals(Object o) { return !(o instanceof FImage) ? false : this.equalsThresh((FImage)o, 0.0F); } public boolean equalsThresh(FImage o, float thresh) { FImage that = o; if (o.height == this.height && o.width == this.width) { for(int i = 0; i < this.height; ++i) { for(int j = 0; j < this.width; ++j) { if (Math.abs(that.pixels[i][j] - this.pixels[i][j]) > thresh) { return false; } } } return true; } else { return false; } } public float getPixelNative(Pixel p) { return this.getPixelNative(p.x, p.y); } public float getPixelNative(int x, int y) { return this.pixels[y][x]; } public float[] getPixelVectorNative(float[] f) { for(int y = 0; y < this.getHeight(); ++y) { for(int x = 0; x < this.getWidth(); ++x) { f[x + y * this.getWidth()] = this.pixels[y][x]; } } return f; } public void setPixelNative(int x, int y, float val) { this.pixels[y][x] = val; } public static FImage[] createArray(int num, int width, int height) { FImage[] array = new FImage[num]; for(int i = 0; i < num; ++i) { array[i] = new FImage(width, height); } return array; } public float sum() { float sum = 0.0F; float[][] var2 = this.pixels; int var3 = var2.length; for(int var4 = 0; var4 < var3; ++var4) { float[] row = var2[var4]; for(int i = 0; i < row.length; ++i) { sum += row[i]; } } return sum; } public MBFImage toRGB() { return new MBFImage(ColourSpace.RGB, new FImage[]{this.clone(), this.clone(), this.clone()}); } public FImage flipX() { int hwidth = this.width / 2; for(int y = 0; y < this.height; ++y) { for(int x = 0; x < hwidth; ++x) { int xx = this.width - x - 1; float tmp = this.pixels[y][x]; this.pixels[y][x] = this.pixels[y][xx]; this.pixels[y][xx] = tmp; } } return this; } public FImage flipY() { int hheight = this.height / 2; for(int y = 0; y < hheight; ++y) { int yy = this.height - y - 1; for(int x = 0; x < this.width; ++x) { float tmp = this.pixels[y][x]; this.pixels[y][x] = this.pixels[yy][x]; this.pixels[yy][x] = tmp; } } return this; } public FImage overlayInplace(FImage img, FImage alpha, int x, int y) { int sx = Math.max(x, 0); int sy = Math.max(y, 0); int ex = Math.min(this.width, x + img.getWidth()); int ey = Math.min(this.height, y + img.getHeight()); for(int yc = sy; yc < ey; ++yc) { for(int xc = sx; xc < ex; ++xc) { float a = alpha.pixels[yc - sy][xc - sx]; this.pixels[yc][xc] = a * img.pixels[yc - sy][xc - sx] + (1.0F - a) * this.pixels[yc][xc]; } } return this; } public FImage overlayInplace(FImage image, int x, int y) { return this.overlayInplace(image, this.clone().fill(1.0F), x, y); } public static FImage randomImage(int width, int height) { FImage img = new FImage(width, height); for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { img.pixels[y][x] = (float)Math.random(); } } return img; } public FImage replace(Float target, Float replacement) { return this.replace(target, replacement); } public FImage replace(float target, float replacement) { for(int r = 0; r < this.height; ++r) { for(int c = 0; c < this.width; ++c) { if (this.pixels[r][c] == target) { this.pixels[r][c] = replacement; } } } return this; } public FImage extractCentreSubPix(float cx, float cy, FImage out) { int width = out.width; int height = out.height; for(int y = 0; y < height; ++y) { for(int x = 0; x < width; ++x) { float ix = (float)((double)((float)x + cx) - (double)(width - 1) * 0.5D); float iy = (float)((double)((float)y + cy) - (double)(height - 1) * 0.5D); out.pixels[y][x] = this.getPixelInterpNative(ix, iy, 0.0F); } } return out; } } static abstract class AbstractMultiScaleObjectDetector, DETECTED_OBJECT> implements MultiScaleObjectDetector { protected Rectangle roi; protected int minSize = 0; protected int maxSize = 0; protected AbstractMultiScaleObjectDetector() { } protected AbstractMultiScaleObjectDetector(int minSize, int maxSize) { this.minSize = minSize; this.maxSize = maxSize; } public void setROI(Rectangle roi) { this.roi = roi; } public void setMinimumDetectionSize(int size) { this.minSize = size; } public void setMaximumDetectionSize(int size) { this.maxSize = size; } public int getMinimumDetectionSize() { return this.minSize; } public int getMaximumDetectionSize() { return this.maxSize; } } sclass Detector extends AbstractMultiScaleObjectDetector { /** * Default step size to make when there is a hint of detection. */ public static final int DEFAULT_SMALL_STEP = 1; /** * Default step size to make when there is definitely no detection. */ public static final int DEFAULT_BIG_STEP = 2; /** * Default scale factor multiplier. */ public static final float DEFAULT_SCALE_FACTOR = 1.1f; protected StageTreeClassifier cascade; protected float scaleFactor = 1.1f; protected int smallStep = 1; protected int bigStep = 2; /** * Construct the {@link Detector} with the given parameters. * * @param cascade * the cascade or tree of stages. * @param scaleFactor * the amount to change between scales (multiplicative) * @param smallStep * the amount to step when there is a hint of detection * @param bigStep * the amount to step when there is definitely no detection */ public Detector(StageTreeClassifier cascade, float scaleFactor, int smallStep, int bigStep) { super(Math.max(cascade.width, cascade.height), 0); this.cascade = cascade; this.scaleFactor = scaleFactor; this.smallStep = smallStep; this.bigStep = bigStep; } /** * Construct the {@link Detector} with the given tree of stages and scale * factor. The default step sizes are used. * * @param cascade * the cascade or tree of stages. * @param scaleFactor * the amount to change between scales */ public Detector(StageTreeClassifier cascade, float scaleFactor) { this(cascade, scaleFactor, DEFAULT_SMALL_STEP, DEFAULT_BIG_STEP); } /** * Construct the {@link Detector} with the given tree of stages, and the * default parameters for step sizes and scale factor. * * @param cascade * the cascade or tree of stages. */ public Detector(StageTreeClassifier cascade) { this(cascade, DEFAULT_SCALE_FACTOR, DEFAULT_SMALL_STEP, DEFAULT_BIG_STEP); } /** * Perform detection at a single scale. Subclasses may override this to * customise the spatial search. The given starting and stopping coordinates * take into account any region of interest set on this detector. * * @param sat * the summed area table(s) * @param startX * the starting x-ordinate * @param stopX * the stopping x-ordinate * @param startY * the starting y-ordinate * @param stopY * the stopping y-ordinate * @param ystep * the amount to step * @param windowWidth * the window width at the current scale * @param windowHeight * the window height at the current scale * @param results * the list to store detection results in */ protected void detectAtScale(final SummedSqTiltAreaTable sat, final int startX, final int stopX, final int startY, final int stopY, final float ystep, final int windowWidth, final int windowHeight, final List results) { for (int iy = startY; iy < stopY; iy++) { final int y = Math.round(iy * ystep); for (int ix = startX, xstep = 0; ix < stopX; ix += xstep) { final int x = Math.round(ix * ystep); final int result = cascade.classify(sat, x, y); if (result > 0) { results.add(new Rectangle(x, y, windowWidth, windowHeight)); } // if there is no detection, then increase the step size xstep = (result > 0 ? smallStep : bigStep); // TODO: think about what to do if there isn't a detection, but // we're very close to having one based on the ratio of stages // passes to total stages. } } } @Override public List detect(FImage image) { final List results = new ArrayList(); final int imageWidth = image.getWidth(); final int imageHeight = image.getHeight(); final SummedSqTiltAreaTable sat = new SummedSqTiltAreaTable(image, cascade.hasTiltedFeatures); // compute the number of scales to test and the starting factor int nFactors = 0; int startFactor = 0; for (float factor = 1; factor * cascade.width < imageWidth - 10 && factor * cascade.height < imageHeight - 10; factor *= scaleFactor) { final float width = factor * cascade.width; final float height = factor * cascade.height; if (width < minSize || height < minSize) { startFactor++; } if (maxSize > 0 && (width > maxSize || height > maxSize)) { break; } nFactors++; } // run the detection at each scale float factor = (float) Math.pow(scaleFactor, startFactor); for (int scaleStep = startFactor; scaleStep < nFactors; factor *= scaleFactor, scaleStep++) { final float ystep = Math.max(2, factor); final int windowWidth = (int) (factor * cascade.width); final int windowHeight = (int) (factor * cascade.height); // determine the spatial range, taking into account any ROI. final int startX = (int) (roi == null ? 0 : Math.max(0, roi.x)); final int startY = (int) (roi == null ? 0 : Math.max(0, roi.y)); final int stopX = Math.round( (((roi == null ? imageWidth : Math.min(imageWidth, roi.x + roi.width)) - windowWidth)) / ystep); final int stopY = Math.round( (((roi == null ? imageHeight : Math.min(imageHeight, roi.y + roi.height)) - windowHeight)) / ystep); // prepare the cascade for this scale cascade.setScale(factor); detectAtScale(sat, startX, stopX, startY, stopY, ystep, windowWidth, windowHeight, results); } return results; } /** * Get the step size the detector will make if there is any hint of a * detection. This should be smaller than {@link #bigStep()}. * * @return the amount to step on any hint of detection. */ public int smallStep() { return smallStep; } /** * Get the step size the detector will make if there is definitely no * detection. This should be bigger than {@link #smallStep()}. * * @return the amount to step when there is definitely no detection. */ public int bigStep() { return bigStep; } /** * Set the step size the detector will make if there is any hint of a * detection. This should be smaller than {@link #bigStep()}. * * @param smallStep * The amount to step on any hint of detection. */ public void setSmallStep(int smallStep) { this.smallStep = smallStep; } /** * Set the step size the detector will make if there is definitely no * detection. This should be bigger than {@link #smallStep()}. * * @param bigStep * The amount to step when there is definitely no detection. */ public void bigStep(int bigStep) { this.bigStep = bigStep; } /** * Get the scale factor (the amount to change between scales * (multiplicative)). * * @return the scaleFactor */ public float getScaleFactor() { return scaleFactor; } /** * Set the scale factor (the amount to change between scales * (multiplicative)). * * @param scaleFactor * the scale factor to set */ public void setScaleFactor(float scaleFactor) { this.scaleFactor = scaleFactor; } /** * Get the classifier tree or cascade used by this detector. * * @return the classifier tree or cascade. */ public StageTreeClassifier getClassifier() { return cascade; } } sclass HaarCascadeDetector { public enum BuiltInCascade { /** * A eye detector */ eye("haarcascade_eye.xml"), /** * A eye with glasses detector */ eye_tree_eyeglasses("haarcascade_eye_tree_eyeglasses.xml"), /** * A frontal face detector */ frontalface_alt("haarcascade_frontalface_alt.xml"), /** * A frontal face detector */ frontalface_alt2("haarcascade_frontalface_alt2.xml"), /** * A frontal face detector */ frontalface_alt_tree("haarcascade_frontalface_alt_tree.xml"), /** * A frontal face detector */ frontalface_default("haarcascade_frontalface_default.xml"), /** * A fullbody detector */ fullbody("haarcascade_fullbody.xml"), /** * A left eye detector */ lefteye_2splits("haarcascade_lefteye_2splits.xml"), /** * A lower body detector */ lowerbody("haarcascade_lowerbody.xml"), /** * A detector for a pair of eyes */ mcs_eyepair_big("haarcascade_mcs_eyepair_big.xml"), /** * A detector for a pair of eyes */ mcs_eyepair_small("haarcascade_mcs_eyepair_small.xml"), /** * A left eye detector */ mcs_lefteye("haarcascade_mcs_lefteye.xml"), /** * A mouth detector */ mcs_mouth("haarcascade_mcs_mouth.xml"), /** * A nose detector */ mcs_nose("haarcascade_mcs_nose.xml"), /** * A right eye detector */ mcs_righteye("haarcascade_mcs_righteye.xml"), /** * An upper body detector */ mcs_upperbody("haarcascade_mcs_upperbody.xml"), /** * A profile face detector */ profileface("haarcascade_profileface.xml"), /** * A right eye detector */ righteye_2splits("haarcascade_righteye_2splits.xml"), /** * An upper body detector */ upperbody("haarcascade_upperbody.xml"); private String classFile; private BuiltInCascade(String classFile) { this.classFile = classFile; } /** * @return The name of the cascade resource */ public String classFile() { return classFile; } /** * Create a new detector with the this cascade. * * @return A new {@link HaarCascadeDetector} */ public HaarCascadeDetector load() { try { return new HaarCascadeDetector(classFile); } catch (final Exception e) { throw new RuntimeException(e); } } } protected Detector detector; protected DetectionFilter> groupingFilter; protected boolean histogramEqualize = false; /** * Construct with the given cascade resource. See * {@link #setCascade(String)} to understand how the cascade is loaded. * * @param cas * The cascade resource. * @see #setCascade(String) */ public HaarCascadeDetector(String cas) { try { setCascade(cas); } catch (final Exception e) { throw new RuntimeException(e); } groupingFilter = new OpenCVGrouping(); } /** * Construct with the {@link BuiltInCascade#frontalface_default} cascade. */ public HaarCascadeDetector() { this(BuiltInCascade.frontalface_default.classFile()); } /** * Construct with the {@link BuiltInCascade#frontalface_default} cascade and * the given minimum search window size. * * @param minSize * minimum search window size */ public HaarCascadeDetector(int minSize) { this(); this.detector.setMinimumDetectionSize(minSize); } /** * Construct with the given cascade resource and the given minimum search * window size. See {@link #setCascade(String)} to understand how the * cascade is loaded. * * @param cas * The cascade resource. * @param minSize * minimum search window size. * * @see #setCascade(String) */ public HaarCascadeDetector(String cas, int minSize) { this(cas); this.detector.setMinimumDetectionSize(minSize); } /** * @return The minimum detection window size */ public int getMinSize() { return this.detector.getMinimumDetectionSize(); } /** * Set the minimum detection window size * * @param size * the window size */ public void setMinSize(int size) { this.detector.setMinimumDetectionSize(size); } /** * @return The maximum detection window size */ public int getMaxSize() { return this.detector.getMaximumDetectionSize(); } /** * Set the maximum detection window size * * @param size * the window size */ public void setMaxSize(int size) { this.detector.setMaximumDetectionSize(size); } /** * @return The grouping filter */ public DetectionFilter> getGroupingFilter() { return groupingFilter; } /** * Set the filter for merging detections * * @param grouping */ public void setGroupingFilter(DetectionFilter> grouping) { this.groupingFilter = grouping; } @Override public List detectFaces(FImage image) { if (histogramEqualize) image.processInplace(new EqualisationProcessor()); final List rects = detector.detect(image); final List> filteredRects = groupingFilter.apply(rects); final List results = new ArrayList(); for (final ObjectIntPair r : filteredRects) { results.add(new DetectedFace(r.first, image.extractROI(r.first), r.second)); } return results; } /** * @see Detector#getScaleFactor() * @return The detector scale factor */ public double getScaleFactor() { return detector.getScaleFactor(); } /** * Set the cascade classifier for this detector. The cascade file is first * searched for as a java resource, and if it is not found then a it is * assumed to be a file on the filesystem. * * @param cascadeResource * The cascade to load. * @throws Exception * if there is a problem loading the cascade. */ public void setCascade(String cascadeResource) throws Exception { // try to load serialized cascade from external XML file InputStream in = null; try { in = OCVHaarLoader.class.getResourceAsStream(cascadeResource); if (in == null) { in = new FileInputStream(new File(cascadeResource)); } final StageTreeClassifier cascade = OCVHaarLoader.read(in); if (this.detector == null) this.detector = new Detector(cascade); else this.detector = new Detector(cascade, this.detector.getScaleFactor()); } catch (final Exception e) { throw e; } finally { if (in != null) { try { in.close(); } catch (final IOException e) { } } } } /** * Set the detector scale factor * * @see Detector#setScaleFactor(float) * * @param scaleFactor * the scale factor */ public void setScale(float scaleFactor) { this.detector.setScaleFactor(scaleFactor); } /** * Serialize the detector using java serialization to the given stream * * @param os * the stream * @throws IOException */ public void save(OutputStream os) throws IOException { final ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(this); } /** * Deserialize the detector from a stream. The detector must have been * written with a previous invokation of {@link #save(OutputStream)}. * * @param is * @return {@link HaarCascadeDetector} read from stream. * @throws IOException * @throws ClassNotFoundException */ public static HaarCascadeDetector read(InputStream is) throws IOException, ClassNotFoundException { final ObjectInputStream ois = new ObjectInputStream(is); return (HaarCascadeDetector) ois.readObject(); } @Override public int hashCode() { int hashCode = HashCodeUtil.SEED; hashCode = HashCodeUtil.hash(hashCode, this.detector.getMinimumDetectionSize()); hashCode = HashCodeUtil.hash(hashCode, this.detector.getScaleFactor()); hashCode = HashCodeUtil.hash(hashCode, this.detector.getClassifier().getName()); hashCode = HashCodeUtil.hash(hashCode, this.groupingFilter); hashCode = HashCodeUtil.hash(hashCode, this.histogramEqualize); return hashCode; } @Override public void readBinary(DataInput in) throws IOException { this.detector = IOUtils.read(in); this.groupingFilter = IOUtils.read(in); histogramEqualize = in.readBoolean(); } @Override public byte[] binaryHeader() { return "HAAR".getBytes(); } @Override public void writeBinary(DataOutput out) throws IOException { IOUtils.write(detector, out); IOUtils.write(groupingFilter, out); out.writeBoolean(histogramEqualize); } @Override public String toString() { return "HaarCascadeDetector[cascade=" + detector.getClassifier().getName() + "]"; } /** * @return the underlying Haar cascade. */ public StageTreeClassifier getCascade() { return detector.getClassifier(); } /** * @return the underlying {@link Detector}. */ public Detector getDetector() { return detector; } } sclass HaarCascade_FaceDetector extends F1> { new HaarCascadeDetector detector; public L get(BufferedImage img) { if (img == null) null; ret map(detector.detectFaces(ImageUtilities.createFImage(img)), func(DetectedFace f) -> RectAndConfidence { RectAndConfidence(openImajRectangleToRect(f.getBounds()), f.getConfidence()) }); } } module HCFD > DynSingleFunctionWithPrintLog { void doIt { pnl(new HaarCascade_FaceDetector().get(loadImage2(#1101409))); } }