当前位置:   article > 正文

C#使用OpenCV识别答题卡填涂区域(方形圆形都可)_opencv答题卡选择区域识别

opencv答题卡选择区域识别

参考这位大佬用c++写的,和这位大佬用EmguCv写的,一步一步翻译成c#OpenCv

具体逻辑为
1.先读取到包裹选项的框(这里是把包裹选项的框的面积设为最大,再获取面积最大的框)
2.用选项的行数和列数获取第一步获取到的框中所有选项的位置(拿列来说,程序可以获取到x最小和x最大的选项,再把最大x和最小x的差除以列数,就可以得到选项之间的间隔,就能获取到每个选项的x,y也同理,所以也有每个选项间的x间隔和y间隔必须相同)
3.选项位置获取到了后,拿每个选项的xy获取到对应点的选项,再根据提供的选项宽高获取此点所在的矩形和矩形所填涂的面积,再根据提供的判断是否填涂参数对比来判断是否选中

限制:1.选项的宽高必须固定;2.图片分辨率最好为1200*1700左右;3.包裹选项的框的粗细要小于选项的框);4.包裹选项的框面积要最大(不最大的话要修改获取框的逻辑);5.扫描的图片不能太倾斜;

直接上代码

具体demo可以去这里拿 https://github.com/cstajj/Scann

调用示例

List<Point[]> selectOption;
int[,] resultArray;
MatchAnswer(fileTextBox.Text, int.Parse(rowTextBox.Text), int.Parse(celTextBox.Text), int.Parse(yzMinTextBox.Text), int.Parse(yzMaxTextBox.Text), int.Parse(heightTextBox.Text), int.Parse(widthTextBox.Text), int.Parse(ttTextBox.Text), out resultArray, out selectOption, out int fzfgCount);
  • 1
  • 2
  • 3

原图
请添加图片描述

结果
在这里插入图片描述

引用

类库
OpenCvSharp4,版本为4.7.0.20230115
OpenCvSharp4.runtime.win ,版本为4.7.0.20230115

using OpenCvSharp;
using Point = OpenCvSharp.Point;
using Size = OpenCvSharp.Size;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Numerics;
using System.Security.Cryptography;
using System.Net.Http;
using System.IO;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

方法

		private static int imgShowIndex = 1;
        private static string showIndex = "";//显示步骤 1234
        private static string showSuqIndex = "";//显示检测到的矩形下标(每个答题卡有很多框) 123
        /// <summary>
        /// 匹配答案
        /// </summary>
        /// <param name="path">文件路径</param>
        /// <param name="row">答案行数</param>
        /// <param name="column">答案列数</param>
        /// <param name="par1">阈值Min可以默认给150</param>
        /// <param name="par2">阈值Max</param>
        /// <param name="height">填涂框的高度 px</param>
        /// <param name="width">填涂框的宽度 px</param>
        /// <param name="judgeSize">填涂覆盖值,达到这个值才为选中</param>
        /// <param name="resultArray">返回数据</param>
        /// <param name="selectOption">选中数据</param>
        /// <param name="fzfgCount">阈值调整次数</param>
        /// <returns></returns>
        public static Mat MatchAnswer(string path, int? row, int? column, int? par1, int? par2, int? height, int? width, int? judgeSize, out int[,] resultArray, out List<Point[]> selectOption, out int fzfgCount)
        {
            JudgeHasValueAndSet(row, 4);
            JudgeHasValueAndSet(column, 6);
            JudgeHasValueAndSet(par1, 150);
            JudgeHasValueAndSet(par2, 255);
            JudgeHasValueAndSet(height, 35);
            JudgeHasValueAndSet(width, 50);
            JudgeHasValueAndSet(judgeSize, 1500);

            resultArray = new int[,] { };
            selectOption = new List<Point[]>();

            Mat answerSheet = Cv2.ImRead(path);
            Point[] result_contour = GetBoundaryOfPic(answerSheet);
            Mat birdMat = WarpPerspective(answerSheet, result_contour);
            ShowImg(birdMat, "鸟瞰");

            //OTSU阈值分割
            Mat target = new Mat();
            int selectOptionCount = 0;
            List<Point[]> selected_contour = new List<Point[]>();
            fzfgCount = 0;
            //自动调整阈值,取到填涂框才继续
            while (selectOptionCount <= 1)
            {
                if (fzfgCount >= 100)
                    return null;
                if (fzfgCount > 0)
                    par1--;
                fzfgCount++;
                Cv2.Threshold(birdMat, target, par1.Value, par2.Value, ThresholdTypes.BinaryInv);//修改thresh或maxval可以调整轮廓取值范围(调的不好会直接取外面的大轮廓)
                //ShowImg(target, "阈值分割");
                selected_contour = SelectedContour(target, height.Value, width.Value);
                selectOptionCount = selected_contour.Count();
            }
            selectOption = selected_contour;
            //3.验证结果
            Mat answerSheet_con = target.Clone();
            Cv2.CvtColor(answerSheet_con, answerSheet_con, ColorConversionCodes.GRAY2BGR);
            Cv2.DrawContours(answerSheet_con, selected_contour, -1, new Scalar(0, 0, 255), 2);

            ShowImg(answerSheet_con, "选项");
            List<Point>[,] classed_contours = ClassedOfContours(selected_contour, row.Value, column.Value);

            //5.绘制并验证
            List<Scalar> color = new List<Scalar>();
            color.Add(new Scalar(0, 0, 255));
            color.Add(new Scalar(255, 0, 255));
            color.Add(new Scalar(0, 255, 255));
            color.Add(new Scalar(255, 0, 0));
            color.Add(new Scalar(0, 255, 0));
            Mat groupMap = target.Clone();
            Cv2.CvtColor(groupMap, groupMap, ColorConversionCodes.GRAY2BGR);
            for (int i = 0; i < row; i++)
            {
                List<List<Point>> tempGroupPoints = new List<List<Point>>();
                for (int j = 0; j < column; j++)
                {
                    if (classed_contours[i, j].Count > 0)
                        tempGroupPoints.Add(classed_contours[i, j]);
                }
                if (tempGroupPoints.Count > 0)
                    Cv2.DrawContours(groupMap, tempGroupPoints, -1, color[(i >= 5 ? i % 5 : i)], 2);
            }
            ShowImg(groupMap, "分组");


            //检测答题者的选项
            resultArray = GetResultArray(target, classed_contours, row.Value, column.Value, judgeSize.Value);
            Mat resultMat = new Mat();
            Cv2.CvtColor(target, resultMat, ColorConversionCodes.GRAY2BGR);

            List<List<Point>> tempPoints = new List<List<Point>>();
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < column; j++)
                {
                    if (resultArray[i, j] == 1)
                    {
                        tempPoints.Add(classed_contours[i, j]);
                    }
                }
            }
            Cv2.DrawContours(resultMat, tempPoints, -1, new Scalar(255, 0, 0), 2);

            ShowImg(resultMat, "结果");
            return resultMat;
        }

        public static void JudgeHasValueAndSet(int? value,int defaultValue = 0) {
            if (value == null)
                value = defaultValue;
        }

        /// <summary>
        /// 寻找边界
        /// </summary>
        /// <param name="mat"></param>
        /// <returns></returns>
        public static Point[] GetBoundaryOfPic(Mat mat, string size = "0,90,3,true")
        {
            //灰度转化
            Mat gray = new Mat();
            Cv2.CvtColor(mat, gray, ColorConversionCodes.RGB2GRAY);
            //进行高斯滤波
            Mat blurred = new Mat();
            Cv2.GaussianBlur(gray, blurred, new Size(3, 3), 0);
            //进行canny边缘检测
            Mat canny = new Mat();
            //Cv2.Canny(blurred, canny, 0, 180);
            string[] str = size.Split(",");
            Cv2.Canny(blurred, canny, int.Parse(str[0]), int.Parse(str[1]), int.Parse(str[2]), bool.Parse(str[3]));
            ShowImg(canny, "canny");

            //寻找矩形边界
            Point[][] contours;
            HierarchyIndex[] hierarchly;
            Cv2.FindContours(canny, out contours, out hierarchly, RetrievalModes.External, ContourApproximationModes.ApproxSimple);

            //Cv2.FindContours(canny, out contours, out hierarchly, RetrievalModes.External, ContourApproximationModes.ApproxNone);

            //Cv2.FindContours(canny, out contours, out hierarchly, RetrievalModes.External, ContourApproximationModes.ApproxTC89KCOS);

            //Cv2.FindContours(canny, out contours, out hierarchly, RetrievalModes.External, ContourApproximationModes.ApproxTC89L1);
            Point[] result_contour;
            if (contours.Length == 1)
            {
                result_contour = contours[0];
            }
            else
            {
                double max = -1;
                int index = -1;
                for (int i = 0; i < contours.Length; i++)
                {
                    if (contours[i].Length < 4)
                    {
                        continue;
                    }
                    double tem = Cv2.ArcLength(contours[i], true);
                    bool pass = IsGreatArc(contours[i]);
                    if (tem > max && pass)
                    {
                        max = tem;
                        index = i;
                    }
                    if (!string.IsNullOrEmpty(showSuqIndex))
                    {
                        Mat birdMat = WarpPerspective(mat, contours[i]);
                        ShowImg2(birdMat, "鸟瞰" + i + "|" + tem.ToString("f2") + (pass ? "|通过" : ""));
                    }
                }
                //取面积最大的矩形为检测填涂区域,所以在做答题卡时需要把填涂区的框做最大,或者自己调整,但上面的判断也需要一并调整
                result_contour = contours[index];

            }
            return result_contour;
        }

        class TempXY
        {
            public int X { get; set; }
            public int Y { get; set; }
        }

        public static bool IsGreatArc(Point[] contours)
        {
            TempXY minXmaxY = new TempXY() { X = -1 };
            TempXY minXminY = new TempXY() { X = -1 };
            TempXY maxXmaxY = new TempXY() { X = -1 };
            TempXY maxXminY = new TempXY() { X = -1 };
            int minX = -1, minY = -1, maxX = -1, maxY = -1;
            foreach (var item in contours)
            {
                if (item.X < minX || minX == -1)
                    minX = item.X;
                if (item.X > maxX || maxX == -1)
                    maxX = item.X;
                if (item.Y < minY || minY == -1)
                    minY = item.Y;
                if (item.Y > maxY || maxY == -1)
                    maxY = item.Y;
            }
            foreach (var item in contours)
            {
                if (Math.Abs(item.X - minX) + Math.Abs(item.Y - maxY) < Math.Abs(minXmaxY.X - minX) + Math.Abs(minXmaxY.Y - maxY) || minXmaxY.X == -1)
                {
                    minXmaxY.X = item.X;
                    minXmaxY.Y = item.Y;
                }
                if (Math.Abs(item.X - minX) + Math.Abs(item.Y - minY) < Math.Abs(minXminY.X - minX) + Math.Abs(minXminY.Y - minY) || minXminY.X == -1)
                {
                    minXminY.X = item.X;
                    minXminY.Y = item.Y;
                }
                if (Math.Abs(item.X - maxX) + Math.Abs(item.Y - maxY) < Math.Abs(maxXmaxY.X - maxX) + Math.Abs(maxXmaxY.Y - maxY) || maxXmaxY.X == -1)
                {
                    maxXmaxY.X = item.X;
                    maxXmaxY.Y = item.Y;
                }
                if (Math.Abs(item.X - maxX) + Math.Abs(item.Y - minY) < Math.Abs(maxXminY.X - maxX) + Math.Abs(maxXminY.Y - minY) || maxXminY.X == -1)
                {
                    maxXminY.X = item.X;
                    maxXminY.Y = item.Y;
                }
            }
            if (Math.Abs(minXminY.X - minXmaxY.X) < 30 && Math.Abs(maxXminY.X - maxXmaxY.X) < 30 && minXmaxY.X > 5 && minXminY.Y > 5 && maxXminY.X - minXminY.X > 50 && minXmaxY.Y - minXminY.Y > 50)
            {
                if (Math.Abs(minXminY.Y - maxXminY.Y) < 30 && Math.Abs(minXmaxY.Y - maxXmaxY.Y) < 30)
                {
                    return true;
                }

            }

            return false;
        }

        /// <summary>
        /// 对图像进行矫正(转为鸟瞰图,删除多余边界)
        /// </summary>
        /// <param name="mat"></param>
        /// <param name="result_contour"></param>
        /// <returns></returns>
        public static Mat WarpPerspective(Mat mat, Point[] result_contour)
        {
            //使用DP算法拟合答题卡的几何轮廓,保存点集pts并顺时针排序
            double result_length = Cv2.ArcLength(result_contour, true);
            Point[] pts = Cv2.ApproxPolyDP(result_contour, result_length * 0.02, true);
            int width = 0;
            int height = 0;
            if (pts.Length == 4)
            {
                if (pts[1].X < pts[3].X)
                {
                    //说明当前为逆时针存储,改为顺时针存储(交换第2、4点)
                    Point p = new Point();
                    p = pts[1];
                    pts[1] = pts[3];
                    pts[3] = p;
                }
                if (Math.Abs(pts[0].X - pts[3].X) > 100)
                {
                    Point temp = pts[pts.Length - 1];
                    for (int i = pts.Length - 1; i >= 0; i--)
                    {
                        if (i == 0)
                            pts[i] = temp;
                        else
                            pts[i] = pts[i - 1];
                    }
                }
                //进行透视变换
                //1.确定变化尺寸的宽度

                float width1 = (pts[0].X - pts[1].X) * (pts[0].X - pts[1].X) + (pts[0].Y - pts[1].Y) * (pts[0].Y - pts[1].Y);
                float width2 = (pts[2].X - pts[3].X) * (pts[2].X - pts[3].X) + (pts[2].Y - pts[3].Y) * (pts[2].Y - pts[3].Y);
                width = width1 > width2 ? (int)Math.Sqrt(width1) : (int)Math.Sqrt(width2);
                //2.确定变化尺寸的高度

                float height1 = (pts[0].X - pts[3].X) * (pts[0].X - pts[3].X) + (pts[0].Y - pts[3].Y) * (pts[0].Y - pts[3].Y);
                float height2 = (pts[2].X - pts[1].X) * (pts[2].X - pts[1].X) + (pts[2].Y - pts[1].Y) * (pts[2].Y - pts[1].Y);
                height = height1 > height2 ? (int)Math.Sqrt(height1) : (int)Math.Sqrt(height2);
            }


            Point2f[] pts_src = Array.ConvertAll(pts.ToArray(), new Converter<Point, Point2f>(PointToPointF));
            Point2f[] pts_target = new Point2f[] { new Point2f(0, 0), new Point2f(width - 1, 0), new Point2f(width - 1, height - 1), new Point2f(0, height - 1) };

            //4.计算透视变换矩阵
            //4.1类型转化
            Mat data = Cv2.GetPerspectiveTransform(pts_src, pts_target);
            //5.进行透视变换
            Mat birdMat = new Mat();
            //进行透视操作
            Mat mat_Perspective = new Mat();
            Mat src_gray = new Mat();
            Cv2.CvtColor(mat, src_gray, ColorConversionCodes.BGR2GRAY);
            Cv2.WarpPerspective(src_gray, birdMat, data, new Size(width, height));
            return birdMat;
        }

        /// <summary>
        /// 获取所有选项位置
        /// </summary>
        /// <param name="target">矫正和OTSU阈值分割后的mat</param>
        /// <returns></returns>
        public static List<Point[]> SelectedContour(Mat target, int height, int width)
        {
            //轮廓筛选
            //1.改善轮廓
            Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(1, 1));
            Cv2.Dilate(target, target, element);
            //ShowImg(target);
            //2.筛选轮廓
            Point[][] target_contour;
            List<Point[]> selected_contour = new List<Point[]>();
            HierarchyIndex[] hierarchly2;
            Cv2.FindContours(target, out target_contour, out hierarchly2, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
            foreach (var m in target_contour)
            {
                Rect rect = Cv2.BoundingRect(m);
                double k = (double)rect.Height / rect.Width;
                if (rect.Height > height && rect.Width > width && rect.Width < 100)
                {
                    selected_contour.Add(m);
                }
            }
            return selected_contour;
        }

        /// <summary>
        /// 把选项分为有序的行列数据
        /// </summary>
        /// <param name="selected_contour">位置</param>
        /// <param name="countOfRow">选项行数</param>
        /// <param name="countOfColumn">列数</param>
        /// <returns></returns>
        public static List<Point>[,] ClassedOfContours(List<Point[]> selected_contour, int countOfRow, int countOfColumn)
        {
            //依据圆心的位置来确认答题卡轮廓的位置
            //1.计算所有外接圆基本数据
            float[] radius = new float[selected_contour.Count];
            Point2f[] center = new Point2f[selected_contour.Count];
            for (int i = 0; i < selected_contour.Count; i++)
            {
                float radiusItem;
                Point2f centerItem;
                Cv2.MinEnclosingCircle(selected_contour[i], out centerItem, out radiusItem);//最小外接圆
                center[i] = centerItem;
                radius[i] = radiusItem;
            }
            //2.计算x轴、y轴分割间隔
            float x_min = 999, y_min = 999;
            float x_max = -1, y_max = -1;
            float x_interval = 0, y_interval = 0;//相邻圆心的间距
            foreach (Point2f pf in center)
            {
                //获取所有圆心中的坐标最值
                if (pf.X < x_min) x_min = pf.X;
                if (pf.X > x_max) x_max = pf.X;

                if (pf.Y < y_min) y_min = pf.Y;
                if (pf.Y > y_max) y_max = pf.Y;
            }
            x_interval = (x_max - x_min) / (countOfColumn - 1);//答题卡每列x个,即x-1个间隔
            y_interval = (y_max - y_min) / (countOfRow - 1);//答题卡每行y个圆,即y-1个间隔

            //4.分类
            List<Point>[,] classed_contours = new List<Point>[countOfRow, countOfColumn];
            //初始化VectorOfVectorOfPoint二维数组
            for (int i = 0; i < countOfRow; i++)
            {
                for (int j = 0; j < countOfColumn; j++)
                {
                    classed_contours[i, j] = new List<Point>();
                }
            }
            if (x_interval == 0 || y_interval == 0)
                return classed_contours;
            for (int i = 0; i < selected_contour.Count; i++)
            {
                Point2f pf = center[i];
                int index_r = (int)Math.Round((pf.Y - y_min) / y_interval);//行号
                int index_c = (int)Math.Round((pf.X - x_min) / x_interval);//列号
                Point[] temp = selected_contour[i];
                classed_contours[index_r, index_c].AddRange(temp.ToList());
            }

            return classed_contours;
        }

        /// <summary>
        /// 检测答题者的选项,获取涂选的结果数组
        /// </summary>
        /// <param name="mat_threshold">经阈值处理后的图像</param>
        /// <param name="classed_contours">经排序分类后的轮廓数组</param>
        /// <param name="countOfRow">一行中轮廓的个数</param>
        /// <param name="countOfColumn">一列中轮廓的个数</param>
        /// <returns></returns>
        public static int[,] GetResultArray(Mat mat_threshold, List<Point>[,] classed_contours, int countOfRow, int countOfColumn, int judgeSize)
        {
            int[,] result_count = new int[countOfRow, countOfColumn];//结果数组
            //统计所有答题圆圈外接矩形内非零像素个数
            Rect[,] re_rect = new Rect[countOfRow, countOfColumn];//外接矩形数组
            int[,] count_roi = new int[countOfRow, countOfColumn];//外接矩形内非零像素个数
            int min_count = 999;//非零像素个数最大值,作为已涂选的参照
            int max_count = -1;//非零像素个数最小值,作为未涂选的参照
            for (int i = 0; i < countOfRow; i++)
            {
                for (int j = 0; j < countOfColumn; j++)
                {
                    List<Point> countour = classed_contours[i, j];
                    re_rect[i, j] = Cv2.BoundingRect(countour);
                    Mat temp = new Mat(mat_threshold, re_rect[i, j]);//提取ROI矩形区域
                    int count = Cv2.CountNonZero(temp);//计算图像内非零像素个数
                    count_roi[i, j] = count;

                    if (count > max_count) max_count = count;
                    if (count < min_count) min_count = count;
                }
            }

            if (judgeSize > 0)
            {
                max_count = judgeSize * 2;
            }


            //比对涂选的答案,以涂满圆圈一半以上为标准
            for (int i = 0; i < countOfRow; i++)
            {
                for (int j = 0; j < countOfColumn; j++)
                {
                    if (count_roi[i, j] > max_count / 2)
                    {
                        result_count[i, j] = 1;
                    }
                }
            }

            return result_count;
        }

        public static void ShowImg(Mat mat, string name = "")
        {
            bool show = false;
            foreach (var item in showIndex)
            {
                int i = int.Parse(item.ToString());
                int i2 = (int)item;
                if (imgShowIndex == int.Parse(item.ToString()))
                {
                    show = true;
                }
            }

            if (!show)
            {
                imgShowIndex++;
                return;
            }
            Cv2.ImShow(!string.IsNullOrEmpty(name) ? name : imgShowIndex.ToString(), mat);
            imgShowIndex++;
        }

        private static int suqShowIndex = 1;
        public static void ShowImg2(Mat mat, string name = "")
        {
            bool show = false;
            if (showSuqIndex == "0")
            {
                Cv2.ImShow(name, mat);
                return;
            }
            else if (!string.IsNullOrEmpty(showSuqIndex))
            {
                foreach (var item in showSuqIndex)
                {
                    int i = int.Parse(item.ToString());
                    int i2 = (int)item;
                    if (suqShowIndex == int.Parse(item.ToString()))
                    {
                        show = true;
                    }
                }

                if (!show)
                {
                    suqShowIndex++;
                    return;
                }
                Cv2.ImShow(!string.IsNullOrEmpty(name) ? name : suqShowIndex.ToString(), mat);
                suqShowIndex++;
            }

        }

        /// <summary>
        /// Point转换为PointF类型
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public static Point2f PointToPointF(Point p)
        {
            return new Point2f(p.X, p.Y);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/811987
推荐阅读
相关标签
  

闽ICP备14008679号