0

I am having three image of pan card for testing skew of image using emgucv and c#.

1st image which is on top Detected 180 degree working properly.

2nd image which is in middle Detected 90 dgree should detected as 180 degree.

3rd image Detected 180 degree should detected as 90 degree.

Detected 180 degree working properly Detected 90 should detected as 180

Detected 180 should detected as 90

One observation I am having that i wanted to share here is when i crop unwanted part of image from up and down side of pan card using paint brush, it gives me expected result using below mention code.

Now i wanted to understand how i can remove the unwanted part using programming. I have played with contour and roi but I am not able to figure out how to fit the same. I am not able to understand whether emgucv itself selects contour or I have to do something.

Please suggest any suitable code example.

Please check code below for angle detection and please help me. Thanks in advance.

imgInput = new Image<Bgr, byte>(impath);
          Image<Gray, Byte> img2 = imgInput.Convert<Gray, Byte>();
          Bitmap imgs;
          Image<Gray, byte> imgout = imgInput.Convert<Gray, byte>().Not().ThresholdBinary(new Gray(50), new Gray(125));
          VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
          Emgu.CV.Mat hier = new Emgu.CV.Mat();
          var blurredImage = imgInput.SmoothGaussian(5, 5, 0 , 0);
          CvInvoke.AdaptiveThreshold(imgout, imgout, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 5, 45);

          CvInvoke.FindContours(imgout, contours, hier, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
          if (contours.Size >= 1)
          {
              for (int i = 0; i <= contours.Size; i++)
              {

                  Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
                  RotatedRect box = CvInvoke.MinAreaRect(contours[i]);
                  PointF[] Vertices = box.GetVertices();
                  PointF point = box.Center;
                  PointF edge1 = new PointF(Vertices[1].X - Vertices[0].X, Vertices[1].Y - Vertices[0].Y);
                  PointF edge2 = new PointF(Vertices[2].X - Vertices[1].X, Vertices[2].Y - Vertices[1].Y);
                  double r = edge1.X + edge1.Y;
                  double edge1Magnitude = Math.Sqrt(Math.Pow(edge1.X, 2) + Math.Pow(edge1.Y, 2));
                  double edge2Magnitude = Math.Sqrt(Math.Pow(edge2.X, 2) + Math.Pow(edge2.Y, 2));
                  PointF primaryEdge = edge1Magnitude > edge2Magnitude ? edge1 : edge2;
                  double primaryMagnitude = edge1Magnitude > edge2Magnitude ? edge1Magnitude : edge2Magnitude;
                  PointF reference = new PointF(1, 0);
                  double refMagnitude = 1;
                  double thetaRads = Math.Acos(((primaryEdge.X * reference.X) + (primaryEdge.Y * reference.Y)) / (primaryMagnitude * refMagnitude));
                  double thetaDeg = thetaRads * 180 / Math.PI;
                  imgInput = imgInput.Rotate(thetaDeg, new Bgr());
                  imgout = imgout.Rotate(box.Angle, new Gray());
                  Bitmap bmp = imgout.Bitmap;
                  break;
              }

          }
manthan
  • 102
  • 1
  • 15

1 Answers1

3

The Problem

Let us start with the problem before the solution:

Your Code

When you submit code, asking for help, at least make some effort to "clean" it. Help people help you! There's so many lines of code here that do nothing. You declare variables that are never used. Add some comments that let people know what it is that you think your code should do.

Bitmap imgs;
var blurredImage = imgInput.SmoothGaussian(5, 5, 0, 0);
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
PointF point = box.Center;
double r = edge1.X + edge1.Y;
// Etc

Adaptive Thresholding

The following line of code produces the following images:

 CvInvoke.AdaptiveThreshold(imgout, imgout, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 5, 45);

Image 1

Image1

Image 2

Image2

Image 3

Image3

Clearly this is not what you're aiming for since the primary contour, the card edge, is completely lost. As a tip, you can always use the following code to display images at runtime to help you with debugging.

CvInvoke.NamedWindow("Output");
CvInvoke.Imshow("Output", imgout);
CvInvoke.WaitKey();

The Soltuion

Since your in example images the card is primarily a similar Value (in the HSV sense) to the background. I do not think simple gray scale thresholding is the correct approach in this case. I purpose the following:

Algorithm

  1. Use Canny Edge Detection to extract the edges in the image.

    EdgesImage

  2. Dilate the edges so as the card content combines.

    DilatedImage

  3. Use Contour Detection to filter for the combined edges with the largest bounding.

    PrimaryContour

  4. Fit this primary contour with a rotated rectangle in order to extract the corner points.

  5. Use the corner points to define a transformation matrix to be applied using WarpAffine.

    MetaImage

  6. Warp and crop the image.

    OutputImage

The Code

You may wish to experiment with the parameters of the Canny Detection and Dilation.

// Working Images
Image<Bgr, byte> imgInput = new Image<Bgr, byte>("Test1.jpg");
Image<Gray, byte> imgEdges = new Image<Gray, byte>(imgInput.Size);
Image<Gray, byte> imgDilatedEdges = new Image<Gray, byte>(imgInput.Size);
Image<Bgr, byte> imgOutput;

// 1. Edge Detection
CvInvoke.Canny(imgInput, imgEdges, 25, 80);

// 2. Dilation
CvInvoke.Dilate(
    imgEdges,
    imgDilatedEdges,
    CvInvoke.GetStructuringElement(
        ElementShape.Rectangle,
        new Size(3, 3),
        new Point(-1, -1)),
    new Point(-1, -1),
    5,
    BorderType.Default,
    new MCvScalar(0));

// 3. Contours Detection
VectorOfVectorOfPoint inputContours = new VectorOfVectorOfPoint();
Mat hierarchy = new Mat();
CvInvoke.FindContours(
    imgDilatedEdges,
    inputContours,
    hierarchy,
    RetrType.External,
    ChainApproxMethod.ChainApproxSimple);
VectorOfPoint primaryContour = (from contour in inputContours.ToList()
                                orderby contour.GetArea() descending
                                select contour).FirstOrDefault();

// 4. Corner Point Extraction
RotatedRect bounding = CvInvoke.MinAreaRect(primaryContour);
PointF topLeft = (from point in bounding.GetVertices()
                  orderby Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(point.Y, 2))
                  select point).FirstOrDefault();
PointF topRight = (from point in bounding.GetVertices()
                  orderby Math.Sqrt(Math.Pow(imgInput.Width - point.X, 2) + Math.Pow(point.Y, 2))
                  select point).FirstOrDefault();
PointF botLeft = (from point in bounding.GetVertices()
                  orderby Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(imgInput.Height - point.Y, 2))
                  select point).FirstOrDefault();
PointF botRight = (from point in bounding.GetVertices()
                   orderby Math.Sqrt(Math.Pow(imgInput.Width - point.X, 2) + Math.Pow(imgInput.Height - point.Y, 2))
                   select point).FirstOrDefault();
double boundingWidth = Math.Sqrt(Math.Pow(topRight.X - topLeft.X, 2) + Math.Pow(topRight.Y - topLeft.Y, 2));
double boundingHeight = Math.Sqrt(Math.Pow(botLeft.X - topLeft.X, 2) + Math.Pow(botLeft.Y - topLeft.Y, 2));
bool isLandscape = boundingWidth > boundingHeight;

// 5. Define warp crieria as triangles              
PointF[] srcTriangle = new PointF[3];
PointF[] dstTriangle = new PointF[3];
Rectangle ROI;
if (isLandscape)
{
    srcTriangle[0] = botLeft;
    srcTriangle[1] = topLeft;
    srcTriangle[2] = topRight;
    dstTriangle[0] = new PointF(0, (float)boundingHeight);
    dstTriangle[1] = new PointF(0, 0);
    dstTriangle[2] = new PointF((float)boundingWidth, 0);
    ROI = new Rectangle(0, 0, (int)boundingWidth, (int)boundingHeight);
}
else
{
    srcTriangle[0] = topLeft;
    srcTriangle[1] = topRight;
    srcTriangle[2] = botRight;
    dstTriangle[0] = new PointF(0, (float)boundingWidth);
    dstTriangle[1] = new PointF(0, 0);
    dstTriangle[2] = new PointF((float)boundingHeight, 0);
    ROI = new Rectangle(0, 0, (int)boundingHeight, (int)boundingWidth);
}
Mat warpMat = new Mat(2, 3, DepthType.Cv32F, 1);
warpMat = CvInvoke.GetAffineTransform(srcTriangle, dstTriangle);

// 6. Apply the warp and crop
CvInvoke.WarpAffine(imgInput, imgInput, warpMat, imgInput.Size);
imgOutput = imgInput.Copy(ROI);
imgOutput.Save("Output1.bmp");

Two extension methods are used:

static List<VectorOfPoint> ToList(this VectorOfVectorOfPoint vectorOfVectorOfPoint)
{
    List<VectorOfPoint> result = new List<VectorOfPoint>();
    for (int contour = 0; contour < vectorOfVectorOfPoint.Size; contour++)
    {
        result.Add(vectorOfVectorOfPoint[contour]);
    }
    return result;
}

static double GetArea(this VectorOfPoint contour)
{
    RotatedRect bounding = CvInvoke.MinAreaRect(contour);
    return bounding.Size.Width * bounding.Size.Height;
}

Outputs

OutputImage1 OutputImage2 OutputImage3

Meta Example

MetaImage

George Kerwood
  • 1,248
  • 8
  • 19
  • I will take care while posting code. Sorry for the inconvenience. – manthan Jun 24 '20 at 20:19
  • That's okay. It's only that you will get better advise, faster, if it is clear what you are trying to do. Most people won't bother to help if it's difficult to understand the problem. I hope my answer will work for you. – George Kerwood Jun 24 '20 at 20:25
  • Its working properly. Thank you so much for help. You saved me. Before your answer I was working on following link. https://www.learnopencv.com/image-alignment-feature-based-using-opencv-c-python/ Let me know whether i was on right track? – manthan Jun 24 '20 at 20:52
  • Before your answer I was working on following link for this issue. https://www.learnopencv.com/image-alignment-feature-based-using-opencv-c-python/ Please Let me know whether i was on right track? Opencv seems quite difficult i think. – manthan Jun 24 '20 at 20:58
  • Can you tell me how you figured out everything? I banging head on wall everyday. – manthan Jun 24 '20 at 21:01
  • That article looks very good. In fact it is probably a better solution that I have provided as it will also resolved the perspective, not just reposition the card. I might spend some time and implement it for my own education. I'll post it here if I do. As for how I figured it out, I have experience with machine vision, but I've also learnt a lot from this community: WarpAffine and Dilate are methods I learnt from people posting better answer than mine on other questions. Now I have extra tools to use on new problems! – George Kerwood Jun 25 '20 at 04:58
  • OpenCV is hard, only in that image processing is complex, and it's not very well documented. My number one tip is to draw and display the image at every step so you can see what is happening. Threshold and image? Draw it! Detect some contours? Draw them! It's important you truly understand what effect each line of code has, even if you don't understand how it works. Keep practicing, and keep asking questions with as much thought and effort as you can. Even if everything you've done is "wrong", your effort in a good question will motivate people to help. @mBangali – George Kerwood Jun 25 '20 at 05:03
  • I found this on stackoverflow. you can refer https://stackoverflow.com/a/57007608/7805023 – manthan Jun 25 '20 at 15:25
  • @GeorgeKerwood Hello, Im trying your code but Im getting the following error: "VectorOfVectorOfPoint" does not contain a definition for "ToList" or an accessible "ToList" extension method that accepts a first argument of type "VectorOfVectorOfPoint" (missing any directive or assembly reference?) – User1899289003 Jul 13 '20 at 17:04
  • In my answer there are two extension methods you need to include. They're in the very last block of code. @User1899289003 – George Kerwood Jul 13 '20 at 17:05
  • @GeorgeKerwood Hi! Actually I get that error even adding those methods, [here](https://dotnetfiddle.net/aU4YQu) is the code I'm trying. (Basically calling your code by clicking a button). – User1899289003 Jul 13 '20 at 19:41
  • @User1899289003 This should throw the error that extension methods must be declared within static classes. – George Kerwood Jul 13 '20 at 20:03
  • @GeorgeKerwood So, what can I do? I tried changing to public List<>, static List<>, private List<> and even with List<> method and I get the same error. – User1899289003 Jul 13 '20 at 20:17
  • @User1899289003 No, the declaring class must be Static. In your example this would be `public static class Program`. Also you can't have a button click in a Console application... just put the code in `Main()`. – George Kerwood Jul 13 '20 at 20:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/217761/discussion-between-user1899289003-and-george-kerwood). – User1899289003 Jul 13 '20 at 21:17