//话说手机上也有二维码解析,但是只能说是娱乐用呵呵,我们是工业用
//运行速度有点慢,可能要500毫秒,以后有空再提速吧.
//上次的解析有些局限,那就是二维码的图像必须是正方形,否则就会出错,这次改过来了,支持各种畸形图像。
//先说解析过程吧,
//1.筛选面积较大的区域,找出4个方向的外壳边界,再从这4条线中找出ECC200二维码图的两条主边界
//2.根据两条主边界寻找两条对边
//3.根据4条边的交点创建一个透视变换
//4.根据透视变换计算出旋转角度,把原图校正成标准的ecc200矩阵图
//5.对校正图进行阈值再分配,得出一个效果较好的二值图
//6.分析校正2值图的网格,并进行筛选和平滑化
//7.根据最普遍间距进程错漏网格线的补充
//8.用高斯模糊矩阵计算出网格单元的高斯模糊像素值,如果大于某个阈值说明为0(白色为主的区域),否则为黑色为主的区域1
//9.根据二进制矩阵解码得出解码字符串 ,解码算法是用的一个老外的解码算法,具体细节没去了解,反正把代码抠出来能解析正确就OK

源图

解析图,绿色的是主边界,红色的是和主边界形成的平行四边形的两条对边,在两条对边进行精确查找得出两条最终的对边

区域图

校正图

二值图

网格图

解码图

//牵连的代码不下5000行,所以只贴主代码了,有思路就行

using System;
namespace ImageProcessing.Graph
{

//表示范围的类
    [Serializable]
    public class Range : ICloneable, IComparable
    {
        public Range() { }
        public Range(double max) { end = max; sort(); }
        public Range(double begin, double end) { this.begin = begin; this.end = end; sort(); }

double begin = 0, end = 0;

public double End
        {
            get { return end; }
            set { end = value; sort(); }
        }

public double Begin
        {
            get { return begin; }
            set { begin = value; sort(); }
        }

public double Center
        {
            get { return (begin + end) / 2d; }
        }

public double Lenght
        {
            get { return Math.Abs(end - begin); }
        }

public Range clone
        {
            get { return new Range(begin, end); }
        }

void sort()
        {
            if (end < begin)
                GraphHelper.Swap(ref begin, ref end);
        }

public static Range FromSize(double begine,double length)
        {
            return new Range(begine, begine + length);
        }

public object Clone() { return clone; }

public double DistanceTo(Range dest)
        {
            if (IntersectWith(dest)) return 0;
            if (begin > dest.end) return begin - dest.end;
            return dest.begin - end;
        }

public double CenterDistanctTo(Range dest)
        {
            return Math.Abs(Center - dest.Center);
        }

public double DistanceTo(double pos)
        {
            if (pos >= begin && pos <= end) return 0;
            else if (pos < begin) return begin - pos;
            else return pos - end;
        }

public Range Join(Range dest)
        {
            return new Range(Math.Min(begin, dest.begin), Math.Max(end, dest.end));
        }

public Range Intersect(Range dest)
        {
            if (!IntersectWith(dest)) { return null; }
            else if (Contains(dest)) return clone;
            return new Range(Math.Max(begin, dest.begin), Math.Min(end, dest.end));
        }

public bool Contains(double d)
        {
            return d >= begin && d <= end;
        }

public bool Contains(Range dest)
        {
            return dest.begin >= this.begin && dest.end <= end;
        }

public bool IntersectWith(Range dest)
        {
            return !(dest.begin > end || dest.end < begin);
        }

public static bool operator ==(Range range1, Range range2)
        {
            if (Equals(range1,null)) return Equals(range2,null);
            else if (Equals(range2, null)) return false;
            return range1.begin == range2.begin && range1.end == range2.end;           
        }

public static bool operator !=(Range range1, Range range2)
        {
            return !(range1 == range2);
        }

public override bool Equals(object obj)
        {
            if (Equals(obj, null)) return false;
            if (!(obj is Range)) return false;
            return this == (Range)obj;
        }

public override int GetHashCode()
        {
            return base.GetHashCode();
        }

public override string ToString()
        {
            return string.Format("[{0},{1}]", begin, end);
        }

public int CompareTo(object obj)
        {
            Range dest = (Range)obj;
            if (begin < dest.begin)
                return -1;
            else if (begin == dest.begin)
            {
                if (this.end == dest.end)
                    return 0;
                else if (this.end < dest.end)
                    return -1;
                else
                    return 1;
            }
            else
                return 1;
        }
    }
}

//这3个函数的作用是扫描线段上的指定颜色区域,所属的类为静态类GraphHelper,这个类有3000行,没必要的就不复制了呵呵

class GraphHelper

{

public static List<Line> GetIntersectLines(byte[] threshBuffers, int imgWidth, int imgHeight, bool seachBlackPixel, Line l)
        {
            RectD rd = new RectD(0, 0, imgWidth, imgHeight);

if (l.StartPos.X >= 0 && l.StartPos.Y >= 0 && l.StartPos.X < imgWidth && l.StartPos.Y < imgHeight)
            {
                return GetIntersectLines(threshBuffers, imgWidth, imgHeight, seachBlackPixel, l.StartPos, l.Direction, l.Length);
            }
            else if (l.EndPos.X >= 0 && l.EndPos.Y >= 0 && l.EndPos.X < imgWidth && l.EndPos.Y < imgHeight)
            {
                return GetIntersectLines(threshBuffers, imgWidth, imgHeight, seachBlackPixel, l.EndPos, l.Direction * -1, l.Length);
            }
            else
            {
                List<PointD> pds = rd.GetIntersect(l);
                if (pds.Count == 2)
                    return GetIntersectLines(threshBuffers, imgWidth, imgHeight, seachBlackPixel, pds[0], pds[1] - pds[0], pds[0].DistanceTo(pds[1]));
            }
            return new List<Line>();
        }

/// <summary>
        /// 单纯计算起始点沿着dir方向上的与指定颜色区域的的相交线段,如果起始点不在范围内则直接中断
        /// </summary>
        public static List<Line> GetIntersectLines(byte[] threshBuffers, int imgWidth, int imgHeight, bool seachBlackPixel, PointD startPos, PointD direction, double length)
        {
            int px = seachBlackPixel ? 0 : 1;
            List<Line> res = new List<Line>();

PointD cur = startPos.clone, last = null, prev = null;
            PointD dir = direction.Unit;
            Point pt;

pt = cur.Point;
            while (InRange(pt, imgWidth, imgHeight))
            {
                if (threshBuffers[pt.X + pt.Y * imgWidth] == px)
                {
                    last = cur.clone;
                    prev = cur.clone;

cur += dir;
                    pt = cur.Point;
                    while (InRange(pt, imgWidth, imgHeight) && threshBuffers[pt.X + pt.Y * imgWidth] == px)
                    {
                        prev = cur;
                        cur += dir;
                        pt = cur.Point;
                    }
                    res.Add(new Line(last, prev));
                }
                cur += dir;
                pt = cur.Point;
            }

return res;
        }

//查找行或列扫描线上的颜色区域段

public static List<Range> GetStrideRanges(byte[] threshBuffers, int imgWidth, int imgHeight, bool searchBlackPixel, int position, bool searchRow)
        {
            return GetStrideIntersectLines(threshBuffers, imgWidth, imgHeight, searchBlackPixel, position, searchRow)
                .ConvertAll<Range>(delegate(Line line)
            {
                if (searchRow) return new Range(line.StartPos.X, line.EndPos.X);
                else return new Range(line.StartPos.Y, line.EndPos.Y);
            });
        }

public static List<Line> GetStrideIntersectLines(byte[] threshBuffers, int imgWidth, int imgHeight, bool seachBlackPixel, int position, bool searchRow)
        {
            PointD start = searchRow ? new PointD(0, position) : new PointD(position, 0);
            PointD dir = searchRow ? new PointD(1, 0) : new PointD(0, 1);
            return GetIntersectLines(threshBuffers, imgWidth, imgHeight, seachBlackPixel, start, dir, searchRow ? imgWidth : imgHeight);
        }

}

//高斯模糊矩阵类

using System;
namespace ImageProcessing.Base.Imaging
{
    public class GaussainMatrix
    {
        public GaussainMatrix(double sigma, int size)
        {
            size |= 1;
            this.size = size;
            this.width = size;
            this.height = size;
            datas = new double[size, size];

int cx = size / 2, cy = size / 2;
            double dx, dy;
            double sum = 0;
            for (int y = 0; y < size; y++)
            {
                for (int x = 0; x < size; x++)
                {
                    dx = x - cx; dy = y - cy;
                    datas[y, x] = Math.Exp(-1.0 * (dx * dx + dy * dy) / (2.0 * sigma * sigma)) / (Math.Sqrt(2.0 * Math.PI) * sigma);
                    sum += datas[y, x];
                }
            }
            for (int y = 0; y < size; y++)
                for (int x = 0; x < size; x++)
                    datas[y, x] = datas[y, x] / sum;               
        }

public GaussainMatrix(double sigma)
            : this(sigma, 1 + 2 * ((int)(3.0 * sigma)))
        {

}

public GaussainMatrix(double sigma, int wndWidth, int wndHeight)
        {
            width = wndWidth | 1;
            height = wndHeight | 1;
            size = (width + height) / 2;

datas = new double[height, width];
            int cx = wndWidth / 2, cy = wndHeight / 2;
            double dx, dy;
            double sum = 0;
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    dx = x - cx; dy = y - cy;
                    datas[y, x] = Math.Exp(-1.0 * (dx * dx + dy * dy) / (2.0 * sigma * sigma)) / (Math.Sqrt(2.0 * Math.PI) * sigma);
                    sum += datas[y, x];
                }
            }
            for (int y = 0; y < height; y++)
                for (int x = 0; x < width; x++)
                    datas[y, x] = datas[y, x] / sum;
        }

public byte Convolve(byte[] buffers, int imgWidth, int imgHeight, int x, int y)
        {
            if (x < 0 || y < 0) return 255;

int hw = width / 2;
            int hh = height / 2;
            int t = Math.Max(0, y - hh);
            int l = Math.Max(0, x - hw);
            int b = Math.Min(imgHeight - 1, y + hh);
            int r = Math.Min(imgWidth - 1, x + hw);
            double sum = 0;
            for (int i = t; i <= b; i++)
                for (int j = l; j <= r; j++)
                    sum += buffers[i * imgWidth + j] * datas[(i - y) + hh, (j - x) + hw];
            return (byte)sum;
        }
       
        double[,] datas;
        int size, width, height;
        public int Size { get { return size; } }
        public int Width { get { return width; } }
        public int Height { get { return height; } }
        public int Radius { get { return size / 2; } }
        public double this[int x, int y] { get { return datas[y, x]; } set { datas[y, x] = value; } }
    }
}

//正文:主代码了

#define test_img
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using ImageProcessing.Graph;
using ImageProcessing.Generic;
using ImageProcessing.Base.Imaging;
using ImageProcessing.Base.Imaging.Thresholding;
using ImageProcessing.Base.ModelFitting.LineFitting;
using ImageProcessing.Base.Decoding.xmDecoding;

namespace ImageProcessing.Base.Decoding
{
    public class ECC200DecoderEx
    {
        public ECC200DecoderEx(Threshold thresh)
        {
            buffers = (byte[])thresh.Buffers.Clone();
            w = thresh.Width;
            h = thresh.Height;
            this.thresh = thresh;
            cimg = new ContourImage(buffers, w, h);
            aimg = thresh.AreaImage; //new AreaImage(cimg.Buffers, w, h, false);
            CheckPosition();
        }

IntegralImage iimg;
        int w, h;
        byte[] buffers;
        Threshold thresh;
        AreaImage aimg;
        ContourImage cimg;

public Bitmap imageAna;       
        public Bitmap imageBits;
        public Bitmap imageGrid;
        public Bitmap imageRegoin;
        public Bitmap imageCorrected;
        public Bitmap imageCorrectedThresh;

byte[,] bitsMat;

static Font font = new Font("宋体", 9);
        public bool showTest = true;
        void CheckPosition()
        {
            List<ContourInfo> infos = aimg.ContourInfoList;

imageAna = null;
            imageBits = null;
            imageRegoin = null;
            imageCorrected = null;

imageAna = aimg.Image;
            Graphics anaG = Graphics.FromImage(imageAna);

foreach (ContourInfo inf in infos)
            {
                if (inf.Area > 300)
                {
                    List<Line> lines = GetRegionEadges(inf);
                    Line eadge1, eadge2 , eadge3, eadge4;
                    if (GetMainEadges(lines,out eadge1,out eadge2))
                    {
                        if (Math.Abs(eadge1.Length - eadge2.Length) / ((eadge2.Length + eadge1.Length) / 2) < 0.25)
                        {
                            if (((eadge2.Length + eadge1.Length) / 2) > 70)
                            {
                                bool b1 = SearchEadge1(thresh, cimg, eadge1, eadge2.EndPos, 35, 0.1, out eadge3);
                                bool b2 = SearchEadge1(thresh, cimg, eadge2, eadge1.EndPos, 35, 0.1, out eadge4);

eadge1.DrawView(new Pen(Color.Green) { DashCap = DashCap.Triangle }, anaG);
                                anaG.DrawString("eadge1", font, Brushes.Green, eadge1.EndPos.PointF);
                                eadge2.DrawView(new Pen(Color.Green) { DashCap = DashCap.Triangle }, anaG);
                                anaG.DrawString("eadge2", font, Brushes.Green, eadge2.EndPos.PointF);
                                new Line(eadge1.EndPos, eadge1.EndPos + eadge2.Direction * 2).DrawView(GraphHelper.DashPen(Color.Red), anaG);
                                new Line(eadge2.EndPos, eadge2.EndPos + eadge1.Direction * 2).DrawView(GraphHelper.DashPen(Color.Red), anaG);

if (b1)
                                {
                                    eadge3.DrawView(Pens.Blue, anaG);
                                    anaG.DrawString("eadge3", font, Brushes.Green, eadge3.EndPos.PointF);
                                }
                                if (b2)
                                {
                                    eadge4.DrawView(Pens.Blue, anaG);
                                    anaG.DrawString("eadge4", font, Brushes.Green, eadge4.EndPos.PointF);
                                }
                                if (b1 && b2)
                                {
                                    PerspectiveTransform trans = BuildTransform(eadge1, eadge2, eadge3, eadge4, thresh.SrcImage, out imageRegoin);
                                    double newAgl = GetPerspectiveTransformAngle(trans, eadge1.StartPos, 200, 200,315);
                                    ThresholdImage thsImg = ReThreshold(thresh, trans, 200, 200, newAgl,out imageCorrected);                                   
                                    imageCorrectedThresh = thsImg.Image;

if (showTest)
                                    {
                                        GraphHelper.ShowImageDialog(imageRegoin);
                                        GraphHelper.ShowImageDialog(imageCorrected);
                                        GraphHelper.ShowImageDialog(imageCorrectedThresh);
                                    }

List<Range> xRanges, yRanges;
                                    if (CheckMatrixCells(thsImg, thsImg.Width * 9 / 10, 0,
                                        thsImg.Width - 1, thsImg.Height / 10, 3, 3, 5, 0.75, 0.5, out xRanges, out yRanges))
                                    {
                                        imageGrid = thsImg.Image;
                                        Graphics gridG = Graphics.FromImage(imageGrid);
                                        DrawRanges(xRanges, yRanges, 200, 200, gridG);
                                        gridG.Dispose();

bitsMat = CalThreshBitsMatrix(thsImg, xRanges, yRanges, out imageBits);
                                        string decStr = "";
                                        try { decStr = ECC200Decoder.Decode(bitsMat); }
                                        catch { }

if (showTest)
                                        {
                                            GraphHelper.ShowImageDialog(imageGrid);
                                            GraphHelper.ShowImageDialog("解码:" + decStr, imageBits);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

anaG.Dispose();
            if (showTest)
            {
                GraphHelper.ShowImageDialog(imageAna);
                imageAna.Dispose();
            }
        }

/// <summary>
        /// 绘制网格图
        /// </summary>
        public static void DrawRanges(List<Range> xRanges, List<Range> yRanges, int width, int height, Graphics g)
        {
            if (xRanges.Count > 0)
            {
                foreach (Range r in xRanges)
                {
                    g.DrawString(xRanges.IndexOf(r).ToString(), font, Brushes.Red, new PointD(r.Begin, 0).PointF);
                    new Line(new PointD(r.Begin, 0), new PointD(r.Begin, height)).DrawView(GraphHelper.GetPenByIndex(xRanges.IndexOf(r)), g);
                }
            }
            if (yRanges.Count > 0)
            {
                foreach (Range r in yRanges)
                {
                    g.DrawString(yRanges.IndexOf(r).ToString(), font, Brushes.Red, new PointD(0, r.Begin).PointF);
                    new Line(new PointD(0, r.Begin), new PointD(width, r.Begin)).DrawView(GraphHelper.GetPenByIndex(yRanges.IndexOf(r)), g);
                }
            }
        }

/// <summary>
        /// 由X方向的区域序列和Y方向区域序列计算出2进制ECC200矩阵
        /// </summary>
        public static byte[,] CalThreshBitsMatrix(ThresholdImage timg, List<Range> xRanges, List<Range> yRanges,out Bitmap imgMat)
        {
            byte[,] bitsMat = new byte[xRanges.Count, yRanges.Count];
            imgMat = new Bitmap(timg.Width, timg.Height);
            Graphics g = Graphics.FromImage(imgMat);
            for (int y = 0; y < yRanges.Count; y++)
            {
                for (int x = 0; x < xRanges.Count; x++)
                {
                    bitsMat[x, y] = CalThreshBit(timg, xRanges[x], yRanges[y], 100);
                    if (bitsMat[x, y] == 0)
                        g.DrawRectangle(Pens.Blue, new Rectangle((int)xRanges[x].Begin, (int)yRanges[y].Begin, (int)xRanges[x].Lenght, (int)yRanges[y].Lenght));
                    else
                        g.FillRectangle(Brushes.Red, new Rectangle((int)xRanges[x].Begin, (int)yRanges[y].Begin, (int)xRanges[x].Lenght, (int)yRanges[y].Lenght));
                }
            }
            g.Dispose();
            return bitsMat;
        }

/// <summary>
        /// 计算矩形区域的高斯模糊像素值,并转化为二进制
        /// </summary>
        public static byte CalThreshBit(ThresholdImage timg, Range xRange, Range yRange,byte threshValue)
        {
            GaussainMatrix gm = new GaussainMatrix(0.7, (int)xRange.Lenght, (int)yRange.Lenght);
            byte res = gm.Convolve(timg.Buffers, timg.Width, timg.Height, (int)(xRange.Center + 0.5), (int)(yRange.Center + 0.5));
            return (byte)(res >= threshValue ? 0 : 1);
        }

/// <summary>
        /// 获取透视变换之后的旋转角度
        /// </summary>
        public static double GetPerspectiveTransformAngle(PerspectiveTransform trans, PointD corner,
            int width, int height, double destAngle)
        {
            List<Point> cnrs = new List<Point>(new Point[] { new Point(0, 0), new Point(width, 0), new Point(width, height), new Point(0, height) });
            Point best = new Point();
            double minDist = double.MaxValue;
            foreach (Point pt in cnrs)
            {
                PointD pd = trans.Transform(pt, width, height);
                if (pd.DistanceTo(corner) < minDist)
                {
                    best = pt;
                    minDist = pd.DistanceTo(corner);
                }
            }
            double res = destAngle - (new PointD(width / 2d, height / 2d) - (PointD)best).Angle;
            return Math.Round(res / 90d) * 90;
        }

/// <summary>
        /// 检测二维码方格,步骤包括像素初扫描,筛选合并,平滑化和错漏填补
        /// </summary>
        public static bool CheckMatrixCells(ThresholdImage timg, int xStart, int yStart, int xEnd, int yEnd,
            int minRangeDist, int minRangeLenght, double joinDist, double rateOfMaxnum, double errCellDistRate,
            out List<Range> xRanges,out List<Range> yRanges)
        {
            xRanges = new List<Range>();
            yRanges = new List<Range>();
            List<Range> xRng, yRng;
            for (int x = xStart; x <= xEnd; x += 2)
            {
                yRng = GraphHelper.GetStrideRanges(timg.Buffers, timg.Width, timg.Height, true, x, false);
                FilterRanges(minRangeDist, minRangeLenght, yRng);
                if (yRng.Count > yRanges.Count)
                    yRanges = yRng;
            }
            for (int x = yStart; x < yEnd; x+=2)
            {
                xRng = GraphHelper.GetStrideRanges(timg.Buffers, timg.Width, timg.Height, true, x, true);
                FilterRanges(minRangeDist, minRangeLenght, xRng);
                if (xRng.Count > xRanges.Count)
                    xRanges = xRng;
            }
            if (xRanges.Count == 0 || yRanges.Count == 0)
                return false;
            double xDistAvg,yDistAvg;
            if (!(SmoothRanges(joinDist, rateOfMaxnum, ref xRanges, out xDistAvg) && SmoothRanges(joinDist, rateOfMaxnum, ref yRanges, out yDistAvg)))
                return false;
            xRanges = FillUpErrors(xRanges, timg.Width, xDistAvg, errCellDistRate);
            yRanges = FillUpErrors(yRanges, timg.Height, yDistAvg, errCellDistRate);
            return true;
        }

/// <summary>
        /// 将网格平滑化,
        /// </summary>
        public static bool SmoothRanges(double joinDist, double rateOfMaxnum, ref List<Range> ranges,out double distAverage)
        {
            List<double> centers = ranges.ConvertAll<double>(delegate(Range r) { return r.Center; });
            List<DistInfo> infos = new List<DistInfo>();
            double sqrDist;
            bool existDist;
            for (int i = 1; i < centers.Count; i++) //求最普遍的点间距
            {
                existDist = false;
                sqrDist = Math.Abs(centers[i] - centers[i - 1]);
                for (int j = 0; j < infos.Count; j++) //重心归纳算法
                {
                    if (Math.Abs(infos[j].distLvl - sqrDist) <= joinDist)
                    {
                        existDist = true;
                        infos[j].Add(sqrDist);
                    }
                }
                if (!existDist)
                    infos.Add(new DistInfo(sqrDist));
            }

DistInfo maxDistinfo = new DistInfo();
            foreach (DistInfo distinfo in infos) //求数量最多者
                if (distinfo.count > maxDistinfo.count)
                    maxDistinfo = distinfo;
            distAverage = maxDistinfo.distLvl;
            if ((double)maxDistinfo.count / (double)ranges.Count < rateOfMaxnum)
                return false;
            double da = distAverage;
            ranges = centers.ConvertAll<Range>(delegate(double ctr) { return new Range(ctr - da / 4d, ctr + da / 4d); });
            return true;
        }

/// <summary>
        /// 补充错漏的网格
        /// </summary>
        public static List<Range> FillUpErrors(List<Range> ranges, double max, double centerDistAverage,double errRate)
        {
            if (ranges.Count == 0) return new List<Range>();

double cellWidth = centerDistAverage / 2d;
            List<Range> res = new List<Range>();
            double dist;
            double pos = 0, rate;
            int ridx = 0;
            double stepLen = -1;

while (ridx < ranges.Count)
            {
                if ((rate = (dist = ranges[ridx].Begin - pos) / cellWidth) > errRate)
                {
                    stepLen = dist / (int)(rate + 0.5);
                    while (pos < ranges[ridx].Begin)
                    {
                        res.Add(Range.FromSize(pos, stepLen));
                        pos += stepLen;
                    }
                }
                res.Add(ranges[ridx].clone);
                pos = ranges[ridx].End;
                ridx++;
            }

if ((rate = (dist = max - pos) / cellWidth) > errRate)
            {
                stepLen = dist / (int)(rate + 0.5);
                while (pos < max)
                {
                    res.Add(Range.FromSize(pos, stepLen));
                    pos += stepLen;
                }
            }

return res;
        }

/// <summary>
        /// 过滤掉太小的或距离太近的区域
        /// </summary>
        public static void FilterRanges(int minRangeDist, int minRangeLenght, List<Range> ranges)
        {
            if (ranges.Count == 0) return;
            else if (ranges.Count == 1) { if (ranges[0].Lenght < minRangeLenght) ranges.Clear(); return; }

int count = ranges.Count;
            for (int i = count - 1; i >= 1; i--)
            {
                if (ranges[i].Lenght < minRangeLenght)
                {
                    if (ranges[i].DistanceTo(ranges[i - 1]) <= minRangeDist)
                        ranges[i - 1] = ranges[i - 1].Join(ranges[i]);
                    ranges.RemoveAt(i);
                }
                else
                {
                    if (ranges[i].DistanceTo(ranges[i - 1]) <= minRangeDist)
                    {
                        ranges[i - 1] = ranges[i - 1].Join(ranges[i]);
                        ranges.RemoveAt(i);
                    }
                }
            }
        }

/// <summary>
        /// 重新计算校正图像的阈值并转化为标准ECC200二维码图
        /// </summary>
        public static ThresholdImage ReThreshold(Threshold sourceThresh,PerspectiveTransform trans,int newWidth,int newHeight,double rotate, out Bitmap imageCorrected)
        {
            Bitmap img = trans.Transform(sourceThresh.SrcImage, newWidth, newHeight);
            img = GraphHelper.TransformImage(img, new PointD(newWidth / 2d, newHeight / 2d), 1, rotate, new PointD());
            imageCorrected = img;
            GrayImage gimg = new GrayImage(img); //重新分配阈值
            Threshold thresh = Threshold.CreateThreshold(sourceThresh.ThreshType, img, sourceThresh.ThresholdValue);
            Histogram hist = gimg.Histogram;
            int grayLvl = hist.Level;
            int upperMax = grayLvl, lowerMax = grayLvl;
            double upMaxRate = 0, lwMaxRate = 0;
            double histRate = 0;
            for (int i = grayLvl + 3; i < 255; i += 3) //求后半部分最大颜色
            {
                if ((histRate = hist.Rate((byte)i, (byte)Math.Min(255, i + 3))) > upMaxRate)
                {
                    upMaxRate = histRate;
                    upperMax = i;
                }
            }
            for (int i = grayLvl - 3; i > 0; i -= 3) //求前半部分最大颜色
            {
                if ((histRate = hist.Rate((byte)Math.Max(0, i - 3), (byte)i)) > lwMaxRate)
                {
                    lwMaxRate = histRate;
                    lowerMax = i;
                }
            }

double ulRate = upMaxRate + lwMaxRate; //总和
            gimg.ThresholdValue = (int)(upperMax * (upMaxRate / ulRate) + lowerMax * (lwMaxRate / ulRate)); //最好二值!!!
            ThresholdImage res = gimg.ThresholdImage;
            for (int i = 0; i < res.Height; i++) //尽量填补太亮的部分
                for (int j = 0; j < res.Width; j++)
                    if (thresh[j, i] == 0)
                        res[j, i] = 0;

return res;
        }

/// <summary>
        /// 创建透视矩阵
        /// </summary>
        public static PerspectiveTransform BuildTransform(Line l1, Line l2, Line l3, Line l4)
        {
            PointD p1 = (Straight)l1 & (Straight)l2;
            PointD p2 = (Straight)l2 & (Straight)l3;
            PointD p3 = (Straight)l3 & (Straight)l4;
            PointD p4 = (Straight)l4 & (Straight)l1;
            return new PerspectiveTransform(p1, p2, p3, p4);
        }

public static PerspectiveTransform BuildTransform(Line l1, Line l2, Line l3, Line l4,Bitmap sourceImage,out Bitmap imageRegion)
        {
            PointD p1 = (Straight)l1 & (Straight)l2;
            PointD p2 = (Straight)l2 & (Straight)l3;
            PointD p3 = (Straight)l3 & (Straight)l4;
            PointD p4 = (Straight)l4 & (Straight)l1;
            if (sourceImage == null)
                imageRegion = null;
            else
                imageRegion = GraphHelper.SelectRegionRgb(sourceImage, p1.Point, p2.Point, p3.Point, p4.Point);

return new PerspectiveTransform(p1, p2, p3, p4);
        }

/// <summary>
        /// 获取区域外壳的四个方向的所有点
        /// </summary>
        public static void GetRegionHull(ContourInfo inf, out List<Point> leftSide, out List<Point> topSide, out List<Point> rightSide, out List<Point> bottomSide)
        {
            Rectangle rect = inf.OuterRect.Rectangle;

int[] leftX = new int[rect.Height + 1];
            int[] rightX = new int[rect.Height + 1];
            int[] topY = new int[rect.Width + 1];
            int[] bottomY = new int[rect.Width + 1];

for (int i = 0; i <= rect.Height; i++)
            {
                leftX[i] = int.MaxValue;
                rightX[i] = 0;
            }
            for (int i = 0; i <= rect.Width; i++)
            {
                topY[i] = int.MaxValue;
                bottomY[i] = 0;
            }

Point pt;
            for (int i = 0; i < inf.ContourPoints.Count; i++) //求外壳的点
            {
                pt = inf.ContourPoints[i].Point;
                if (pt.X < leftX[pt.Y - rect.Top])
                    leftX[pt.Y - rect.Top] = pt.X;
                if (pt.X > rightX[pt.Y - rect.Top])
                    rightX[pt.Y - rect.Top] = pt.X;
                if (pt.Y < topY[pt.X - rect.Left])
                    topY[pt.X - rect.Left] = pt.Y;
                if (pt.Y > bottomY[pt.X - rect.Left])
                    bottomY[pt.X - rect.Left] = pt.Y;
            }

topSide = ArrayToList(topY, rect.Left, true);
            leftSide = ArrayToList(leftX, rect.Top, false);
            rightSide = ArrayToList(rightX, rect.Top, false);
            bottomSide = ArrayToList(bottomY, rect.Left, true);
        }

/// <summary>
        /// 计算出区域4个方向的外壳的边界线
        /// </summary>
        public static List<Line> GetRegionEadges(ContourInfo inf) //有待改善,速度太慢
        {
            List<Point> leftSide, topSide, rightSide, bottomSide;
            GetRegionHull(inf, out leftSide, out topSide, out rightSide, out bottomSide);

List<StraightModel> mds = new List<StraightModel>();
            List<StraightModel> sms;
            sms = RansacStrait.FindModels(new StraightModel { distThresh = 3 }, leftSide, 10, 4, 70);
            if (sms.Count > 0)
                mds.Add(sms[0]);
            sms = RansacStrait.FindModels(new StraightModel { distThresh = 3 }, rightSide, 10, 4, 70);
            if (sms.Count > 0)
                mds.Add(sms[0]);
            sms = RansacStrait.FindModels(new StraightModel { distThresh = 3 }, topSide, 10, 4, 70);
            if (sms.Count > 0)
                mds.Add(sms[0]);
            sms = RansacStrait.FindModels(new StraightModel { distThresh = 3 }, bottomSide, 10, 4, 70);
            if (sms.Count > 0)
                mds.Add(sms[0]);

mds.Sort(delegate(StraightModel sm1, StraightModel sm2) { if (sm1.errSum / Math.Sqrt(sm1.Count) < sm2.errSum / Math.Sqrt(sm2.Count)) return -1; else return 1; });
            return mds.ConvertAll<Line>(delegate(StraightModel sm) { return sm.Line; });
        }

/// <summary>
        /// 获取主拐角
        /// </summary>
        public static bool GetMainEadges(List<Line> lines, out Line eadge1, out Line eadge2)
        {
            eadge1 = null;
            eadge2 = null;
            if (lines.Count > 1)
            {
                eadge1 = lines[0];
                if (lines.Count >= 2)
                {
                    eadge2 = lines[1];
                    if (GraphHelper.GetAngleOff(eadge2.Angle - eadge1.Angle) >= 45d)
                        goto ex;
                    else if (lines.Count >= 3)
                    {
                        eadge2 = lines[2];
                        if (GraphHelper.GetAngleOff(eadge2.Angle - eadge1.Angle) >= 45d)
                            goto ex;
                    }
                }
            }
            return false;
        ex:
            PointD cnr = (Straight)eadge1 & (Straight)eadge2;
            if (cnr == null)
                return false;

PointD end1 = cnr.DistanceTo(eadge1.StartPos) > cnr.DistanceTo(eadge1.EndPos) ? eadge1.StartPos : eadge1.EndPos;
            PointD end2 = cnr.DistanceTo(eadge2.StartPos) > cnr.DistanceTo(eadge2.EndPos) ? eadge2.StartPos : eadge2.EndPos;

eadge1 = new Line(cnr, end1);
            eadge2 = new Line(cnr, end2);

return true;
        }

/// <summary>
        /// 获取线段上指定颜色的像素个数
        /// </summary>
        public static int CalColorNum(Threshold timg, Line line,bool isBlackColor)
        {
            PointD dir = line.Direction.Unit;
            double len = line.Length;
            double l = 0;
            PointD cur = line.StartPos;
            Point pt;
            int count = 0;
            while (l < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, timg.Width, timg.Height))
                    break;
                if (timg.Buffers[pt.X + pt.Y * timg.Width] == 0)
                {
                    if (isBlackColor)
                        count++;
                }
                else
                {
                    if (!isBlackColor)
                        count++;
                }

cur += dir;
                l++;
            }
            return count;
        }

public static int CalColorNum(Threshold timg, Line line, bool isBlackColor,out Point? start,out Point? end)
        {
            end = null;
            start = null;
           
            PointD dir = line.Direction.Unit;
            double len = line.Length;
            double l = 0;
            PointD cur = line.StartPos;
            Point pt;
            int count = 0;
            while (l < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, timg.Width, timg.Height))
                    break;
                if (timg.Buffers[pt.X + pt.Y * timg.Width] == 0)
                {
                    if (isBlackColor)
                    {
                        if (start == null)
                            start = pt;
                        else
                            end = pt;
                        count++;
                    }
                }
                else
                {
                    if (!isBlackColor)
                    {
                        if (start == null)
                            start = pt;
                        else
                            end = pt;
                        count++;
                    }
                }

cur += dir;
                l++;
            }
            if (count == 1) end = new Point(start.Value.X, start.Value.Y);
            return count;
        }

public static bool SearchEadge1(Threshold timg, ContourImage cimg, Line sourceLine, PointD startPos, double angleRange, double leastRate, out Line result)
        {
            angleRange = Math.Abs(angleRange);
            Line curLine = new Line(startPos, startPos + sourceLine.Direction);
            result = new Line(startPos, startPos + sourceLine.Direction);
            PointD exDir = (startPos - sourceLine.StartPos).Unit * 3d;

Point? start, end;
            int num0 = CalColorNum(timg, curLine, true, out start, out end), num;
            int aglDir = (startPos - sourceLine.StartPos).ComparaTo(curLine.Direction);
            //bool curIsOutside = true;
            if (num0 / sourceLine.Length >= leastRate)
            {
                aglDir = -aglDir;
                for (double agl = 1; agl < Math.Abs(angleRange) + 1d; agl++)
                {
                    result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, aglDir * agl, new PointD()));
                    num = CalColorNum(timg, result, true, out start, out end);
                    if (num / sourceLine.Length < leastRate / 10)
                    {
                        curLine = result.clone;
                        aglDir = -aglDir;
                        break;
                    }
                    result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, -1 * aglDir * agl, new PointD()));
                    num = CalColorNum(timg, result, true, out start, out end);
                    if (num / sourceLine.Length < leastRate / 10)
                    {
                        curLine = result.clone;
                        break;
                    }
                }
                //curIsOutside = false;
                //if (IsEadgeLike(timg, curLine.StartPos, curLine.Direction.Unit, exDir, sourceLine.Length, leastRate * 0.8, 0.65, out num))
                //    return true;
            }

for (double agl = 0; agl < Math.Abs(angleRange) + 1d; agl ++)
            {
                result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, aglDir * agl, new PointD()));
                num = CalColorNum(timg, result, true, out start, out end);
                if (num / sourceLine.Length >= leastRate)
                {
                    if (IsEadgeLike(timg, result.StartPos, result.Direction.Unit, exDir, sourceLine.Length, leastRate * 0.8, 0.65, out num))
                        return true;
                }
                //if (curIsOutside) //如果起始位置是外部
                //{
                //    return true;
                //}
                //else //如果起始位置是内部
                //{
                //    if (num / sourceLine.Length < leastRate) //如果碰到空白部分
                //    {
                //        result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, aglDir * (agl - 1d), new PointD()));
                //        return true;
                //    }
                //}
            }

result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, aglDir * 30d, new PointD()));
            return false;
        }

/// <summary>
        /// 在大致位置的邻域找出黑白相间的边缘
        /// </summary>
        public static bool SearchEadge(Threshold timg, ContourImage cimg, Line sourceLine, PointD startPos, double angleRange, double leastRate, out Line result)
        {
            angleRange = Math.Abs(angleRange);
            Line curLine = new Line(startPos,startPos + sourceLine.Direction);
            result = new Line(startPos, startPos + sourceLine.Direction);

double bestAngle = 0, bestCount = 0;
            double prevPos, prevNeg, posnum, negnum, maxDist = 0;
            prevPos = prevNeg = CalColorNum(timg, curLine, true);
            int step = 1;
            while (step <= ((int)angleRange / 3d + 1))
            {
                result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, step * 3d, new PointD()));
                posnum = CalColorNum(timg, result, true);
                if (Math.Min(posnum, prevPos) < curLine.Length * 0.05)
                {
                    if (Math.Abs(posnum - prevPos) / (Math.Min(posnum, prevPos) + 1) > maxDist)
                    {
                        maxDist = Math.Abs(posnum - prevPos) / (Math.Min(posnum, prevPos) + 1);
                        if (posnum > prevPos) //如果后面大于前面的
                            bestAngle = step * 3d;
                        else
                            bestAngle = step * 3d - 3d;
                        bestCount = Math.Max(posnum, prevPos);
                    }
                }
                prevPos = posnum;

result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, step * -3d, new PointD()));
                negnum = CalColorNum(timg, result, true);
                if (Math.Min(negnum, prevNeg) < curLine.Length * 0.05)
                {
                    if (Math.Abs(negnum - prevNeg) / (Math.Min(negnum, prevNeg) + 1) > maxDist)
                    {
                        maxDist = Math.Abs(negnum - prevNeg) / (Math.Min(negnum, prevNeg) + 1);
                        if (negnum > prevNeg) //如果后面大于前面的
                            bestAngle = -step * 3d;
                        else
                            bestAngle = 3d - step * 3d;
                        bestCount = Math.Max(negnum, prevNeg);
                    }
                }
                prevNeg = negnum;
                step++;
            }
            if (bestCount / curLine.Length > leastRate)
            {
                result = new Line(startPos, GraphHelper.Transform(curLine.EndPos, startPos, 1, bestAngle, new PointD()));
                return true;
            }

return false;
        }

/// <summary>
        /// 在邻域内进行边缘再校正
        /// </summary>
        public static Line AjustEadge(ContourImage cimg, Line line,int aglDir)
        {
            List<Point> points = new List<Point>();
            PointD p0 = line.StartPos;
            PointD dir = line.Direction.Unit * 8;
            double len = line.Length;
            double l = 0;
            PointD cur = p0;
            Point pt;
            List<Point> pts;
            while (l < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, cimg.Width, cimg.Height))
                    break;
                pts = ImageTransform.CheckRoundPoints(pt.X, pt.Y, 4, 0, cimg.Width, cimg.Height, cimg.Buffers);
                for (int i = 0; i < pts.Count; i++)
                {
                    if (!points.Contains(pts[i]))
                    {
                        if (((PointD)pts[i] - line.StartPos).ComparaTo(line.Direction) * aglDir >= 0)
                            points.Add(pts[i]);
                    }
                }
                cur += dir;
                l += 8;
            }
            List<StraightModel> mds = RansacStrait.FindModels(new StraightModel { distThresh = 3 }, points, 10, 4, points.Count);
            if (mds.Count > 0)
                return mds[0].Line;
            else
                return null;
        }

public static bool IsEadgeLike(Threshold timg, PointD start, PointD dirUnit, PointD exDir, double len, double minRate, double maxRate, out int count)
        {
            int w = timg.Width, h = timg.Height;

double index = 0;
            count = 0;
            PointD cur = start, pd;
            Point pt;
            PointD lastErrPos = null;

while (index < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, w, h)) //如果超出边界,停止
                    break;
                if (timg.Buffers[pt.X + pt.Y * w] == 0) //当前点为区域点
                {
                    lastErrPos = null;
                    pd = cur + exDir;
                    pt = pd.Point;
                    if (GraphHelper.InRange(pt, w, h))
                    {
                        if (timg.Buffers[pt.X + pt.Y * w] != 0) //外部点为空白点
                            count++;
                    }
                    else
                        count++;
                }
                else
                {
                    if (lastErrPos == null)
                        lastErrPos = cur.clone;
                    else
                    {
                        if (cur.DistanceTo(lastErrPos) > (len / 3d))
                        {
                            count = 0;
                            return false;
                        }
                    }
                }
                index++;
                cur += dirUnit;
            }
            double rate = count / len;
            return rate >= minRate && rate <= maxRate; //是否在范围内
        }

/// <summary>
        /// 从起始点开始沿着dirUnit方向扫描此线段上的点是否类似边界
        /// </summary>
        /// <param name="start">要扫描的起始点</param>
        /// <param name="dirUnit">扫描方向</param>
        /// <param name="exDir">这里是重点,因为判断是否是边界不能只是判断当前点在区域内,还要判断和他差距为exDir的点不在区域内</param>
        /// <param name="len">扫描长度</param>
        /// <param name="count">返回满足边界条件的点的总数</param>
        /// <returns>是否满足边界条件</returns>
        bool IsEadgeLike(PointD start, PointD dirUnit, PointD exDir, double len, out int count)
        {
            double index = 0;
            count = 0;
            PointD cur = start, pd;
            Point pt;
            while (index < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, w, h)) //如果超出边界,停止
                    break;
                if (aimg.Datas[pt.X + pt.Y * w] < -1) //当前点为区域点
                {
                    pd = cur + exDir;
                    pt = pd.Point;
                    if (GraphHelper.InRange(pt, w, h))
                    {
                        if (aimg.Datas[pt.X + pt.Y * w] >= -1) //外部点为空白点
                            count++;
                    }
                    else
                        count++;
                }
                index++;
                cur += dirUnit;
            }
            return count >= (int)len / 3;
        }

public static bool IsEadgeLike(AreaImage aimg, PointD start, PointD dirUnit, PointD exDir, double len, double minRate, double maxRate, out int count)
        {
            int w = aimg.Width, h = aimg.Height;
            double index = 0;
            count = 0;
            PointD cur = start, pd;
            Point pt;
            while (index < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, w, h)) //如果超出边界,停止
                    break;
                if (aimg.Datas[pt.X + pt.Y * w] < -1) //当前点为区域点
                {
                    pd = cur + exDir;
                    pt = pd.Point;
                    if (GraphHelper.InRange(pt, w, h))
                    {
                        if (aimg.Datas[pt.X + pt.Y * w] >= -1) //外部点为空白点
                            count++;
                    }
                    else
                        count++;
                }
                index++;
                cur += dirUnit;
            }
            double rate = count / len;
            return rate >= minRate && rate <= maxRate; //是否在范围内
        }

/// <summary>
        /// 从起始点开始沿着dirUnit方向扫描此线段上的点是否类似边界
        /// </summary>
        /// <param name="start">要扫描的起始点</param>
        /// <param name="dirUnit">扫描方向</param>
        /// <param name="exDir">这里是重点,因为判断是否是边界不能只是判断当前点在区域内,还要判断和他差距为exDir的点不在区域内</param>
        /// <param name="len">扫描长度</param>
        /// <param name="minRate">满足条件的比例下限</param>
        /// <param name="maxRate">满足条件的比例上限</param>
        /// <param name="count">返回满足边界条件的点的总数</param>
        /// <returns>是否满足边界条件</returns>
        bool IsEadgeLike(PointD start, PointD dirUnit, PointD exDir, double len, double minRate, double maxRate, out int count)
        {
            double index = 0;
            count = 0;
            PointD cur = start, pd;
            Point pt;
            while (index < len)
            {
                pt = cur.Point;
                if (!GraphHelper.InRange(pt, w, h)) //如果超出边界,停止
                    break;
                if (aimg.Datas[pt.X + pt.Y * w] < -1) //当前点为区域点
                {
                    pd = cur + exDir;
                    pt = pd.Point;
                    if (GraphHelper.InRange(pt, w, h))
                    {
                        if (aimg.Datas[pt.X + pt.Y * w] >= -1) //外部点为空白点
                            count++;
                    }
                    else
                        count++;
                }
                index++;
                cur += dirUnit;
            }
            double rate = count / len;
            return rate >= minRate && rate <= maxRate; //是否在范围内
        }
        /// <summary>
        /// 将数值转为坐标序列
        /// </summary>
        static List<Point> ArrayToList(int[] arr, int start, bool xIsIndex)
        {
            List<Point> pts = new List<Point>();
            if (xIsIndex)
            {
                for (int i = 0; i < arr.Length; i++)
                    pts.Add(new Point(i + start, arr[i]));
            }
            else
            {
                for (int i = 0; i < arr.Length; i++)
                    pts.Add(new Point(arr[i], i + start));
            }
            return pts;
        }

static void FillArray(Dictionary<int, int> dc, int minKey, int maxKey, int dcValue)
        {
            for (int i = minKey; i < maxKey; i++)
                dc.Add(i, dcValue);
        }
    }

public class DistInfo
    {
        public DistInfo() { }
        public DistInfo(double dist)
        {
            distLvl = dist;
            count = 1;
        }
        public DistInfo(double dist, int num)
        {
            distLvl = dist;
            count = num;
        }
        public void Add(double dist)
        {
            distLvl = (distLvl * count + dist) / (++count);
        }
        public double distLvl = 0;
        public int count = 0;
    }
}

标准的视觉ECC200二维码解析(值得一看哦,比很多二维码解析都要强力的:-})相关推荐

  1. 机器人摘果子看图写话_小猴摘果子看图写话二年级

    篇一:小猴摘果子看图写话二年级 刘旭彬 一天,阳光灿烂,小猴子提着一个空篮筐来到苹果树下摘苹果.小猴子尾巴吊在苹果树上摘下苹果一个一个地扔到篮筐里.不一会儿,小猴子就摘满了一篮筐.它高高兴兴地背着一篮 ...

  2. 请用python代码表示什么_深度解析什么是二维码?用Python 5行代码生成个性二维码...

    二维码满天飞, 随便扫一扫就能扫到不一样的内容. 有没有好奇什么是二维码? 又是怎么生成的呢? 今天我们就用python 5行代码 生成一个二维码,并且是个性的二维码,想你所想的,先看效果图,准备好微 ...

  3. uniapp中qrcode生成二维码后传的参数不见了_二维码扫描登录,你必须知道的 3 件事...

    作者 | 互联网平头哥 本文经授权转载自互联网平头哥(ID:it_pingtouge) 扫二维码登录现在比较常见,比如微信.支付宝等 PC 端登录,并且好像每款 APP 都支持扫码登录,不搞个扫码登录 ...

  4. 【Android App】二维码的讲解及生成属于自己的二维码实战(附源码和演示 超详细必看)

    需要全部代码请点赞关注收藏后评论区留言~~~ 一.二维码基本内容介绍 条形码只能表达十几位数字编码,无法表示更复杂的数据. 二维码在二维方格上描出一个个黑点,从而表达更丰富的信息. 二维码早已在手机A ...

  5. python实现二维码识别软件_用 Python 生成 识别二维码

    说到二维码大家一定不陌生,可以说现在二维码几乎渗透到了我们生活的各个角落,举例来说吧,我们到超市商场购物时扫描二维码付款,我们出行时乘坐公交地铁扫描二维码进站,我们到菜鸟驿站取件时扫描二维码取件,如果 ...

  6. 用c#开发微信(2)扫描二维码,用户授权后获取用户基本信息 (源码下载)

    本文将介绍基于Senparc.Weixin微信开发框架来实现网页授权来获取用户基本信息.先生成包含授权及回调url信息的二维码:用户用微信扫描之后,被要求授权以获取Ta的用户基本信息:用户授权后,通过 ...

  7. 你的微信二维码是唯一的吗?【微信二维码的秘密】

    你的微信二维码是唯一的吗?[微信二维码的秘密] 原文:你的微信二维码是唯一的吗?[微信二维码的秘密] 最近听说有一老板被一科技公司业务员忽悠,说"您赶快来注册您唯一的二维码吧!否则,会被别人 ...

  8. QRCode二维码生成方案及其在带LOGO型二维码中的应用(2)

    QRCode二维码生成方案及其在带LOGO型二维码中的应用(2) 原文:QRCode二维码生成方案及其在带LOGO型二维码中的应用(2) 续前:QRCode二维码生成方案及其在带LOGO型二维码中的应 ...

  9. 二维码用的完吗?有没有二维码图案用完的那天?

    不会用完. 二维码是指在一维条码的基础上扩展出另一维具有可读性的条码,使用黑白矩形图案表示二进制数据,被设备扫描后可获取其中所包含的信息.它比传统的Bar Code条形码能存更多的信息,也能表示更多的 ...

最新文章

  1. 微服务架构及分布式事务解决方案
  2. 我设计了一个牛逼的本地缓存!
  3. 蓝桥杯 历届试题 分考场(DFS+枚举)
  4. 手机号归属地区编码_这些关于手机号码的冷知识 你知道吗
  5. css04使用外部样式
  6. Guice 1.0 用户指南
  7. cloudstack centOS安装(二)
  8. 你不知道的华为交换机22个实用技巧
  9. MsChart控件在VC++中的使用(VS2013+MFC+对话框)
  10. 使用tftp服务把路由器的配置上传到服务器
  11. 【Shawn-Git】gitlub的使用指导(针对六届软件杯)
  12. 骨传导耳机推荐,2021骨传导耳机排行榜
  13. 白鹭小游戏开发,并发布到微信平台
  14. windows无法启动MySQL服务(位于本地计算机上)。错误1067:进程意外终止
  15. 2010年财富杂志全球500强榜公布 沃尔玛居榜首
  16. java(小白)判断学生成绩
  17. gurobi证书过期了怎么办
  18. 基于lame对mp3进行分割的简单实现
  19. 基于SSM的网上订餐系统-基于Java Web的网上订餐系统
  20. 短距离无线传输技术分析

热门文章

  1. Armv8-R系列之ARM Cortex-R52 由来
  2. 安防互联网摄像头海康大华硬盘录像机视频流媒体服务器EasyNVR在layer弹出层中使用video标签无法最大化全屏播放问题解决
  3. 获取拉勾网招聘信息数据
  4. 华为无线通用软件开发 实习一面二面
  5. snapchat_如何从Snapchat故事中删除快照
  6. linux 服务器 ssd,关于linux:搭载固态硬盘的服务器究竟比机械硬盘快多少
  7. Feign客户端异常IOException: Incomplete output stream解决方案
  8. finall,finally,finalize
  9. c语言单字符输入和输出函数分别为,c语言第六章字符数据
  10. linux php启动端口,linux中如何开放指定端口