/*
* 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.demonstrations.feature.describe;
import boofcv.abst.feature.dense.DescribeImageDenseHoG;
import boofcv.factory.feature.dense.ConfigDenseHoG;
import boofcv.factory.feature.dense.FactoryDescribeImageDense;
import boofcv.gui.DemonstrationBase;
import boofcv.gui.image.ImagePanel;
import boofcv.gui.image.ScaleOptions;
import boofcv.gui.image.ShowImages;
import boofcv.io.UtilIO;
import boofcv.struct.feature.TupleDesc_F64;
import boofcv.struct.image.GrayF32;
import boofcv.struct.image.ImageBase;
import boofcv.struct.image.ImageType;
import georegression.geometry.UtilPoint2D_I32;
import georegression.struct.point.Point2D_I32;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Displays an image and lets the user click on it to show the closest HOG descriptor
* at that location.
*
* @author Peter Abeles
*/
public class VisualizeHogDescriptorApp<T extends ImageBase> extends DemonstrationBase<T>
{
ControlHogDescriptorPanel controlPanel = new ControlHogDescriptorPanel(this);
VisualizePanel imagePanel = new VisualizePanel();
ConfigDenseHoG config = new ConfigDenseHoG();
final Object hogLock = new Object();
DescribeImageDenseHoG<T> hog;
Color colors[];
float cos[],sin[];
Point2D_I32 selectedPixel;
int selectedIndex;
TupleDesc_F64 targetDesc;
Point2D_I32 targetLocation;
T input;
public VisualizeHogDescriptorApp(List<String> exampleInputs, ImageType<T> imageType) {
super(exampleInputs, imageType);
colors = new Color[256];
for (int i = 0; i < colors.length; i++) {
colors[i] = new Color(i, i, i);
}
add(controlPanel,BorderLayout.WEST);
add(imagePanel,BorderLayout.CENTER);
config.pixelsPerCell = 20;
config.cellsPerBlockY = 5;
updateDescriptor();
imagePanel.setScaling(ScaleOptions.NONE);
imagePanel.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
selectRegion(e.getX(),e.getY());
}
});
}
private void selectRegion( int x , int y ) {
int bestIndex = -1;
synchronized (hogLock) {
double bestDistance = Double.MAX_VALUE;
List<Point2D_I32> locations = hog.getLocations();
for (int i = 0; i < locations.size(); i++) {
Point2D_I32 p = locations.get(i);
double d = UtilPoint2D_I32.distance(p.x, p.y, x, y);
if (d < bestDistance) {
bestDistance = d;
bestIndex = i;
}
}
if( bestIndex >= 0 ) {
selectedPixel = new Point2D_I32(x,y);
selectedIndex = bestIndex;
targetDesc = hog.getDescriptions().get(bestIndex);
targetLocation = hog.getLocations().get(bestIndex);
imagePanel.repaint();
}
}
}
private void updateDescriptor() {
synchronized (hogLock) {
hog = (DescribeImageDenseHoG<T>) FactoryDescribeImageDense.hog(config, imageType);
}
int numAngles = config.orientationBins;
cos = new float[numAngles];
sin = new float[numAngles];
for (int i = 0; i < numAngles; i++) {
double theta = Math.PI*(i+0.5)/numAngles;
cos[i] = (float)Math.cos(theta);
sin[i] = (float)Math.sin(theta);
}
}
@Override
public void processImage(BufferedImage buffered, T input) {
boolean inputSizeChanged = false;
synchronized (hogLock) {
inputSizeChanged =
this.input == null || this.input.width != input.width || this.input.height != input.height;
this.input = input;
hog.process(input);
if( inputSizeChanged ) {
targetDesc = null;
targetLocation = null;
selectedPixel = null;
} else {
if (isRegionSelected()) {
targetDesc = hog.getDescriptions().get(selectedIndex);
targetLocation = hog.getLocations().get(selectedIndex);
}
}
}
imagePanel.setBufferedImage(buffered);
imagePanel.setPreferredSize(new Dimension(buffered.getWidth(),buffered.getHeight()));
imagePanel.setMinimumSize(new Dimension(buffered.getWidth(),buffered.getHeight()));
}
private boolean isRegionSelected() {
return targetDesc != null;
}
private void renderHog(int bcx , int bcy ,
TupleDesc_F64 desc ,
Graphics2D g2 ) {
Line2D.Float line = new Line2D.Float();
int gridWidth = config.pixelsPerCell*config.cellsPerBlockX;
int gridHeight = config.pixelsPerCell*config.cellsPerBlockY;
int tl_x = bcx - gridWidth/2;
int tl_y = bcy - gridHeight/2;
g2.setColor(Color.BLACK);
g2.fillRect(tl_x,tl_y,gridWidth,gridHeight);
double maxValue = 0;
for (int i = 0; i < desc.value.length; i++) {
maxValue = Math.max(maxValue,desc.value[i]);
}
float foo = config.pixelsPerCell/2.0f;
int index = 0;
for (int cellY = 0; cellY < config.cellsPerBlockY; cellY++) {
for (int cellX = 0; cellX < config.cellsPerBlockX; cellX++) {
int c_x = tl_x + (int)((cellX+0.5)*config.pixelsPerCell);
int c_y = tl_y + (int)((cellY+0.5)*config.pixelsPerCell);
for (int i = 0; i < config.orientationBins; i++) {
int a = (int) (255.0f * desc.value[index++]/maxValue);
g2.setColor(colors[a]);
float x0 = c_x - foo * cos[i];
float x1 = c_x + foo * cos[i];
float y0 = c_y - foo * sin[i];
float y1 = c_y + foo * sin[i];
line.setLine(x0, y0, x1, y1);
g2.draw(line);
}
}
}
if( controlPanel.doShowGrid ) {
g2.setColor(Color.RED);
for (int cellY = 0; cellY <= config.cellsPerBlockY; cellY++) {
int y = tl_y+cellY*config.pixelsPerCell;
g2.drawLine(tl_x,y,tl_x+gridWidth,y);
}
for (int cellX = 0; cellX <= config.cellsPerBlockX; cellX++) {
int x = tl_x+cellX*config.pixelsPerCell;
g2.drawLine(x,tl_y,x,tl_y+gridHeight);
}
}
}
private class VisualizePanel extends ImagePanel {
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
synchronized (hogLock) {
if( isRegionSelected() ) {
renderHog(targetLocation.x, targetLocation.y,targetDesc,(Graphics2D)g);
}
}
}
}
public void visualsChanged() {
imagePanel.repaint();
}
public void configChanged() {
config.pixelsPerCell = controlPanel.cellWidth;
config.cellsPerBlockX = controlPanel.gridX;
config.cellsPerBlockY = controlPanel.gridY;
config.fastVariant = controlPanel.fast;
config.orientationBins = controlPanel.histogram;
synchronized (hogLock) {
updateDescriptor();
hog.process(input);
}
synchronized (hogLock ) {
if( selectedPixel != null ) {
selectRegion(selectedPixel.x,selectedPixel.y);
}
}
}
public static void main(String[] args) {
List<String> examples = new ArrayList<>();
examples.add(UtilIO.pathExample("segment/berkeley_horses.jpg"));
examples.add(UtilIO.pathExample("segment/berkeley_man.jpg"));
examples.add(UtilIO.pathExample("shapes/shapes01.png"));
examples.add(UtilIO.pathExample("shapes/shapes02.png"));
ImageType imageType = ImageType.single(GrayF32.class);
VisualizeHogDescriptorApp app = new VisualizeHogDescriptorApp(examples, imageType);
app.openFile(new File(examples.get(0)));
app.waitUntilDoneProcessing();
ShowImages.showWindow(app, "Hog Descriptor Visualization",true);
}
}