zoukankan      html  css  js  c++  java
  • 【C#】身份证识别(二):提取目标区域图像

    目录

    完整项目地址:https://gitee.com/xgpxg/ICRS

    引言:

    为获得更好的识别效果,现对身份证含有个人信息区域进行提取,缩小识别范围,以此来提高识别精度和效率


    一、获取身份证号区域矩形

    身份证号区域矩形的获取,见上一篇文章:
    身份证识别(一):身份证号定位


    二、获取地址、出生年月、性别、名族、姓名区域矩形

    1. 获取号码区域位置

    在第一步得到的结果是一个旋转矩形RotatedRect,该矩形的属性如下:

    Angle:矩形旋转角度(相对水平方向),顺时针旋转值为正,逆时针旋转值为负。

    Center:矩形的中心

    Size:矩形的尺寸

    GetVertices():PointF[]类型,包含矩形的四个顶点坐标。

    有了这些信息之后就可以以该矩形(以下称为标准矩形)为基准计算其它区域的相对位置。

    2. 获取各个区域的包围矩形

    由于标准矩形的顶点顺序不确定,所以选用标准矩形的中心作为参考点,然后计算地址区域中心的偏移量。

    以住址区域为例:

    偏移量的计算:

    标准矩形的中心为rr.Center

    若旋转角度为0

        float w = (float)(rr.Size.Width * 0.8); //住址区宽度
        float h = (float)(rr.Size.Height * 1.7);//住址区高度
        float px = (float)(rr.Center.X - rr.Size.Height * 3.1);//住址区x坐标
        float py = (float)(rr.Center.Y - rr.Size.Height * 2.4);//住址区y坐标
        PointF center = new PointF(px,py);
        RotatedRect rect = new RotatedRect(center,new SizeF(w,h),rr.Angle);

    若旋转角度不为0

        float w = (float)(rr.Size.Width * 0.8 * Math.Cos(rr.Angle); //住址区宽度
        float h = (float)(rr.Size.Height * 1.7* Math.Cos(rr.Angle));//住址区高度

    三、剪切目标区域

    裁剪目标区域,即将目标矩形内的像素点复制到一幅与矩形相同大小,并保持水平的图像中,以方便进一步的识别。

    对于旋转角度为0的矩形,只要以左上角顶点为中心依次将矩形区域复制到新图像即可。

    对于倾斜的矩形,则需要先选取旋转中心,然后将矩形旋转至水平方向,然后在对像素点进行遍历复制。

    对于上一步得到的矩形,虽然GetVertices()可以获取四个顶点的坐标,但是无法确定顶点顺序,则无法确定旋转中心点,所以首先要对矩形顶点编号。我们按逆时针方向进行顶点编号。


    这样当Angle<0编号为①的顶点就是旋转中心,当Angle<0编号为①的顶点就是旋转中心。

    顶点编号主要代码:

         /// <summary>
        /// 矩形顶点编号
        /// </summary>
        /// <param name="pointfs"></param>
        /// <param name="angle"></param>
        /// <returns></returns>
        public static PointF[] RectCode(RotatedRect rect)
        {
    
        PointF[] p = rect.GetVertices();
        PointF[] pointfs = new PointF[4];
        pointfs[0] = p[0];
        pointfs[1] = p[0];
        pointfs[2] = p[0];
        pointfs[3] = p[0];
        //逆时针编号
        for (int i = 1; i < 4; i++)
        {
        if (p[i].X < p[i - 1].X)
        pointfs[0] = p[i];
        if (p[i].Y > p[i - 1].Y)
        pointfs[1] = p[i];
        if (p[i].X > p[i - 1].X)
        pointfs[2] = p[i];
        if (p[i].Y < p[i - 1].Y)
        pointfs[3] = p[i];
        }
    
        return pointfs;
        }
    

    然后新建一幅与矩形区域相同大小的图像:

         Image<Bgr, byte> newImg = new Image<Bgr, byte>(new Size((int)rect.Size.Width, (int)rect.Size.Height));

    因为要复制原始图像区域内的像素,所以并不是对原始图像旋转,而是对newImg内的点与原始图像内的点对应起来,进行一个映射。

    如上图所示,绿色矩形为newImg,蓝色矩形为身份证号区域,首先将将绿色矩形的坐标平移到红色虚线矩形处,然后再旋转虚线矩形的坐标,即完成了坐标映射。

    newImg的坐标为(x0,y0),img(原始图像)的坐标为(x,y)平移的偏移量为(Δx,Δy),旋转角度为angle,则(x0,y0)处的像素值可以表示为:

    (x0,y0).Color = rote(angle)[(x0+Δx,y0+Δy)].Color

    具体代码如下:

       public static Image<Bgr,byte> Rote(Image<Bgr,byte>img, RotatedRect rect)
        {
        PointF center = new PointF();
        PointF[] pointfs = RectCode(rect);
        if(rect.Angle < 0)
        {
        center = pointfs[0];
        }
        if(rect.Angle >= 0)
        {
        center = pointfs[3];
        }
        Image<Bgr, byte> output = new Image<Bgr, byte>(new Size((int)rect.Size.Width, (int)rect.Size.Height));
        int w = (int)rect.Size.Width;
        int h = (int)rect.Size.Height;
        for (int i = (int)center.X,m=0; i < w + (int)center.X; i++,m++)
        {
        for (int j = (int)center.Y, n = 0; j < h + (int)center.Y; j++,n++)
        {
        {
        Point p = PointRotate(center, new PointF(i,  j), -rect.Angle);
        if (p.X >= img.Size.Width)
        p.X = img.Size.Width - 1;
        if (p.Y >= img.Size.Height )
        p.Y = img.Size.Height  - 1;
        output[n, m] = img[p.Y, p.X];
        }
        }
        }
        if (Math.Abs(rect.Angle) > 45)
        {
        output =  output.Rotate(180, new Bgr(Color.White));
        }
        return output;
        }

    如此一来便提取出了各个区域的图像,下一步进行字符识别。

    四、提取效果


    附一: 获取各个区域的包围矩形的主要代码:

         /// <summary>
        /// 身份证号区域
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static RotatedRect IdRotatedRect(Image<Bgr, byte> img)
        {
        Image<Bgr, byte> a = new Image<Bgr, byte>(img.Size);
        VectorOfVectorOfPoint con = GetContours(BinImg(img));
        Point[][] con1 = con.ToArrayOfArray();
        PointF[][] con2 = Array.ConvertAll<Point[], PointF[]>(con1, new Converter<Point[], PointF[]>(PointToPointF));
        for (int i = 0; i < con.Size; i++)
        {
        RotatedRect rrec = CvInvoke.MinAreaRect(con2[i]);
        float w = rrec.Size.Width;
        float h = rrec.Size.Height;
        if (w / h > 6 && w / h < 10 && h > 20)
        {
    
        PointF[] pointfs = rrec.GetVertices();
        for (int j = 0; j < pointfs.Length; j++)
        {
        CvInvoke.Line(a, new Point((int)pointfs[j].X, (int)pointfs[j].Y), new Point((int)pointfs[(j + 1) % 4].X, (int)pointfs[(j + 1) % 4].Y), new MCvScalar(0, 0, 255, 255), 4);
    
        }
        return rrec;
        }
        }
        return new RotatedRect();
        }
        /// <summary>
        /// 地址区域
        /// </summary>
        /// <param name="rr"></param>
        /// <returns></returns>
        public static RotatedRect AddressRotatedRect(RotatedRect rr)
        {
        float w = (float)((rr.Size.Width * 0.8));
        float h = (float)(rr.Size.Height * 1.7);
        float px = (float)(rr.Center.X - rr.Size.Height * 3.1);
        float py = (float)(rr.Center.Y - rr.Size.Height * 2.4);
        PointF center = new PointF(px,py);
        RotatedRect rect = new RotatedRect(center,new SizeF(w,h),rr.Angle);
        return rect;
        }
        /// <summary>
        /// 年份区域
        /// </summary>
        /// <param name="rr"></param>
        /// <returns></returns>
        public static RotatedRect DateRotatedRect(RotatedRect rr)
        {
        float w = (float)(rr.Size.Width * 0.7);
        float h = (float)(rr.Size.Height * 1);
        float px = (float)(rr.Center.X - rr.Size.Height * 3.7);
        float py = (float)(rr.Center.Y - rr.Size.Height * 4);
        PointF center = new PointF(px, py);
        RotatedRect rect = new RotatedRect(center, new SizeF(w, h), rr.Angle);
        return rect;
        }
        /// <summary>
        /// 性别区域
        /// </summary>
        /// <param name="rr"></param>
        /// <returns></returns>
        public static RotatedRect SexRotatedRect(RotatedRect rr)
        {
        float w = (float)(rr.Size.Width * 0.7);
        float h = (float)(rr.Size.Height * 1);
        float px = (float)(rr.Center.X - rr.Size.Height * 3.7);
        float py = (float)(rr.Center.Y - rr.Size.Height * 5);
        PointF center = new PointF(px, py);
        RotatedRect rect = new RotatedRect(center, new SizeF(w, h), rr.Angle);
        return rect;
        }
        /// <summary>
        /// 姓名区域
        /// </summary>
        /// <param name="rr"></param>
        /// <returns></returns>
        public static RotatedRect NameRotatedRect(RotatedRect rr)
        {
    
        float w = (float)(rr.Size.Width * 0.3 );
        float h = (float)(rr.Size.Height * 1 );
        float px = (float)(rr.Center.X - rr.Size.Height * 5.1);
        float py = (float)(rr.Center.Y - rr.Size.Height * 6.3);
        PointF center = new PointF(px, py);
        RotatedRect rect = new RotatedRect(center, new SizeF(w, h), rr.Angle);
        return rect;
        }
    
    

    附二: 点旋转代码:

       public static Point PointRotate(PointF center, PointF p1, double angle)
        {
        Point tmp = new Point();
        double angleHude = angle * Math.PI / 180;/*角度变成弧度*/
        double x1 = (p1.X - center.X) * Math.Cos(angleHude) + (p1.Y - center.Y) * Math.Sin(angleHude) + center.X;
        double y1 = -(p1.X - center.X) * Math.Sin(angleHude) + (p1.Y - center.Y) * Math.Cos(angleHude) + center.Y;
        tmp.X = (int)x1;
        tmp.Y = (int)y1;
        return tmp;
        }

    附三: 判断点是否在多边形内

          public static bool IsInside(Point inputponint,PointF[] pointfs)
            {
                GraphicsPath myGraphicsPath = new GraphicsPath();
                Region myRegion = new Region();
                myGraphicsPath.Reset();
                myGraphicsPath.AddPolygon(pointfs);
                myRegion.MakeEmpty();
                myRegion.Union(myGraphicsPath);
    
                return  myRegion.IsVisible(inputponint);
            }
  • 相关阅读:
    那段岁月
    ExtJS +Asp.NET实践(1)GridPanel与服务器端数据交互
    Jqplot+asp.net画图实现
    VC++ 内存机理的个人理解(一)——地址和指针的关系
    恢复PL/SQL Developer中删除确认对话框
    VC++ 内存机理的个人理解(二)——堆和栈
    用VS 2008开发WCF(一)——最快速的WCF入门
    用VS 2008开发WCF(二)——构建最初的服务器
    联想G460笔记本触摸板驱动 For Windows 7 x64
    .NET async await 关键字最简单例子
  • 原文地址:https://www.cnblogs.com/cnsec/p/13286778.html
Copyright © 2011-2022 走看看