标准的视觉ECC200二维码解析(值得一看哦,比很多二维码解析都要强力的:-})
源图
解析图,绿色的是主边界,红色的是和主边界形成的平行四边形的两条对边,在两条对边进行精确查找得出两条最终的对边
区域图
校正图
二值图
网格图
解码图
//牵连的代码不下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二维码解析(值得一看哦,比很多二维码解析都要强力的:-})相关推荐
- 机器人摘果子看图写话_小猴摘果子看图写话二年级
篇一:小猴摘果子看图写话二年级 刘旭彬 一天,阳光灿烂,小猴子提着一个空篮筐来到苹果树下摘苹果.小猴子尾巴吊在苹果树上摘下苹果一个一个地扔到篮筐里.不一会儿,小猴子就摘满了一篮筐.它高高兴兴地背着一篮 ...
- 请用python代码表示什么_深度解析什么是二维码?用Python 5行代码生成个性二维码...
二维码满天飞, 随便扫一扫就能扫到不一样的内容. 有没有好奇什么是二维码? 又是怎么生成的呢? 今天我们就用python 5行代码 生成一个二维码,并且是个性的二维码,想你所想的,先看效果图,准备好微 ...
- uniapp中qrcode生成二维码后传的参数不见了_二维码扫描登录,你必须知道的 3 件事...
作者 | 互联网平头哥 本文经授权转载自互联网平头哥(ID:it_pingtouge) 扫二维码登录现在比较常见,比如微信.支付宝等 PC 端登录,并且好像每款 APP 都支持扫码登录,不搞个扫码登录 ...
- 【Android App】二维码的讲解及生成属于自己的二维码实战(附源码和演示 超详细必看)
需要全部代码请点赞关注收藏后评论区留言~~~ 一.二维码基本内容介绍 条形码只能表达十几位数字编码,无法表示更复杂的数据. 二维码在二维方格上描出一个个黑点,从而表达更丰富的信息. 二维码早已在手机A ...
- python实现二维码识别软件_用 Python 生成 识别二维码
说到二维码大家一定不陌生,可以说现在二维码几乎渗透到了我们生活的各个角落,举例来说吧,我们到超市商场购物时扫描二维码付款,我们出行时乘坐公交地铁扫描二维码进站,我们到菜鸟驿站取件时扫描二维码取件,如果 ...
- 用c#开发微信(2)扫描二维码,用户授权后获取用户基本信息 (源码下载)
本文将介绍基于Senparc.Weixin微信开发框架来实现网页授权来获取用户基本信息.先生成包含授权及回调url信息的二维码:用户用微信扫描之后,被要求授权以获取Ta的用户基本信息:用户授权后,通过 ...
- 你的微信二维码是唯一的吗?【微信二维码的秘密】
你的微信二维码是唯一的吗?[微信二维码的秘密] 原文:你的微信二维码是唯一的吗?[微信二维码的秘密] 最近听说有一老板被一科技公司业务员忽悠,说"您赶快来注册您唯一的二维码吧!否则,会被别人 ...
- QRCode二维码生成方案及其在带LOGO型二维码中的应用(2)
QRCode二维码生成方案及其在带LOGO型二维码中的应用(2) 原文:QRCode二维码生成方案及其在带LOGO型二维码中的应用(2) 续前:QRCode二维码生成方案及其在带LOGO型二维码中的应 ...
- 二维码用的完吗?有没有二维码图案用完的那天?
不会用完. 二维码是指在一维条码的基础上扩展出另一维具有可读性的条码,使用黑白矩形图案表示二进制数据,被设备扫描后可获取其中所包含的信息.它比传统的Bar Code条形码能存更多的信息,也能表示更多的 ...
最新文章
- 微服务架构及分布式事务解决方案
- 我设计了一个牛逼的本地缓存!
- 蓝桥杯 历届试题 分考场(DFS+枚举)
- 手机号归属地区编码_这些关于手机号码的冷知识 你知道吗
- css04使用外部样式
- Guice 1.0 用户指南
- cloudstack centOS安装(二)
- 你不知道的华为交换机22个实用技巧
- MsChart控件在VC++中的使用(VS2013+MFC+对话框)
- 使用tftp服务把路由器的配置上传到服务器
- 【Shawn-Git】gitlub的使用指导(针对六届软件杯)
- 骨传导耳机推荐,2021骨传导耳机排行榜
- 白鹭小游戏开发,并发布到微信平台
- windows无法启动MySQL服务(位于本地计算机上)。错误1067:进程意外终止
- 2010年财富杂志全球500强榜公布 沃尔玛居榜首
- java(小白)判断学生成绩
- gurobi证书过期了怎么办
- 基于lame对mp3进行分割的简单实现
- 基于SSM的网上订餐系统-基于Java Web的网上订餐系统
- 短距离无线传输技术分析
热门文章
- Armv8-R系列之ARM Cortex-R52 由来
- 安防互联网摄像头海康大华硬盘录像机视频流媒体服务器EasyNVR在layer弹出层中使用video标签无法最大化全屏播放问题解决
- 获取拉勾网招聘信息数据
- 华为无线通用软件开发 实习一面二面
- snapchat_如何从Snapchat故事中删除快照
- linux 服务器 ssd,关于linux:搭载固态硬盘的服务器究竟比机械硬盘快多少
- Feign客户端异常IOException: Incomplete output stream解决方案
- finall,finally,finalize
- c语言单字符输入和输出函数分别为,c语言第六章字符数据
- linux php启动端口,linux中如何开放指定端口