/* * 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.gui.calibration; import boofcv.abst.geo.calibration.ImageResults; import boofcv.alg.distort.AdjustmentType; import boofcv.alg.distort.ImageDistort; import boofcv.alg.distort.LensDistortionOps; import boofcv.alg.geo.RectifyImageOps; import boofcv.alg.geo.calibration.CalibrationObservation; import boofcv.core.image.border.BorderType; import boofcv.gui.feature.VisualizeFeatures; import boofcv.io.image.ConvertBufferedImage; import boofcv.struct.calib.CameraPinholeRadial; import boofcv.struct.distort.Point2Transform2_F32; import boofcv.struct.geo.PointIndex2D_F64; import boofcv.struct.image.GrayF32; import boofcv.struct.image.ImageType; import boofcv.struct.image.Planar; import georegression.struct.point.Point2D_F32; import georegression.struct.point.Point2D_F64; import org.ejml.data.DenseMatrix64F; import javax.swing.*; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; /** * Displays information images of planar calibration targets. * * @author Peter Abeles */ public class CalibratedImageGridPanel extends JPanel { // images of calibration target List<BufferedImage> images; // which image is being displayed int selectedImage; // for displaying undistorted image BufferedImage undistorted; // true if the image has been undistorted boolean isUndistorted = false; // observed feature locations List<CalibrationObservation> features = new ArrayList<>(); // results of calibration List<ImageResults> results = new ArrayList<>(); // for displaying corrected image Planar<GrayF32> origMS; Planar<GrayF32> correctedMS; // configures what is displayed or not boolean showPoints = true; boolean showErrors = true; boolean showUndistorted = false; boolean showAll = false; boolean showNumbers = true; boolean showOrder = true; ImageDistort<GrayF32,GrayF32> undoRadial; Point2Transform2_F32 remove_p_to_p; // how much errors are scaled up double errorScale; // int horizontal line int lineY=-1; public void setDisplay( boolean showPoints , boolean showErrors , boolean showUndistorted , boolean showAll , boolean showNumbers , boolean showOrder, double errorScale ) { this.showPoints = showPoints; this.showErrors = showErrors; this.showUndistorted = showUndistorted; this.showAll = showAll; this.showNumbers = showNumbers; this.showOrder = showOrder; this.errorScale = errorScale; } public void setSelected( int selected ) { this.selectedImage = selected; this.isUndistorted = false; if( origMS == null ) { BufferedImage image = images.get(selected); // the number of bands can be difficult to ascertain without digging deep into the data structure // so just declare a new one using convert origMS = ConvertBufferedImage.convertFromMulti(image,null,true,GrayF32.class); correctedMS = ConvertBufferedImage.convertFromMulti(image,null,true,GrayF32.class); undistorted = new BufferedImage(image.getWidth(),image.getHeight(),image.getType()); } } public void setImages( List<BufferedImage> images ) { this.images = images; } public void setResults( List<CalibrationObservation> features , List<ImageResults> results ) { this.features = features; this.results = results; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; if( images == null || selectedImage >= images.size() ) return; BufferedImage image = images.get(selectedImage); double scaleX = getWidth()/(double)image.getWidth(); double scaleY = getHeight()/(double)image.getHeight(); double scale = Math.min(1,Math.min(scaleX,scaleY)); AffineTransform tranOrig = g2.getTransform(); AffineTransform tran = g2.getTransform(); tran.concatenate(AffineTransform.getScaleInstance(scale,scale)); g2.setTransform(tran); if( showUndistorted) { if( undoRadial != null && !isUndistorted ) { undoRadialDistortion(image); isUndistorted = true; } g2.drawImage(undistorted,0,0,null); } else g2.drawImage(image,0,0,null); g2.setTransform(tranOrig); if( features.size() > selectedImage ) { drawFeatures(g2, scale); } if( lineY > -1 ) { g2.setColor(Color.RED); g2.setStroke(new BasicStroke(3)); g2.drawLine(0,lineY,getWidth(),lineY); } } private void undoRadialDistortion(BufferedImage image) { ConvertBufferedImage.convertFromMulti(image, origMS,true, GrayF32.class); for( int i = 0; i < origMS.getNumBands(); i++ ) { GrayF32 in = origMS.getBand(i); GrayF32 out = correctedMS.getBand(i); undoRadial.apply(in,out); } if( correctedMS.getNumBands() == 3 ) ConvertBufferedImage.convertTo(correctedMS,undistorted,true); else if( correctedMS.getNumBands() == 1 ) ConvertBufferedImage.convertTo(correctedMS.getBand(0),undistorted); else throw new RuntimeException("What kind of image has "+correctedMS.getNumBands()+"???"); } private void drawFeatures(Graphics2D g2 , double scale) { g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); CalibrationObservation set = features.get(selectedImage); Point2D_F32 adj = new Point2D_F32(); if( showOrder ) { List<Point2D_F64> adjusted; if( showUndistorted ) { adjusted = new ArrayList<>(); for( PointIndex2D_F64 p : set.points ) { remove_p_to_p.compute((float)p.x,(float)p.y,adj); adjusted.add(new Point2D_F64(adj.x,adj.y)); } } else { adjusted = (List)set.points; } renderOrder(g2,scale, adjusted); } if( showPoints ) { g2.setColor(Color.BLACK); g2.setStroke(new BasicStroke(3)); for( PointIndex2D_F64 p : set.points ) { if( showUndistorted ) { remove_p_to_p.compute((float)p.x,(float)p.y,adj); } else { adj.set((float)p.x,(float)p.y); } VisualizeFeatures.drawCross(g2, adj.x*scale, adj.y*scale, 4); } g2.setStroke(new BasicStroke(1)); g2.setColor(Color.RED); for( PointIndex2D_F64 p : set.points ) { if( showUndistorted ) { remove_p_to_p.compute((float)p.x,(float)p.y,adj); } else { adj.set((float)p.x,(float)p.y); } VisualizeFeatures.drawCross(g2, adj.x*scale, adj.y*scale, 4); } } if( showAll ) { for( CalibrationObservation l : features ) { for( PointIndex2D_F64 p : l.points ) { if( showUndistorted ) { remove_p_to_p.compute((float)p.x,(float)p.y,adj); } else { adj.set((float)p.x,(float)p.y); } VisualizeFeatures.drawPoint(g2,adj.x*scale,adj.y*scale,2,Color.BLUE,false); } } } if( showNumbers ) { if( showUndistorted ) drawNumbers(g2, set,remove_p_to_p,scale); else drawNumbers(g2, set,null,scale); } if( showErrors && results != null && results.size() > selectedImage ) { ImageResults result = results.get(selectedImage); g2.setStroke(new BasicStroke(4)); g2.setColor(Color.BLACK); for( int i = 0; i < set.size(); i++ ) { PointIndex2D_F64 p = set.get(i); if( showUndistorted ) { remove_p_to_p.compute((float)p.x,(float)p.y,adj); } else { adj.set((float)p.x,(float)p.y); } double r = errorScale*result.pointError[i]; if( r < 1 ) continue; VisualizeFeatures.drawCircle(g2, adj.x * scale, adj.y * scale, r); } g2.setStroke(new BasicStroke(2.5f)); g2.setColor(Color.ORANGE); for( int i = 0; i < set.size(); i++ ) { PointIndex2D_F64 p = set.get(i); if( showUndistorted ) { remove_p_to_p.compute((float)p.x,(float)p.y,adj); } else { adj.set((float)p.x,(float)p.y); } double r = errorScale*result.pointError[i]; if( r < 1 ) continue; VisualizeFeatures.drawCircle(g2, adj.x * scale, adj.y * scale, r); } } } public static void renderOrder(Graphics2D g2, double scale , List<Point2D_F64> points ) { g2.setStroke(new BasicStroke(5)); Line2D.Double l = new Line2D.Double(); for (int i = 0,j = 1; j < points.size(); i=j,j++) { Point2D_F64 p0 = points.get(i); Point2D_F64 p1 = points.get(j); double fraction = i / ((double) points.size() - 2); // fraction = fraction * 0.8 + 0.1; int red = (int)(0xFF*fraction) + (int)(0x00*(1-fraction)); int green = 0x00; int blue = (int)(0x00*fraction) + (int)(0xff*(1-fraction)); int lineRGB = red << 16 | green << 8 | blue; l.setLine(scale * p0.x , scale * p0.y, scale * p1.x, scale * p1.y ); g2.setColor(new Color(lineRGB)); g2.draw(l); } } public void setDistorted (CameraPinholeRadial param , DenseMatrix64F rect ) { if( rect == null ) { this.undoRadial = LensDistortionOps.imageRemoveDistortion( AdjustmentType.FULL_VIEW, BorderType.ZERO, param, null, ImageType.single(GrayF32.class)); this.remove_p_to_p = LensDistortionOps.transform_F32(AdjustmentType.FULL_VIEW, param, null, false); } else { this.undoRadial = RectifyImageOps.rectifyImage(param, rect, BorderType.ZERO, ImageType.single(GrayF32.class)); this.remove_p_to_p = RectifyImageOps.transformPixelToRect_F32(param, rect); } } public void setLine( int y ) { this.lineY = y; } public static void drawNumbers( Graphics2D g2 , CalibrationObservation foundTarget , Point2Transform2_F32 transform , double scale ) { Font regular = new Font("Serif", Font.PLAIN, 16); g2.setFont(regular); Point2D_F32 adj = new Point2D_F32(); AffineTransform origTran = g2.getTransform(); for( int i = 0; i < foundTarget.size(); i++ ) { Point2D_F64 p = foundTarget.get(i); int gridIndex = foundTarget.get(i).index; if( transform != null ) { transform.compute((float)p.x,(float)p.y,adj); } else { adj.set((float)p.x,(float)p.y); } String text = String.format("%2d",gridIndex); int x = (int)(adj.x*scale); int y = (int)(adj.y*scale); g2.setColor(Color.BLACK); g2.drawString(text,x-1,y); g2.drawString(text,x+1,y); g2.drawString(text,x,y-1); g2.drawString(text,x,y+1); g2.setTransform(origTran); g2.setColor(Color.GREEN); g2.drawString(text,x,y); } } }