/*
* Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.alg.background.moving;
import boofcv.alg.background.BackgroundModelMoving;
import boofcv.alg.misc.GImageMiscOps;
import boofcv.alg.misc.ImageMiscOps;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageBase;
import boofcv.struct.image.ImageType;
import boofcv.testing.BoofTesting;
import georegression.struct.homography.Homography2D_F32;
import georegression.struct.point.Point2D_F32;
import georegression.transform.homography.HomographyPointOps_F32;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Peter Abeles
*/
public abstract class GenericBackgroundModelMovingChecks {
Random rand = new Random(234);
int width = 60;
int height = 50;
protected double backgroundOutsideTol = 0.01;
protected List<ImageType> imageTypes = new ArrayList<>();
public abstract<T extends ImageBase>
BackgroundModelMoving<T,Homography2D_F32> create( ImageType<T> imageType );
/**
* Basic check were multiple images are feed into the algorithm and another image,
* which has a region which is clearly different is then segmented.
*/
@Test
public void basicCheck() {
for( ImageType type : imageTypes ) {
basicCheck(type);
}
}
private <T extends ImageBase> void basicCheck( ImageType<T> imageType ) {
BackgroundModelMoving<T,Homography2D_F32> alg = create(imageType);
T frame = imageType.createImage(width,height);
Homography2D_F32 homeToWorld = new Homography2D_F32(1,0,width/2,0,1,height/2,0,0,1);
alg.initialize(width*2,height*2,homeToWorld);
for (int i = 0; i < 30; i++) {
Homography2D_F32 homeToCurrent = new Homography2D_F32();
if( i > 0 ) {
homeToCurrent.a13 = rand.nextFloat() * 5 - 2.5f;
homeToCurrent.a23 = rand.nextFloat() * 5 - 2.5f;
}
noise(100, 2, frame);
alg.updateBackground(new Homography2D_F32(),frame);
}
int x0 = 10, y0 = 12, x1 = 40, y1 = 38;
noise(100,2,frame);
GImageMiscOps.fillRectangle(frame,200,x0,y0,x1-x0,y1-y0);
Homography2D_F32 homeToCurrent = new Homography2D_F32();
GrayU8 segmented = new GrayU8(width,height);
alg.segment(homeToCurrent, frame, segmented);
// segmented.printBinary();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if( x >= x0 && x < x1 && y >= y0 && y < y1 ) {
assertEquals(1,segmented.get(x,y));
} else {
assertEquals(0,segmented.get(x,y));
}
}
}
}
/**
* The current image is partially outside of the background image. Check to see if it blows up
* and that segmented pixels are correctly marked as inside or outside
*/
@Test
public void currentOutsideBackground() {
for( ImageType type : imageTypes ) {
currentOutsideBackground(type);
}
}
private <T extends ImageBase>
void currentOutsideBackground( ImageType<T> imageType ) {
T frame = imageType.createImage(width, height);
GrayU8 segmented = new GrayU8(width,height);
BackgroundModelMoving<T, Homography2D_F32> alg = create(frame.getImageType());
Homography2D_F32 homeToWorld = new Homography2D_F32();
alg.initialize(width, height, homeToWorld);
alg.setUnknownValue(2);
double translationTol = backgroundOutsideTol/2;
Homography2D_F32 homeToCurrent = new Homography2D_F32();
homeToCurrent.a13 = 5;
checkTransform(frame, segmented, alg, homeToCurrent,translationTol);
homeToCurrent.a13 = -5;
checkTransform(frame, segmented, alg, homeToCurrent,translationTol);
homeToCurrent.a13 = 0;
homeToCurrent.a23 = 5;
checkTransform(frame, segmented, alg, homeToCurrent,translationTol);
homeToCurrent.a23 = -5;
checkTransform(frame, segmented, alg, homeToCurrent,translationTol);
// make it more interesting
homeToCurrent.set(1.0f, 0.6f, 20, -0.6f, 0.95f, 20, 0, 0, 1);
checkTransform(frame, segmented, alg, homeToCurrent, backgroundOutsideTol);
}
private <T extends ImageBase> void checkTransform(T frame, GrayU8 segmented,
BackgroundModelMoving<T, Homography2D_F32> alg,
Homography2D_F32 homeToCurrent , double tol ) {
Homography2D_F32 currentToHome = homeToCurrent.invert(null);
alg.reset();
alg.updateBackground(homeToCurrent, frame);
alg.segment(homeToCurrent, frame, segmented);
checkSegmented(currentToHome, segmented,tol);
alg.segment(new Homography2D_F32(), frame, segmented);
checkSegmented(homeToCurrent, segmented,tol);
}
/**
* Checks to see if pixels outside of BG are marked as unknown
*/
private void checkSegmented(Homography2D_F32 transform , GrayU8 segmented , double tol ) {
Point2D_F32 p = new Point2D_F32();
int numErrors = 0;
for (int y = 0; y < segmented.height; y++) {
for (int x = 0; x < segmented.width; x++) {
HomographyPointOps_F32.transform(transform,x,y,p);
int xx = (int)Math.floor(p.x);
int yy = (int)Math.floor(p.y);
if( segmented.isInBounds(xx,yy)) {
if( segmented.get(x, y) == 2)
numErrors++;
}else {
if( segmented.get(x, y) != 2)
numErrors++;
}
}
}
assertTrue( numErrors/(double)(segmented.width*segmented.height) <=tol );
}
/**
* If a pixel in the current frame goes outside the background it should be marked as background
*/
@Test
public void markNoBackgroundAsBackground() {
for( ImageType type : imageTypes ) {
markNoBackgroundAsBackground(type);
}
}
private <T extends ImageBase>
void markNoBackgroundAsBackground( ImageType<T> imageType ) {
T frame = imageType.createImage(width, height);
GrayU8 segmented = new GrayU8(width,height);
BackgroundModelMoving<T, Homography2D_F32> alg = create(frame.getImageType());
alg.setUnknownValue(2);
Homography2D_F32 homeToWorld = new Homography2D_F32();
alg.initialize(width, height, homeToWorld);
Homography2D_F32 homeToCurrent = new Homography2D_F32();
homeToCurrent.a13 = 5;
alg.updateBackground(homeToCurrent,frame);
alg.segment(homeToCurrent,frame,segmented);
for (int y = 0; y < height; y++) {
for (int x = 0; x < 5; x++) {
assertEquals(2,segmented.get(x,y));
}
for (int x = 5; x < width; x++) {
assertEquals(0,segmented.get(x,y));
}
}
}
/**
* Mark pixels which haven't been observed as unknown
*/
@Test
public void markUnobservedAsUnknown() {
for( ImageType type : imageTypes ) {
markUnobservedAsUnknown(type);
}
}
private <T extends ImageBase>
void markUnobservedAsUnknown( ImageType<T> imageType ) {
T frame = imageType.createImage(width, height);
GrayU8 segmented = new GrayU8(width,height);
BackgroundModelMoving<T, Homography2D_F32> alg = create(frame.getImageType());
alg.setUnknownValue(2);
Homography2D_F32 homeToWorld = new Homography2D_F32();
alg.initialize(width, height, homeToWorld);
Homography2D_F32 homeToCurrent = new Homography2D_F32();
alg.segment(homeToCurrent,frame,segmented);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
assertEquals(2,segmented.get(x,y));
}
}
}
/**
* Sees if reset discard the previous history in the background image
*/
@Test
public void reset() {
for( ImageType type : imageTypes ) {
reset(type);
}
}
private <T extends ImageBase>
void reset( ImageType<T> imageType ) {
T frame = imageType.createImage(width, height);
BackgroundModelMoving<T, Homography2D_F32> alg = create(frame.getImageType());
Homography2D_F32 homeToWorld = new Homography2D_F32(1,0,width/2,0,1,height/2,0,0,1);
alg.initialize(width*2,height*2,homeToWorld);
Homography2D_F32 homeToCurrent = new Homography2D_F32();
GImageMiscOps.fill(frame,100);
alg.updateBackground(homeToCurrent,frame);
alg.reset();
GImageMiscOps.fill(frame,50);
alg.updateBackground(homeToCurrent,frame);
GrayU8 segmented = new GrayU8(width,height);
GrayU8 expected = new GrayU8(width,height);
// there should be no change
// if reset isn't the case then this will fail
alg.segment(homeToCurrent,frame,segmented);
BoofTesting.assertEquals(expected,segmented,1e-8);
GImageMiscOps.fill(frame,100);
ImageMiscOps.fill(expected,1);
// it should be all changed. really just a sanity check
alg.segment(homeToCurrent,frame,segmented);
BoofTesting.assertEquals(expected,segmented,1e-8);
}
@Test
public void checkSubImage() {
for( ImageType type : imageTypes ) {
checkSubImage(type);
}
}
private <T extends ImageBase>
void checkSubImage( ImageType<T> imageType ) {
T frame = imageType.createImage(width, height);
GrayU8 segmented = new GrayU8(width,height);
checkSubImage_process(frame, segmented);
GrayU8 expected = segmented.clone();
frame = BoofTesting.createSubImageOf(frame);
segmented = BoofTesting.createSubImageOf(segmented);
ImageMiscOps.fill(segmented,0);
checkSubImage_process(frame, segmented);
GrayU8 found = segmented.clone();
// see if both produce the same result
BoofTesting.assertEquals(expected,found,1e-8);
}
private <T extends ImageBase>
void checkSubImage_process( T frame, GrayU8 segmented)
{
rand = new Random(2345);
BackgroundModelMoving<T, Homography2D_F32> alg = create(frame.getImageType());
Homography2D_F32 homeToWorld = new Homography2D_F32(1,0,width/2,0,1,height/2,0,0,1);
alg.initialize(width*2,height*2,homeToWorld);
for (int i = 0; i < 5; i++) {
Homography2D_F32 homeToCurrent = new Homography2D_F32();
if( i > 0 ) {
homeToCurrent.a13 = rand.nextFloat() * 5 - 2.5f;
homeToCurrent.a23 = rand.nextFloat() * 5 - 2.5f;
}
noise(100, 30, frame);
alg.updateBackground(homeToCurrent,frame);
}
Homography2D_F32 homeToCurrent = new Homography2D_F32();
noise(100, 30, frame);
alg.segment(homeToCurrent, frame, segmented);
}
private void noise( double mean , double range , ImageBase image ) {
GImageMiscOps.fill(image,mean);
GImageMiscOps.addUniform(image,rand,-range,range);
}
}