First I really appreciate the the blog http://opencv-code.com/tutorials/automatic-perspective-correction-for-quadrilateral-objects/
I'm following this, but I'm not processing the image (Used in the blog above) but, rather I'm trying to detect the paper sheet of any size (A4, legal or any normal rectangular paper size) in real time camera preview.
Problem I'm getting is after "Expanding the hough line segments to fit the image" I'm getting large number of hough lines hence getting more than 4 intersecting points (not == 4).
like this http://s17.postimg.org/i0a57fb8v/device_2015_04_07_171351.png
How can I remove the rest invalid points, I just need the 4 corners points? I'm using OpenCV Library for Android
Please mainly focus on detectPaperSheet() method. Here is my code:
package org.opencv.samples.tutorial1;
import java.util.ArrayList;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.Toast;
public class Tutorial1Activity extends Activity implements
CvCameraViewListener2 {
private static final String TAG = "OCVSample::Activity";
private CameraBridgeViewBase mOpenCvCameraView;
private boolean mIsJavaCamera = true;
private MenuItem mItemSwitchCamera = null;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
#Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
Log.i(TAG, "OpenCV loaded successfully");
mOpenCvCameraView.enableView();
}
break;
default: {
super.onManagerConnected(status);
}
break;
}
}
};
public Tutorial1Activity() {
Log.i(TAG, "Instantiated new " + this.getClass());
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "called onCreate");
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.tutorial1_surface_view);
if (mIsJavaCamera)
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_java_surface_view);
else
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_native_surface_view);
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
}
#Override
public void onPause() {
super.onPause();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
#Override
public void onResume() {
super.onResume();
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_9, this,
mLoaderCallback);
}
public void onDestroy() {
super.onDestroy();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.i(TAG, "called onCreateOptionsMenu");
mItemSwitchCamera = menu.add("Toggle Native/Java camera");
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
String toastMesage = new String();
Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
if (item == mItemSwitchCamera) {
mOpenCvCameraView.setVisibility(SurfaceView.GONE);
mIsJavaCamera = !mIsJavaCamera;
if (mIsJavaCamera) {
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_java_surface_view);
toastMesage = "Java Camera";
} else {
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_native_surface_view);
toastMesage = "Native Camera";
}
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
mOpenCvCameraView.enableView();
Toast toast = Toast.makeText(this, toastMesage, Toast.LENGTH_LONG);
toast.show();
}
return true;
}
public void onCameraViewStarted(int width, int height) {
}
public void onCameraViewStopped() {
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
return detectPaperSheet(inputFrame.rgba());
}
private Mat detectPaperSheet(Mat original_image) {
Mat imgSource = original_image;
Mat untouched = original_image.clone();
// Converting to grayscale
Mat mHsvMat = new Mat(imgSource.rows(), imgSource.cols(),
CvType.CV_8UC1, new Scalar(0));
Imgproc.cvtColor(imgSource, mHsvMat, Imgproc.COLOR_BGRA2GRAY, 4);
// apply gaussian blur to smoothen lines of dots
Imgproc.GaussianBlur(imgSource, imgSource, new Size(11, 11), 0);
// Applying Canny
Imgproc.Canny(mHsvMat, mHsvMat, 80, 100);
Mat lines = new Mat();
int threshold = 100;
int minLineSize = 150;
int lineGap = 40;
Imgproc.HoughLinesP(mHsvMat, lines, 1, Math.PI / 180, threshold,
minLineSize, lineGap);
// Expanding the Lines To Image Width and Height
ArrayList<Point> corners = new ArrayList<Point>();
for (int x = 0; x < lines.cols(); x++) {
double[] vec = lines.get(0, x);
double[] val = new double[4];
val[0] = 0;
val[1] = ((float) vec[1] - vec[3]) / (vec[0] - vec[2]) * -vec[0]
+ vec[1];
val[2] = imgSource.cols();
val[3] = ((float) vec[1] - vec[3]) / (vec[0] - vec[2])
* (imgSource.cols() - vec[2]) + vec[3];
lines.put(0, x, val);
}
for (int x = 0; x < lines.cols(); x++) {
double[] vec = lines.get(0, x);
double x1 = vec[0], y1 = vec[1], x2 = vec[2], y2 = vec[3];
Point start = new Point(x1, y1);
Point end = new Point(x2, y2);
Core.line(imgSource, start, end, new Scalar(255, 0, 0), 1);
}
for (int i = 0; i < lines.cols(); i++) {
for (int j = i + 1; j < lines.cols(); j++) {
Point pt = computeIntersect(lines.get(0, i), lines.get(0, j));
if (pt.x >= 0 && pt.y >= 0)
corners.add(pt);
}
}
if (corners.size() < 4) {
Log.e("Corner < 4", corners.size() + " |");
return untouched;
} else {
Log.e("Corner > 4", corners.size() + " |");
}
//Mat cornerPoints = new Mat();
for (int j = 0; j < corners.size(); j++) {
Core.circle(imgSource,
new Point(corners.get(j).x, corners.get(j).y), 20,
new Scalar(0, 0, 255), 2);
}
return imgSource;
}
private static Point computeIntersect(double[] a, double[] b) {
double x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3], x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];
double denom = ((x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4));
Point pt = new Point();
if (denom != 0) {
pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2)
* (x3 * y4 - y3 * x4))
/ denom;
pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2)
* (x3 * y4 - y3 * x4))
/ denom;
return pt;
} else
return new Point(-1, -1);
}
}
I remember poly approximation was of some help for me in such case (detecting rectangular shape). However I used it with findContours method (with CV_CHAIN_APPROX_SIMPLE mode), as it seemed to produce better results than Hough lines.
[edit]
I did it in C++ with JNI, but in Java I think it should look something like this:
Mat srcImg; //you may want to apply Canny or some threshold before searching for contours
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy;
Imgproc.findContours(srcImg, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
MatOfPoint2f mat2fsrc, mat2fdst;
Scalar color = new Scalar(250, 250, 255);
for (int i = 0; i < contours.size(); i++) {
contours.get(i).convertTo(mat2fsrc, CvType.CV_32FC2);
Imgproc.approxPolyDP(mat2fsrc, mat2fdst, 0.01 * Imgproc.arcLength(mat2fsrc, true), true);
mat2fdst.convertTo(contours.get(i), CvType.CV_32S);
Imgproc.drawContours(srcImg, contours, i, color, 2, 8, hierarchy, 0, new Point());
}
Related
I am creating a derivative calculator in which the user enters the degree of their polynomial and then enters the coefficients of each term. The calculator displays the resulting derivative on an applet window as well as a graph of the original function.
Here is the graphing class.
package beta;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.List;
import javax.swing.JApplet;
public class GraphingCalc extends JApplet
{
public void drawAxes(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
Line2D.Double yaxis = new Line2D.Double(200, 400, 200, 0);
Line2D.Double xaxis = new Line2D.Double(0, 200, 400, 200);
g2.draw(yaxis);
g2.draw(xaxis);
for (int i = 0; i<=20; i++)
{
Line2D.Double ytick = new Line2D.Double(197, 400 - i * 20, 203, 400 - i * 20);
Line2D.Double xtick = new Line2D.Double(400 - i * 20, 203, 400 - i * 20, 197);
g2.draw(ytick);
g2.draw(xtick);
}
}
public void drawFunction(Graphics g, List<Double> l)
{
Graphics2D g2 = (Graphics2D)g;
double x1 = 0;
double y1 = 0;
double x2 = 0;
double y2 = 0;
int size = l.size();
for (double x = -10; x <= 10; x += 0.2)
{
x1 = x;
for (int d = size-1; d>=0; d--)
{
y1 += l.get(d) * Math.pow(x1, d);
}
Point2D.Double first = new Point2D.Double(20 * x1 + 200, -20 * y1 + 200);
x2 = x1 + 0.2;
for (int d = size-1; d>=0; d--)
{
y2 += l.get(d) * Math.pow(x2, d);
}
Point2D.Double second = new Point2D.Double(20 * x2 + 200, -20 * y2 + 200);
Line2D.Double line = new Line2D.Double(first, second);
g2.draw(line);
}
}
}
Here is the derivative calculation class. The only relevant part is when I get the list of coefficients from the user coeffList
package beta;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class DerivativeCalculator
{
public DerivativeCalculator()
{
String d = JOptionPane.showInputDialog("Enter the degree of your polynomial: ");
String v = JOptionPane.showInputDialog("Enter the x value "
+ "at which you want to take the derivative: ");
degree = Integer.parseInt(d);
value = Double.parseDouble(v);
coeffList = new ArrayList<Double>();
for (int i = 0; i <= degree; i++)
{
String console = JOptionPane.showInputDialog("Enter the coefficient of the "
+ "x^" + i + " term.");
Double coeff = Double.parseDouble(console);
coeffList.add(coeff);
}
}
public double calc()
{
double dx = 0.00001;
double x1 = value;
double y1 = 0;
for (int d = degree; d >= 0; d--)
{
y1 += coeffList.get(d) * Math.pow(x1, d);
}
double x2 = x1 + dx;
double y2 = 0;
for (int d = degree; d >= 0; d--)
{
y2 += coeffList.get(d) * Math.pow(x2, d);
}
double slope = (y2 - y1)/ (x2 - x1);
DecimalFormat round = new DecimalFormat("##.##");
round.setRoundingMode(RoundingMode.DOWN);
return Double.valueOf(round.format(slope));
}
public String getEquation()
{
String equation = "";
for (int d = degree; d >= 1; d--)
{
equation = equation + String.valueOf(coeffList.get(d)) + "x^" + String.valueOf(d) + " + ";
}
equation = equation + String.valueOf(coeffList.get(0)) + "x^" + String.valueOf(0);
return equation;
}
public String getValue()
{
return String.valueOf(value);
}
public List<Double> getCoeff()
{
return coeffList;
}
private int degree;
private double value;
private List<Double> coeffList;
}
Lastly, this is the test class which incorporates the two previous classes.
package beta;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JApplet;
import javax.swing.JOptionPane;
public class DerivativeCalculatorTest extends JApplet
{
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
setSize(800,800);
DerivativeCalculator myDerivCalc = new DerivativeCalculator();
g2.drawString(String.valueOf(myDerivCalc.calc()), 10, 100);
g2.drawString(myDerivCalc.getEquation(), 10, 40);
g2.drawString(myDerivCalc.getValue(), 10, 70);
GraphingCalc myGrapher = new GraphingCalc();
myGrapher.drawAxes(g2);
myGrapher.drawFunction(g2, myDerivCalc.getCoeff());
}
}
The applet runs, displays all the derivative information correctly, but the function graph does not draw correctly. For example when I input x + 5, the applet draws a bunch of individual straight lines, but they are clustered in the shape of a parabola.
I immediately suspect that it has to do with the way I draw the graph. I actually make a bunch of short lines with length 0.2.
for (double x = -10; x <= 10; x += 0.2)
{
x1 = x;
for (int d = size-1; d>=0; d--)
{
y1 += l.get(d) * Math.pow(x1, d);
}
Point2D.Double first = new Point2D.Double(20 * x1 + 200, -20 * y1 + 200);
x2 = x1 + 0.2;
for (int d = size-1; d>=0; d--)
{
y2 += l.get(d) * Math.pow(x2, d);
}
Point2D.Double second = new Point2D.Double(20 * x2 + 200, -20 * y2 + 200);
Line2D.Double line = new Line2D.Double(first, second);
g2.draw(line);
}
What is the problem? Any suggestions?
Some Points:
Your GraphingCalc class should not extends the JApplet and also for drawing lines you don't need to create some Line2D objects and then draw them. You can simply call drawLine method:
package beta;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.List;
public class GraphingCalc
{
public void drawAxes(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawLine(200, 400, 200, 0);
g2.drawLine(0, 200, 400, 200);
for (int i = 0; i<=20; i++)
{
g2.drawLine(197, 400 - i * 20, 203, 400 - i * 20);
g2.drawLine(400 - i * 20, 203, 400 - i * 20, 197);
}
}
public void drawFunction(Graphics g, List<Double> l)
{
Graphics2D g2 = (Graphics2D)g;
double x1 = 0;
double y1 = 0;
double x2 = 0;
double y2 = 0;
int size = l.size();
for (double x = -10; x <= 10; x += 0.2)
{
x1 = x;
for (int d = size-1; d>=0; d--)
{
y1 += l.get(d) * Math.pow(x1, d);
}
x2 = x1 + 0.2;
for (int d = size-1; d>=0; d--)
{
y2 += l.get(d) * Math.pow(x2, d);
}
g2.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
}
}
}
This way your program shows both axis and ticks. Also for a Polynomial of degree 1 it draws a line, but somehow I can tell it's not drawing the polynomial correctly. Maybe you want to correct some calculations. As you can see a simple polynomial of degree one with these coefficients should not be look like this:
Good Luck.
I am well and truly stuck on this method. I need to create an attack method for a ComputerPlayer that chooses a random location and attacks the other player's board. Then, there is an attack method where I have to just attack the other player's board. However, when I run the program and place my guesses on my board (GUI), the dots just appear on the ComputerPlayer's board. I don't know why my methods are wrong though.
/**
* Attack the specified Location loc. Marks
* the attacked Location on the guess board
* with a positive number if the enemy Player
* controls a ship at the Location attacked;
* otherwise, if the enemy Player does not
* control a ship at the attacked Location,
* guess board is marked with a negative number.
*
* If the enemy Player controls a ship at the attacked
* Location, the ship must add the Location to its
* hits taken. Then, if the ship has been sunk, it
* is removed from the enemy Player's list of ships.
*
* Return true if the attack resulted in a ship sinking;
* false otherwise.
*
* #param enemy
* #param loc
* #return
*/
#Override
public boolean attack(Player enemy, Location loc)
{
int[][] array = getGuessBoard();
if(!enemy.hasShipAtLocation(loc))
array[loc.getRow()][loc.getCol()] = -1;
else
{
array[loc.getRow()][loc.getCol()] = 1;
enemy.getShip(loc).takeHit(loc);
}
if(enemy.getShip(loc).isSunk()) {
enemy.removeShip(enemy.getShip(loc));
return true;
}
return false;
}
#Override
public boolean attack(Player enemy, Location loc)
{
int range = (10 - 0) + 1;
int r = (int) Math.random() * range;
int c = (int) Math.random() * range;
int[][] array = getGuessBoard();
if(array[r][c] != -1)
{
if(!enemy.hasShipAtLocation(loc))
array[loc.getRow()][loc.getCol()] = -1;
else
{
array[loc.getRow()][loc.getRow()] = 1;
enemy.getShip(loc).takeHit(loc);
}
if(getShip(loc).isSunk())
{
enemy.removeShip(enemy.getShip(loc));
return true;
}
}
return false;
}
GUI for Game
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class BattleshipDriver extends Canvas implements MouseListener
{
public static final int WIDTH = 1024, HEIGHT = WIDTH / 12 * 9;
private Battleship battleship;
private int x, y, squareSize, len;
private Player p1, p2;
private BufferedImage logo, end;
public BattleshipDriver()
{
battleship = new Battleship();
battleship.addPlayer(new HumanPlayer("Mr. Hubbard"));
battleship.addPlayer(new ComputerPlayer("AlphaBattleship"));
x = 90;
y = 200;
squareSize = 36;
len = squareSize * 10 - 1;
p1 = battleship.getPlayer(0);
p2 = battleship.getPlayer(1);
// Get Battleship Logo
try {
logo = ImageIO.read(new File("src/Logo.png"));
} catch (IOException e) {
e.printStackTrace();
}
// Get End Screen
try {
end = ImageIO.read(new File("src/End.png"));
} catch (IOException e) {
e.printStackTrace();
}
addMouseListener(this);
new Window(WIDTH, HEIGHT, "Battleship", this);
try {
Thread.sleep(100);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
render();
}
private void render()
{
Graphics g = getGraphics();
// Background
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, WIDTH, HEIGHT);
if(!battleship.gameOver())
{
// Boards
renderGrid(g, x, y, squareSize);
renderGuesses(g, p1, x, y, squareSize);
renderGrid(g, 570, y, squareSize);
renderGuesses(g, p2, 570, y, squareSize);
// Names
g.setColor(Color.WHITE);
g.drawString(p1.getName(), x, y + 25 + len);
g.drawString(p2.getName(), 570, y + 25 + len);
}
else
{
// End Screen
g.drawImage(end, 0, 0, this);
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", 1, squareSize));
String winner = battleship.getWinner().getName();
g.drawString(winner, WIDTH / 2 - (winner.length() * squareSize / 4), HEIGHT / 4);
g.drawString("Wins!", WIDTH / 2 - ("Wins!".length() * squareSize / 4), HEIGHT / 4 + squareSize);
}
// Battleship Logo
g.drawImage(logo, WIDTH / 2 - 246, 10, this);
g.dispose();
}
private void renderGrid(Graphics g, int x, int y, int s)
{
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", 1, s / 2));
// Row Lines
for(int i = 0; i < 11; i++)
g.drawLine(x, y+i*s, x+len, y+i*s);
// Column Lines
for(int i = 0; i < 11; i++)
g.drawLine(x+i*s, y, x+i*s, y+len);
// Row Markers
for(int i = 0; i < 10; i++) //marks row coordinates on side
g.drawString(i + "", x-(int)(s*0.43), y+(int)(s*0.67)+s*i);
// Column Markers
for(int i = 0; i < 10; i++) //marks column coordinates on top
g.drawString(i + "", x+(int)(s*0.4)+s*i, y-(int)(s*0.2));
}
public void renderGuesses(Graphics g, Player player, int x, int y, int s)
{
int[][] guessBoard = player.getGuessBoard();
for(int r = 0; r < guessBoard.length; r++)
for(int c = 0; c < guessBoard[r].length; c++)
if(guessBoard[r][c] > 0) // hit
{
g.setColor(Color.RED);
g.fillOval(c*s+x+(int)(s*0.35), r*s+y+(int)(s*0.35), (int)(s*0.33), (int)(s*0.33));
}
else if(guessBoard[r][c] < 0) // miss
{
g.setColor(Color.WHITE);
g.fillOval(c*s+x+(int)(s*0.35), r*s+y+(int)(s*0.35), (int)(s*0.33), (int)(s*0.33));
}
}
#Override
public void mouseClicked(MouseEvent e)
{
int r = e.getY();
int c = e.getX();
int len = squareSize * 10 - 1;
if(r > y && r < y + len && c > x && c < x + len) // clicked on board
{
int row = (r - y) / squareSize;
int col = (c - x) / squareSize;
System.out.println(row + ", " + col);
Location loc = new Location(row, col);
if(p1.getGuessBoard()[row][col] == 0)
{
p1.attack(p2, loc);
p2.attack(p1, loc);
}
battleship.upkeep();
render();
}
System.out.println(r + ", " + c);
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
public static void main(String[] args)
{
new BattleshipDriver();
}
}
I can create circle on touch but can not remove the previous one. I have manage to remove the very first circle but the code is poor and it does not really work as I want. I like to draw circle every time I touch the screen and remove the previous circle . So the screen starts with a circle and as I will touch a new position the previous should be removed and there will be a new one.So how to do that part?
Here is my work:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Display display = new Display(this);
display.init();
display.backPaint.setColor(Color.WHITE);
setContentView(display);
}
static class Display extends View {
ArrayList<Point> points = new ArrayList();
int touch1_x=700;
int touch1_y=700;
Paint backPaint;
Paint circlePaint;
Paint circlePaint2;
Display(Context context) {
super(context);
}
void init() {
backPaint = new Paint();
backPaint.setColor(Color.WHITE);
backPaint.setStyle(Paint.Style.FILL);
circlePaint = new Paint();
circlePaint.setColor(Color.YELLOW);
circlePaint2 = new Paint();
circlePaint2.setColor(Color.WHITE);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getActionMasked()) {
// if (event.getX(event.getActionIndex()) > 100 && event.getX(event.getActionIndex()) < 200) {
touch1_x = (int) event.getX(event.getActionIndex());
touch1_y = (int) event.getY(event.getActionIndex());
// }
System.out.println("touch1_x ===" + touch1_x);
points.add(new Point(touch1_x, touch1_y));
points.add(new Point(touch1_x, touch1_y));
return true;
}
return false;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backPaint);
canvas.drawCircle(touch1_x, touch1_y, 50, circlePaint);
for(Point p: points){
canvas.drawCircle(p.x, p.y, 50, circlePaint);
}
for(Point p: points){
canvas.drawCircle(p.x, p.y, 50,circlePaint2 );
}
invalidate();
}
}
}
relevant question
You could erase canvas before drawing the next circle, using
canvas.drawColor(Color.TRANSPARENT,Mode.CLEAR);
import turtle
t=turtle.Turtle()
wn=turtle.Screen()
for count in range(360):
t.fd(3)
t.rt(1)
wn.exitonclick()
I have a customised view for photo annotate, you can use this to do your job.
1: Create a file AnnotationView.java and copy the following code
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
/**
* #author Bob Gao
* http://bobgao.net
*/
public class AnnotationView extends ImageView implements OnTouchListener {
private static final float KEY_STROKE_WIDTH = 4;
private List<Annotation> annotations = new ArrayList<Annotation>();
private Annotation mAnnotation;
private Canvas mCanvas;
private Paint mPaint;
// private Rect mOutRect;
public AnnotationView(Context context) {
super(context);
setOnTouchListener(this);
setDrawingCacheEnabled(true);
setScaleType(ScaleType.FIT_XY);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.mCanvas = canvas;
for (Annotation annotation : annotations) {
mPaint.setColor(annotation.getColor());
switch (annotation.getShape()) {
case Annotation.KEY_SHAPE_CIRCLE:
drawCircle(annotation.getX(), annotation.getY(), annotation.getRadius());
break;
case Annotation.KEY_SHAPE_ARROW:
drawAL((int) annotation.getX(), (int) annotation.getY(), (int) annotation.getRadius(), annotation.getDegree());
break;
case Annotation.KEY_SHAPE_SQUARES:
drawRect((int) annotation.getX(), (int) annotation.getY(), (int) annotation.getRadius());
break;
case Annotation.KEY_SHAPE_TRIANGLE:
drawTriangle((int) annotation.getX(), (int) annotation.getY(), (int) annotation.getRadius(), annotation.getDegree());
break;
}
}
}
/**
* Draw a triangle
*
* #param x
* #param y
* #param r
*/
public void drawTriangle(int x, int y, int r, float degree) {
Path path = new Path();
path.moveTo(x, y);
path.lineTo(x - 2 * r, y + 2 * r);
path.lineTo(x + 2 * r, y + 2 * r);
path.lineTo(x, y);
path.close();
rotatePath(path, degree);
mCanvas.drawPath(path, mPaint);
}
public void drawRect(int x, int y, int r) {
int lenght = 4 * r;
int left = x - lenght / 2;
int top = y - lenght / 2;
int right = x + lenght / 2;
int bottom = y + lenght / 2;
Path path = new Path();
path.moveTo(left, top);
path.lineTo(right, top);
path.lineTo(right, bottom);
path.lineTo(left, bottom);
path.lineTo(left, top);
path.close();
mCanvas.drawPath(path, mPaint);
}
public void drawCircle(float x, float y, float r) {
mCanvas.drawCircle(x, y, r, mPaint);
}
/**
* Draw arrow
*
* #param sx
* #param sy
* #param ex
* #param ey
*/
public void drawAL(int sx, int sy, int r, float degree) {
int ex = (int) (sx + 2 * r);
int ey = (int) (sy + 2 * r);
switch (new Float(degree).intValue()) {
case 90:
ex = (int) (sx - 2 * r);
ey = (int) (sy + 2 * r);
break;
case 180:
ex = (int) (sx - 2 * r);
ey = (int) (sy - 2 * r);
break;
case 270:
ex = (int) (sx + 2 * r);
ey = (int) (sy - 2 * r);
break;
}
double H = 8; // the height of arrow
double L = 3.5; // half of bottom line
int x3 = 0;
int y3 = 0;
int x4 = 0;
int y4 = 0;
double awrad = Math.atan(L / H); // the rotation of arrow
double arraow_len = Math.sqrt(L * L + H * H); // the length of arrow
double[] arrXY_1 = rotateVec(ex - sx, ey - sy, awrad, true, arraow_len);
double[] arrXY_2 = rotateVec(ex - sx, ey - sy, -awrad, true, arraow_len);
double x_3 = ex - arrXY_1[0]; // (x3,y3) the first point
double y_3 = ey - arrXY_1[1];
double x_4 = ex - arrXY_2[0]; // (x4,y4) the second point
double y_4 = ey - arrXY_2[1];
Double X3 = new Double(x_3);
x3 = X3.intValue();
Double Y3 = new Double(y_3);
y3 = Y3.intValue();
Double X4 = new Double(x_4);
x4 = X4.intValue();
Double Y4 = new Double(y_4);
y4 = Y4.intValue();
// draw line
mCanvas.drawLine(sx, sy, ex, ey, mPaint);
Path triangle = new Path();
triangle.moveTo(ex, ey);
triangle.lineTo(x3, y3);
triangle.lineTo(x4, y4);
triangle.close();
mCanvas.drawPath(triangle, mPaint);
}
// Calculate
public double[] rotateVec(int px, int py, double ang, boolean isChLen, double newLen) {
double mathstr[] = new double[2];
// 矢量旋转函数,参数含义分别是x分量、y分量、旋转角、是否改变长度、新长度
double vx = px * Math.cos(ang) - py * Math.sin(ang);
double vy = px * Math.sin(ang) + py * Math.cos(ang);
if (isChLen) {
double d = Math.sqrt(vx * vx + vy * vy);
vx = vx / d * newLen;
vy = vy / d * newLen;
mathstr[0] = vx;
mathstr[1] = vy;
}
return mathstr;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (mAnnotation != null) {
float minX = 0;
float minY = 0;
float maxX = 0;
float maxY = 0;
switch (mAnnotation.getShape()) {
case Annotation.KEY_SHAPE_ARROW:
minX = 0;
minY = 0;
maxX = getLeft() + getWidth() - mAnnotation.getRadius();
maxY = getTop() + getHeight() - mAnnotation.getRadius();
break;
case Annotation.KEY_SHAPE_SQUARES:
minX = getLeft() + mAnnotation.getRadius() / 2;
minY = getTop() + mAnnotation.getRadius() / 2;
maxX = getLeft() + getWidth() - mAnnotation.getRadius() / 2;
maxY = getTop() + getHeight() - mAnnotation.getRadius() / 2;
break;
case Annotation.KEY_SHAPE_CIRCLE:
minX = 0;
minY = 0;
maxX = getLeft() + getWidth() - mAnnotation.getRadius();
maxY = getTop() + getHeight() - mAnnotation.getRadius();
case Annotation.KEY_SHAPE_TRIANGLE:
minX = getLeft() + mAnnotation.getRadius();
minY = getTop() + mAnnotation.getRadius();
maxX = getLeft() + getWidth() - mAnnotation.getRadius();
maxY = getTop() + getHeight() - mAnnotation.getRadius();
break;
}
if (x > minX && x < maxX && y > minY && y < maxY) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mAnnotation.setX(x);
mAnnotation.setY(y);
postInvalidate();
case MotionEvent.ACTION_MOVE:
mAnnotation.setX(x);
mAnnotation.setY(y);
postInvalidate();
break;
}
}
return true;
}
return false;
}
public void startAnnotate(String pathName) {
mAnnotation = new Annotation();
mAnnotation.setMaxRadius(getWidth() / 2);
mAnnotation.setColor(Color.RED);
mAnnotation.setX(getWidth() / 2);
mAnnotation.setY(getHeight() / 2);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(KEY_STROKE_WIDTH);
annotations.add(mAnnotation);
postInvalidate();
}
public void stopAnnotate() {
if (annotations.isEmpty()) {
mPaint = null;
mAnnotation = null;
} else {
annotations.remove(annotations.size() - 1);
if (annotations.isEmpty()) {
mAnnotation = null;
mPaint = null;
} else {
mAnnotation = annotations.get(annotations.size() - 1);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mAnnotation.getColor());
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(KEY_STROKE_WIDTH);
}
}
postInvalidate();
}
public void plus() {
if (mAnnotation != null) {
mAnnotation.plus();
postInvalidate();
}
}
public void sub() {
if (mAnnotation != null) {
mAnnotation.sub();
postInvalidate();
}
}
public void rotateRight() {
if (mAnnotation != null) {
if (mAnnotation.getDegree() >= 360) {
mAnnotation.setDegree(0);
} else {
mAnnotation.setDegree(mAnnotation.getDegree() + 90);
}
postInvalidate();
}
}
public void rotateLeft() {
if (mAnnotation != null) {
if (mAnnotation.getDegree() <= 0) {
mAnnotation.setDegree(270);
} else {
mAnnotation.setDegree(mAnnotation.getDegree() - 90);
}
postInvalidate();
}
}
public void changeColor(int color) {
if (mAnnotation != null) {
mAnnotation.setColor(color);
mPaint.setColor(color);
}
postInvalidate();
}
/**
* Change the shape
*
* #param id
*/
public void changeShape(int id) {
if (mAnnotation != null) {
mAnnotation.setShape(id);
postInvalidate();
}
}
/**
* Draw line
*
* #param fromX
* start point x
* #param fromY
* start point y
* #param toX
* end point x
* #param toY
* end point y
*/
public void drawLine(float fromX, float fromY, float toX, float toY) {
Path linePath = new Path();
linePath.moveTo(fromX, fromY);
linePath.lineTo(toX, toY);
linePath.close();
mCanvas.drawPath(linePath, mPaint);
invalidate();
}
public List<Annotation> getAnnotations() {
return annotations;
}
private void rotatePath(Path path, float degree) {
Matrix mMatrix = new Matrix();
RectF bounds = new RectF();
path.computeBounds(bounds, true);
mMatrix.postRotate(degree, (bounds.right + bounds.left) / 2, (bounds.bottom + bounds.top) / 2);
path.transform(mMatrix);
}
}
2. In your activity, create the AnnotationView and assign your image to it, then append this view to your root view.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AnnotationView mPreview = (FrameLayout) findViewById(R.id.camera_preview);
AnnotationView mAnnotationView = new AnnotationView(this);
Bitmap bitmap;
try {
bitmap = CBitmapUtil.decode(mPictureFile); // Get your image as a bitmap
mAnnotationView.setImageBitmap(bitmap);
mPreview.addView(mAnnotationView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
} catch (FileNotFoundException e) {
e.printStackTrace();
Toast.makeText(this, "Photo cannot be saved, please try again later.", Toast.LENGTH_SHORT).show();
}
}
I've made an android app that generates a tone and plays it, and it works fine on my emulator, but on my actual device, after a minute or so, it "stops working". Any ideas as to why?
This is the class where pretty much everything happens:
package com.funguscow;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
public class Afspl extends Activity {
public DrawView vi;
private Point size;
Display disp;
public int wide, high, cx, cy;
boolean doPlay = false;
Thread soundPlayer;
int note = 0;
int lastNote = note;
boolean changed = false;
int countTick = 0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
disp = getWindowManager().getDefaultDisplay();
try{
disp.getSize(size);
wide = size.x;
high = size.y;
}catch(Exception e){
wide = disp.getWidth();
high = disp.getHeight();
}
cx = 0; //tracks mouse x
cy = 0; //tracks mouse y
vi = new DrawView(getApplicationContext());
setContentView(vi);
soundPlayer = new Thread(new Runnable(){
public void run(){
int count = 0;
while(true){
try{
Thread.sleep((long)(duration * 10));
}catch(Exception e){}
//if(++countTick>)
if(!doPlay){
if(count==1 && track.getState()==AudioTrack.STATE_INITIALIZED)track.stop();
continue;
}
lastNote = note;
note = cy * 12 / high;
changed = (lastNote!=note);
if(cx > wide/2)note+=12;
int freq = (int)(440 * Math.pow(1.059463, note));
genTone(freq);
if(count==0){
handler.post(new Runnable(){
public void run(){
playSound();
}
});
count++;
}else{
handler.post(new Runnable(){
public void run(){
if(++countTick >= 10){
countTick = 0;
return;
}
track.release();
playSound();
}
});
}
}
}
});
soundPlayer.start();
}
private int waveType = 0;
class DrawView extends View{
Paint paint = new Paint();
public DrawView(Context context){
super(context);
}
public void onDraw(Canvas c){
int red = 256;
int green = 0;
int blue = 0;
int curInc = 0;
for(int i=0; i<24; i++){
if(curInc==0){
green += 256/2;
if(green>=256){
curInc = 1;
}
}
else if(curInc == 1){
red -= 256/2;
if(red<=0){
curInc = 2;
}
}
else if(curInc == 2){
blue += 256/2;
if(blue >= 256){
curInc = 3;
}
}
else if(curInc == 3){
green -= 256/2;
if(green<=0){
curInc = 4;
}
}
else if(curInc == 4){
red += 256/2;
if(red>=256){
curInc = 5;
}
}else if(curInc == 5){
blue -= 256/2;
if(blue<=0){
curInc=0;
}
}
int width = wide;
int start = 0;
if(i<12){
width/=2;
}
else{
start = width/2;
red = 256-red;
green = 256-green;
blue = 256-blue;
}
paint.setColor(getColor(red, blue, green));
int height = high / 12;
int starty = i * height;
int endy = (i+1) * height;
if(i>=12){
starty = (i-12) * height;
endy = (i-11) * height;
}
c.drawRect(start, starty, width, endy, paint);
}
paint.setColor(Color.WHITE);
c.drawRect((wide * 11)/12, (high*11)/12, wide, high, paint);
}
public int getColor(int r, int g, int b){
int red = (r << 16) & 0x00ff0000;
int green = (g << 8) & 0x0000ff00;
int blue = b & 0x000000ff;
return 0xff000000 | red | green | blue;
}
}
public boolean onTouchEvent(MotionEvent me){
int type = me.getActionMasked();
switch(type){
case MotionEvent.ACTION_DOWN:
cx = (int)me.getX();
cy = (int)me.getY();
doPlay = true;
return true;
case MotionEvent.ACTION_UP:
doPlay = false;
if(cx > (wide * 11)/12 && cy > (high * 11)/12){
if(++waveType>3)waveType=0;
}
return true;
case MotionEvent.ACTION_MOVE:
cx = (int)me.getX();
cy = (int)me.getY();
return true;
default:
return super.onTouchEvent(me);
}
}
public double duration = 1;
public int sampleRate = 8000;
public int numSamples = (int)duration * sampleRate;
public final double[] samples = new double[numSamples];
public final byte[] generatedSnd = new byte[numSamples * 2];
public Handler handler = new Handler();
AudioTrack track;
public void genTone(int freq){
float period = (float)sampleRate / (float)freq;
for(int i = 0; i<numSamples; i++){
switch(waveType){
case 0:
samples[i] = Math.sin(2 * i * Math.PI / period);
break;
case 1:
samples[i] = (int)(i/period) % 2 == 0 ? -1 : 1;
break;
case 2:
samples[i] = (i % period)/period;
break;
default:
samples[i] = ( 2 * Math.asin(Math.sin(2 * Math.PI * i / period)) / Math.PI);
}
}
int idx = 0;
int efcx = cx;
if(cx>wide/2)efcx = wide-cx;
int volume = 32767 * 2 * efcx/wide;
for (final double dVal : samples) {
final short val = (short) ((dVal) * volume);
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
public void playSound(){
track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length, AudioTrack.MODE_STATIC);
track.write(generatedSnd, 0, generatedSnd.length);
track.play();
}
}
So... how can I make this app function properly without "stop working"? Is there something in this class I should change? If you have an answer please do not be vague, show me the coding problem and how to fix it. Thank you.
I had to check that track.getState() wasn't STATE_UNINITIALIZED and that track.getPlayState() wasn't PLAYSTATE_STOPPED before calling stop().
I have made some progress detecting a specific kind of object. Actually a card, just like any other in your wallet.
Now I'm stuck with deskewing the photo. See:
The blue (rounded) rectangle represents the detected contour.
The purple rotate rectangle represents a RotatedRect extracted from the detected contour.
The green line is just the bounding box.
Well I need neither of those rectangles. The rectangles both have 90 degree corners. Which won't get me the perspective.
My question:
How can I get as accurate as possible all quadrangle corners from a contour?
I have created a class Quadrangle which creates the quadrangle of the 4 most largest connected polygon vertices which will intersect each other at some point. This will work in nearly any case.
If you use this code, remember to adjust the width and height in Quadrangle.warp. Note that it isn't 100% complete, the first and last polygon vertices won't be connected if they may be connect for example.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
class Line {
public Point offset;
public double angle;
public Line(Point offset, double angle) {
this.offset = offset.clone();
this.angle = angle;
}
public Point get(int length) {
Point result = offset.clone();
result.x += Math.cos(angle) * length;
result.y += Math.sin(angle) * length;
return result;
}
public Point getStart() {
return get(-5000);
}
public Point getEnd() {
return get(5000);
}
public void scale(double factor) {
offset.x *= factor;
offset.y *= factor;
}
public static Point intersect(Line l1, Line l2) {
return getLineLineIntersection(l1.getStart().x, l1.getStart().y, l1.getEnd().x, l1.getEnd().y,
l2.getStart().x, l2.getStart().y, l2.getEnd().x, l2.getEnd().y
);
}
public static Point getLineLineIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
double det1And2 = det(x1, y1, x2, y2);
double det3And4 = det(x3, y3, x4, y4);
double x1LessX2 = x1 - x2;
double y1LessY2 = y1 - y2;
double x3LessX4 = x3 - x4;
double y3LessY4 = y3 - y4;
double det1Less2And3Less4 = det(x1LessX2, y1LessY2, x3LessX4, y3LessY4);
if (det1Less2And3Less4 == 0){
// the denominator is zero so the lines are parallel and there's either no solution (or multiple solutions if the lines overlap) so return null.
return null;
}
double x = (det(det1And2, x1LessX2,
det3And4, x3LessX4) /
det1Less2And3Less4);
double y = (det(det1And2, y1LessY2,
det3And4, y3LessY4) /
det1Less2And3Less4);
return new Point(x, y);
}
protected static double det(double a, double b, double c, double d) {
return a * d - b * c;
}
}
class LineSegment extends Line implements Comparable {
public double length;
public LineSegment(Point offset, double angle, double length) {
super(offset, angle);
this.length = length;
}
public void melt(LineSegment segment) {
Point point = new Point();
point.x += Math.cos(angle) * length;
point.y += Math.sin(angle) * length;
point.x += Math.cos(segment.angle) * segment.length;
point.y += Math.sin(segment.angle) * segment.length;
angle = Math.atan2(point.y, point.x);
offset.x = (offset.x * length + segment.offset.x * segment.length) / (length + segment.length);
offset.y = (offset.y * length + segment.offset.y * segment.length) / (length + segment.length);
length += segment.length;
}
#Override
public int compareTo(Object other) throws ClassCastException {
if (!(other instanceof LineSegment)) {
throw new ClassCastException("A LineSegment object expected.");
}
return (int) (((LineSegment) other).length - this.length);
}
}
class Quadrangle {
static int
TOP = 0,
RIGHT = 1,
BOTTOM = 2,
LEFT = 3;
public Line[] lines = new Line[4];
public Quadrangle() {
}
private static double getAngle(Point p1, Point p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x);
}
private static double getLength(Point p1, Point p2) {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
private static double roundAngle(double angle) {
return angle - (2*Math.PI) * Math.round(angle / (2 * Math.PI));
}
public static Quadrangle fromContour(MatOfPoint contour) {
List<Point> points = contour.toList();
List<LineSegment> segments = new ArrayList<>();
// Create line segments
for (int i = 0; i < points.size(); i++) {
double a = getAngle(points.get(i), points.get((i + 1) % points.size()));
double l = getLength(points.get(i), points.get((i + 1) % points.size()));
segments.add(new LineSegment(points.get(i), a, l));
}
// Connect line segments
double angleDiffMax = 2 * Math.PI / 100;
List<LineSegment> output = new ArrayList<>();
for (LineSegment segment : segments) {
if (output.isEmpty()) {
output.add(segment);
} else {
LineSegment top = output.get(output.size() - 1);
double d = roundAngle(segment.angle - top.angle);
if (Math.abs(d) < angleDiffMax) {
top.melt(segment);
} else {
output.add(segment);
}
}
}
Collections.sort(output);
Quadrangle quad = new Quadrangle();
for (int o = 0; o < 4; o += 1) {
for (int i = 0; i < 4; i++) {
if (Math.abs(roundAngle(output.get(i).angle - (2 * Math.PI * o / 4))) < Math.PI / 4) {
quad.lines[o] = output.get(i);
}
}
}
return quad;
}
public void scale(double factor) {
for (int i = 0; i < 4; i++) {
lines[i].scale(factor);
}
}
public Mat warp(Mat src) {
Mat result = src.clone();
Core.line(result, lines[TOP].get(-5000), lines[TOP].get(5000), new Scalar(200, 100, 100), 8);
Core.line(result, lines[RIGHT].get(-5000), lines[RIGHT].get(5000), new Scalar(0, 255, 0), 8);
Core.line(result, lines[BOTTOM].get(-5000), lines[BOTTOM].get(5000), new Scalar(255, 0, 0), 8);
Core.line(result, lines[LEFT].get(-5000), lines[LEFT].get(5000), new Scalar(0, 0, 255), 8);
Point p = Line.intersect(lines[TOP], lines[LEFT]);
System.out.println(p);
if (p != null) {
Core.circle(result, p, 30, new Scalar(0, 0, 255), 8);
}
double width = 1400;
double height = width / 2.15;
Point[] srcProjection = new Point[4], dstProjection = new Point[4];
srcProjection[0] = Line.intersect(lines[TOP], lines[LEFT]);
srcProjection[1] = Line.intersect(lines[TOP], lines[RIGHT]);
srcProjection[2] = Line.intersect(lines[BOTTOM], lines[LEFT]);
srcProjection[3] = Line.intersect(lines[BOTTOM], lines[RIGHT]);
dstProjection[0] = new Point(0, 0);
dstProjection[1] = new Point(width - 1, 0);
dstProjection[2] = new Point(0, height - 1);
dstProjection[3] = new Point(width - 1, height - 1);
Mat warp = Imgproc.getPerspectiveTransform(new MatOfPoint2f(srcProjection), new MatOfPoint2f(dstProjection));
Mat rotated = new Mat();
Size size = new Size(width, height);
Imgproc.warpPerspective(src, rotated, warp, size, Imgproc.INTER_LINEAR);
return rotated;
}
}