ttOM123

How to crop to document edges in photo?

Recommended Posts

I didn't use diagrams; I just solved the equations. First, I knew that it would be a homogeneous matrix, since that's a well-known fact, so I just had to find the matrix.

 

I assumed a solution of the form:

 / Mx * x1 - x0      My * x3 - x0      x0 \
|                                          |
|  Mx * y1 - y0      My * y3 - y0      y0  |
|                                          |
 \    Mx - 1            My - 1         1  /

That may not be an obvious choice at first glance, but I chose it because you can quickly verify that it gives the correct results for (0,0)->(x0,y0), (1,0)->(x1,y1), and (0,1)->(x3,y3). So I just need to find Mx, My so that (1,1)->(x2, y2).

 

I need:

(Mx * x1 + My * x3 - x0) / (Mx + My - 1) = x2

(Mx * y1 + My * y3 - y0) / (Mx + My - 1) = y2

 

Which is:

Mx * (x1 - x2) + My * (x3 - x2)  = x0 - x2

Mx * (y1 - y2) + My * (y3 - y2) = y0 - y2

 

Those are just simultaneous equations that can be solved by Cramer's Rule.

 

EDIT: Note I didn't solve the same problem (sort of) solved in the PDF file. That was a quadrilateral to another quadrilateral. I just solved a unit square to a quadrilateral. The quadrilateral to quadrilateral could be solved by finding the matrix that maps a quadrilateral to a unit square; then the solution is simply to map the first quadrilateral to a unit square, then map the unit square to the second quadrilateral. That second mapping can be found by inverting the matrix for the first method. Maybe I'll add a comment later explaining how that can be done fairly easily.

 

Thanks for your explanation, I haven't got time to go though it in depth at the moment but I appreciate it.

Edited by ttOM123

Share this post


Link to post
Share on other sites

Your algorithm is backwards. When looping though the y (rows) and x (columns) you should be calculating the destination pixel for that x,y address.

You might want to review: http://boltbait.com/pdn/CodeLab/help/overview.asp

 

Yeah I found that out, code should be this which produces the image I posted above, the transformation maps from the selected quadrilateral to the region bounded by the canvas. So this function is nearly there, just need to change to the correct aspect ratio. To run it, copy the picture in first post into paint.net, and copy into Codelab script.

 

Another question, is it possible to enlarge or shrink the canvas size from this script? Or even better, produce the output onto a new image where the script defines the canvas size?

 

Still cannot get the "if" statement to work so to only grab pixels inside the canvas.

            //if ((XX > rect.Left) && (XX < rect.Right)) {
                   //if ((YY > rect.Top) && (YY < rect.Bottom)) { 
                        //dst[x,y] = src[XX,YY];
                //}
            //}

To me, it looks like the boundaries are correct, took me a while to work out the origin in the top left of the canvas, not bottom left, and that the y axis is positive downwards. You don't need to check with this particular transformation as I'm always grabbing from inside the canvas, but it would be nice to have.

#region UICode
int Amount1=0;	//[0,100]Slider 1 Description
int Amount2=0;	//[0,100]Slider 2 Description
int Amount3=0;	//[0,100]Slider 3 Description
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    // Delete any of these lines you don't need
    Rectangle selection = EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
    int CenterX = ((selection.Right - selection.Left) / 2)+selection.Left;
    int CenterY = ((selection.Bottom - selection.Top) / 2)+selection.Top;
    ColorBgra PrimaryColor = (ColorBgra)EnvironmentParameters.PrimaryColor;
    ColorBgra SecondaryColor = (ColorBgra)EnvironmentParameters.SecondaryColor;
    int BrushWidth = (int)EnvironmentParameters.BrushWidth;

    ColorBgra CurrentPixel;
    double a = 0.300191;
    double b = -0.190286;
    double c = 509.000000;
    double d = 0.013433;
    double e = 0.408391;
    double f = 172.000000;
    double g = 0.000026;
    double h = -0.000291;

    double  X, Y;
    int XX, YY;
    
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            X = (a*x + b*y + c) / (g*x + h*y + 1);
            Y = (d*x + e*y + f) / (g*x + h*y + 1);
            XX =  Convert.ToInt32(X);
            YY =  Convert.ToInt32(Y);
            dst[x,y] = src[XX,YY];
            //if ((XX > rect.Left) && (XX < rect.Right)) {
                   //if ((YY > rect.Top) && (YY < rect.Bottom)) { 
                        //dst[x,y] = src[XX,YY];
                //}
            //}
        }
    }
}

Edited by ttOM123

Share this post


Link to post
Share on other sites

There's no need for the if statement; in fact, you certainly don't want it. You can read pixels from anywhere within the src buffer. If you couldn't, you wouldn't be able to write the plugin. Not allowing reads outside the rect boundaries would mean most of the reads you need to do wouldn't be done. The restriction is on writing to the dst buffer: in the Render routine, you can never write to the dst buffer outside the rect limits (and you can never write to the src buffer).

 

src and dst are the canvas; rect is not the canvas. It's a rather arbitrary region, callled an ROI, within the canvas that PDN has handed to a particular invocation of Render for processing. Read BoltBait's excellent tutorial, which he linked to earlier.

 

A change you should probably make is to replace "dst[x,y] = src[XX,YY]" with "dst[x,y] = "src.GetBilinearSample((float)X, (float)Y)." Instead of taking a point sample from the nearest pixel, GetBilinearSample linearly interpolates between the four surrounding pixels. For situations like in this plugin, that almost always produces a better result.

 

EDIT: Notice it's "src.GetBilinearSample((float)X, (float)Y)" with the doubles X and Y, not "src.GetBilinearSample((float)XX, (float)YY)" with the ints XX and YY. There's hardly ever a good reason to call GetBilinearSample with arguments that have been truncated or rounded to integer values.

 

Also, to answer your question: No, a plugin can't change the canvas size.

Edited by MJW

Share this post


Link to post
Share on other sites

Haha I can provide a dll which only does one photo :P.

I've been flat out. Haven't touched it for a week apart from MJW's advice which works nicely. The next hurdle is some method of entering the 4 coordinates of the quadrangle, which I haven't looked into yet. I was thinking of somehow clicking on the canvas 4 times so one can zoom in and be precise then run the plugin, so no menus hopefully.

Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.