https://doi.org/10.5201/ipol.2017.209
Raw File
Tip revision: 0a2766f5dec8a9683f9baa9010aa47f168afea6e authored by Software Heritage on 02 November 2017, 00:00:00 UTC
ipol: Deposit 1313 in collection ipol
Tip revision: 0a2766f
crop_and_zoom.cpp
// This program is free software: you can use, modify and/or redistribute it
// under the terms of the simplified BSD Lcense. You should have received a
// copy of this license along this program. If not, see
// <http://www.opensource.org/licenses/bsd-license.html>.
//
// Copyright (C) 2017, Javier Sánchez Pérez <jsanchez@ulpgc.es>
// All rights reserved.


#include "crop_and_zoom.h"
#include "direct_method/transformation.h"
#include "sutherland_hodgman.h"


#include <stdio.h>
#include <stack>

/**
  *
  * Computes the inscribed rectangle by updating the corners
  * according to the projection of the image corners
  *
**/
void fast_rectangle(
  float *H,    //set of transformations to be cropped
  int nx,      //number of columns
  int ny,      //number of rows
  int nframes, //number of frames
  int nparams, //type of transformation
  float &x1,   //x coordinate of top-left corner
  float &y1,   //y coordinate of top-left corner
  float &x2,   //x coordinate of bottom-right corner
  float &y2    //y coordinate of bottom-right corner
)
{  
  float *H_1=new float[nparams];

  x1=y1=0; x2=nx-1; y2=ny-1;

  //find the inside common rectangle
  for(int i=0;i<nframes;i++)
  { 
    inverse_transform(&(H[i*nparams]), H_1, nparams);
    float xp, yp;

    //project the image corners and store the limits
    project(0,0,H_1,xp,yp,nparams);
    if(xp>x1) x1=xp;
    if(yp>y1) y1=yp;

    project(nx-1,0,H_1,xp,yp,nparams);
    if(xp<x2) x2=xp;
    if(yp>y1) y1=yp;

    project(0,ny-1,H_1,xp,yp,nparams);
    if(xp>x1) x1=xp;
    if(yp<y2) y2=yp;

    project(nx-1,ny-1,H_1,xp,yp,nparams);
    if(xp<x2) x2=xp;
    if(yp<y2) y2=yp;
  }

  delete []H_1;
}



/**
  *
  * Find the intersecting polygon to all the homographies 
  *
**/
poly find_intersecting_polygon(
  float *H,    //set of transformations to be cropped
  int nx,      //number of columns
  int ny,      //number of rows
  int nframes, //number of frames
  int nparams  //type of transformation
)
{
  float *H_1=new float[nparams];

  poly clipper=poly_new();
  clipper->len=4;
  clipper->alloc=0;
  clipper->v=new vec_t[4];
  clipper->v[0].x=0   ; clipper->v[0].y=0; 
  clipper->v[1].x=nx-1; clipper->v[1].y=0;
  clipper->v[2].x=nx-1; clipper->v[2].y=ny-1; 
  clipper->v[3].x=0   ; clipper->v[3].y=ny-1;

  //find the inside common rectangle
  for(int i=0;i<nframes;i++)
  {
    inverse_transform(&(H[i*nparams]), H_1, nparams);

    //project the image corners and store the limits
    vec_t s[4]; 
    project(0, 0, H_1,s[0].x,s[0].y,nparams);
    project(nx-1,0, H_1,s[1].x,s[1].y,nparams);
    project(nx-1, ny-1,H_1,s[2].x,s[2].y,nparams);
    project(0,ny-1,H_1,s[3].x,s[3].y,nparams);

    poly_t subject = {4, 0, s};

    //compute the intersection of both polygons using sutherland-hodgman method
    poly res=poly_clip(&subject, clipper);

    poly_free(clipper);
    clipper=res;
  }

  delete []H_1;

  return clipper;
}


/**
  *
  * Create a binary image with 1 inside the common
  * region and 0 in the empty regions 
  *
**/
void create_binary_image(
  poly polygon, //intersecting polygon
  int  *I,      //output binary image with polygon mask
  int  nx,      //number of columns
  int  ny       //number of rows
)
{
  for(int i=0; i<nx*ny; i++) I[i]=0;

  float ymin=ny, ymax=0;

  //find the y-limits of the polygon
  for(int i=0; i<polygon->len; i++)
  {
    if(ymin>polygon->v[i].y) ymin=polygon->v[i].y;
    if(ymax<polygon->v[i].y) ymax=polygon->v[i].y;
  }

  //intersect with the polygon all the rows in between and fill
  for(int y=(int)(ymin+0.5); y<(int)(ymax+0.5); y++)
  {
    float xmin=-1, xmax=-1;

    int i=0;
    int j=polygon->len-1;
    int intersections=0;

    //find the intersection of the current row with the polygon
    //since the polygon is convex we need to find two contacts
    while(i<polygon->len && intersections<2)
    {
      //intersection test
      if((polygon->v[j].y>=y && polygon->v[i].y<=y)||
         (polygon->v[j].y<=y && polygon->v[i].y>=y))
      {
        float x1=polygon->v[j].x;
        float y1=polygon->v[j].y;
        float x2=polygon->v[i].x;
        float y2=polygon->v[i].y;

        //if line is coincident, fill it completely
        if((int)(y1+0.5)==(int)(y2+0.5))
        {
          xmin=x1; xmax=x2;
          intersections=2;
        }
        else if(intersections==0)
          xmin=(y-y1)*(x2-x1)/(y2-y1)+x1;
        else
          xmax=(y-y1)*(x2-x1)/(y2-y1)+x1;

        intersections++;
      }

      j=i;
      i++;
    }

    //swap x limits if not ordered
    if(xmin>xmax)
    {
      float tmp=xmin;
      xmin=xmax;
      xmax=tmp;
    }

    //fill the row 
    if(xmin>=0 && xmin<nx && xmax>=0 && xmax<nx)
      for(int x=(int)xmin; x<=(int)xmax; x++) I[y*nx+x]=255;
  }
}


/**
  *
  * Find the rectangle from the binary image
  * Method of maximum rectangle beneath a histogram
  *
**/
void find_rectangle(
  int *I,    //binary image with polygon mask
  int nx,    //number of columns of the image
  int ny,    //number of rows of the image
  float &x1, //computed x coordinate of top-left corner of the rectangle
  float &y1, //computed y coordinate of top-left corner of the rectangle
  float &x2, //computed x coordinate of bottom-right corner of the rectangle
  float &y2  //computed y coordinate of bottom-right corner of the rectangle
)
{
  //initialize histogram array to 0
  int *H=new int[nx*ny]();

  //create the histogram array
  for(int i=1; i<ny; i++)
    for(int j=0; j<nx; j++)
      if(I[i*nx+j])
        H[i*nx+j]=1+H[(i-1)*nx+j];

  long int max_area = 0;

  //find the maximum area axis-parallel rectangle 
  for(int i=1; i<ny; i++)
  { 
    int *L=new int[nx]();
    int *R=new int[nx]();
    std::stack<int> stack; 

    //find the left limit for this position of the histogram
    for(int j=0; j<nx; j++)
    {
      while (!stack.empty() && H[i*nx+j]<=H[i*nx+stack.top()])
        stack.pop();

      int t;
      if(stack.empty())
        t=0;
      else
        t=stack.top();

      L[j]=t;
      stack.push(j);
    }

    //empty stack 
    std::stack<int> empty;
    std::swap(stack, empty);

    //find the right limit for this position of the histogram
    for (int j=nx-1; j>=0; j--)
    {
      while(!stack.empty() && H[i*nx+j]<=H[i*nx+stack.top()])
        stack.pop();

      int t;
      if(stack.empty())
        t=nx-1;
      else
        t=stack.top();

      R[j]=t;
      stack.push(j);
    }

    //calculate final area in the row and find the maximum
    for (int j=0; j<nx; j++)
    {
      long int A=H[i*nx+j]*(R[j]-L[j]);
      if (A>max_area)
      {
        max_area=A;
        x1=L[j]+1;
        y1=i-H[i*nx+j];
        x2=R[j];
        y2=i;
      }
    }
    delete []L;
    delete []R;
  }

  delete []H;
}


/**
  *
  * Find the optimal inscribed rectangle 
  *
**/
void accurate_rectangle(
  float *H,    //set of transformations to be cropped
  int nx,      //number of columns
  int ny,      //number of rows
  int nframes, //number of frames
  int nparams, //type of transformation
  float &x1,   //x coordinate of top-left corner
  float &y1,   //y coordinate of top-left corner
  float &x2,   //x coordinate of bottom-right corner
  float &y2,   //y coordinate of bottom-right corner
  int verbose  //verbose mode
)
{
  if(verbose)
    printf("   Calculating intersection polygon\n");
  poly polygon=find_intersecting_polygon(H,nx,ny,nframes,nparams);

  int *I=new int[nx*ny];

  if(verbose)
    printf("   Creating binary image with polygon mask\n");
  create_binary_image(polygon, I, nx, ny);

  if(verbose)
    printf("   Computing the inscribed rectangle\n");
  find_rectangle(I, nx, ny, x1, y1, x2, y2);  

  poly_free(polygon);
  delete []I;
}

/**
  *
  * Computes the transformation neccessary to remove the 
  * empty regions (similarity: translate + scale)
  *
**/
void apply_crop_zoom_transform(
  float *H,
  int nx,
  int ny,
  int nframes,
  int nparams,
  float xx1,
  float yy1,
  float xx2, 
  float yy2,
  int verbose
)
{
  //round off
  int x1=(int) (xx1+.5);
  int y1=(int) (yy1+.5);
  int x2=(int) (xx2-.1);
  int y2=(int) (yy2-.1);

  //compute necessary zoom and translation to apply to the transformations
  float s, s1=(float) (y2-y1)/ny;
  float s2=(float) (x2-x1)/nx;

  if(s1<s2)
    s=s1;
  else
    s=s2;

  //translate the center of the rectangle to the origin
  float dx=(float) (x1+x2)/2.;
  float dy=(float) (y1+y2)/2.;
  float dxx=(float) nx/2.;
  float dyy=(float) ny/2.;

  //create a similarity transform for composition
  float matrix[9]={s,0.,dx-s*dxx,0.,s,dy-s*dyy,0.,0.,1.};
  float *T=new float[nparams];

  matrix2params(matrix,T,nparams);

  if(verbose)
  {
    float xp1, xp2, yp1, yp2;
    project(0,0,T,xp1,yp1,nparams);
    project(nx,ny,T,xp2,yp2,nparams);

    printf("   -Frame size: (0, 0)-(%d, %d)\n", nx, ny);
    printf("   -Crop rectangle: (%d, %d)-(%d, %d)\n", x1, y1, x2, y2);
    printf("   -Real rectangle: (%f, %f)-(%f, %f)\n", xp1, yp1, xp2, yp2);
    printf("   -Transformation: zoom=%f, translation=(%f, %f)\n", s, dx, dy);
  }

  for(int i=0;i<nframes;i++) 
    //modify the smoothing transformations
    compose_transform(&(H[i*nparams]), T, &(H[i*nparams]),nparams);

  delete []T;
}


/**
  *
  * Function for dealing with empty regions after stabilization
  *
**/
void crop_and_zoom(
  float *H,    //set of transformations to be cropped
  int nx,      //number of columns
  int ny,      //number of rows
  int nframes, //number of frames
  int nparams, //type of transformation
  int type,    //type of filling strategy
  int verbose  //verbose mode
)
{
  //test if the transformations has enough degrees of freedom
  if(nparams<=3)
  {
    if(verbose)
      printf("   Cannot apply Crop&Zoom to translation "
             "or Euclidean transforms\n");
    return;
  }

  //coordinates of the cropping rectangle
  float x1, y1, x2, y2;

  //calculating the cropping rectangle following one strategy
  switch(type)
  {
    case NO_POSTPROCESS:
      if(verbose)
        printf("  No post-processing:\n");
      break;
    case FAST_CROP_ZOOM:
      if(verbose)
        printf("  Fast Crop and Zoom:\n");
      fast_rectangle(H, nx, ny, nframes, nparams, x1, y1, x2, y2);
      //compute the new transformation for cropping and zooming
      apply_crop_zoom_transform(H, nx, ny, nframes, nparams, x1, y1, x2, y2, verbose);
      break;
    case CROP_ZOOM:
      if(verbose)
        printf("  Crop and Zoom:\n");
      accurate_rectangle(H, nx, ny, nframes, nparams, x1, y1, x2, y2, verbose);
      //compute the new transformation for cropping and zooming
      apply_crop_zoom_transform(H, nx, ny, nframes, nparams, x1, y1, x2, y2, verbose);
      break;
    case NEIGHBOR_INPAINT:
      if(verbose)
        printf("  Neighbor inpainting (not implemented yet):\n");
      break;
  }
}
back to top