/// <summary> /// 构造函数,由Hog图生成块内归一化后的向量图 /// </summary> /// <param name="hogGram">Hog图</param> /// <param name="blockWidth">块宽</param> /// <param name="blockHeight">块高</param> public NormBlockVectorGram(HogGram hogGram, int blockWidth, int blockHeight) { BlockSize.Width = blockWidth; BlockSize.Height = blockHeight; int hogWidth = hogGram.HogSize.Width; int hogHeight = hogGram.HogSize.Height; int hogPartNumber = hogGram.PartNumber; BlockGramWidth = hogWidth - blockWidth + 1; BlockGramHeight = hogHeight - blockHeight + 1; NormBlockVectors = new ArrayList(); if (hogGram != null && hogWidth >= blockWidth && hogHeight >= blockWidth) { HGram = hogGram; for (int row = 0; row < BlockGramHeight; row++) { for (int col = 0; col < BlockGramWidth; col++) { double[] vec = new double[blockWidth * blockHeight * hogPartNumber]; double vecsum = 0; for (int i = 0; i < blockHeight; i++) { for (int j = 0; j < blockWidth; j++) { for (int p = 0; p < hogPartNumber; p++) { double r = hogGram.HogCells[(col + j) + (row + i) * hogWidth].HistElements[p].rho; // hogGram.HogCells的索引是从小到大。 vec[p + j * hogPartNumber + i * hogPartNumber * blockWidth] = r; //vecsum += r * r; // 这种归一化是将向量约束在“半径”为1的“球面”上,因为r>=0,实际上是分布在1/4个球面 vecsum += r; // 这种归一化是将向量约束在“边长为1的”立方体“内 } } } // 归一化向量(非常重要) for (int i = 0; i < vec.Length; i++) { if (vecsum != 0) { //vec[i] = vec[i] / Math.Sqrt(vecsum); // 对应上述第一种归一化法 vec[i] = vec[i] / vecsum; } else { vec[i] = 0; } } NormBlockVectors.Add(vec); } } } }
/// <summary> /// 绘制Hog示意图 /// </summary> /// <param name="hogGram">Hog图数据</param> /// <param name="width">示意图宽</param> /// <param name="height">示意图高</param> public static Bitmap DrawHogGram(HogGram hogGram, int width, int height) { Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb); Graphics g = Graphics.FromImage(bmp); int grid_width = width / hogGram.HogSize.Width; // 格子宽 int grid_height = height / hogGram.HogSize.Height; // 格子高 // 格子中心相对坐标 PointF rel_center = new PointF((float)(grid_width / 2), (float)(grid_height / 2)); float radius = Math.Min((float)(grid_width / 2), (float)(grid_height / 2)); double maxval = 0; double[] transval = new double[hogGram.HogCells.Length * hogGram.PartNumber]; for (int i = 0; i < hogGram.HogCells.Length; i++) { for (int j = 0; j < hogGram.PartNumber; j++) { transval[j + i * hogGram.PartNumber] = hogGram.HogCells[i].HistElements[j].rho; if (transval[j + i * hogGram.PartNumber] > maxval) { maxval = transval[j + i * hogGram.PartNumber]; } } } for (int i = 0; i < transval.Length; i++) { if (maxval != 0) { transval[i] = 255 * Math.Sqrt(transval[i]) / Math.Sqrt(maxval); } } HogHistElement he; PointF center; PointF right; PointF left; for (int row = 0; row < hogGram.HogSize.Height; row++) { for (int col = 0; col < hogGram.HogSize.Width; col++) { for (int i = 0; i < hogGram.PartNumber; i++) { he = hogGram.HogCells[col + row * hogGram.HogSize.Width].HistElements[i]; center = new PointF(col * grid_width + grid_width / 2, row * grid_height + grid_height / 2); right = new PointF((float)(center.X + radius * Math.Cos(he.theta)), (float)(center.Y + radius * Math.Sin(he.theta))); left = new PointF((float)(center.X - radius * Math.Cos(he.theta)), (float)(center.Y - radius * Math.Sin(he.theta))); int idx = i + (col + row * hogGram.HogSize.Width) * hogGram.PartNumber; // hog示意图体现在某个方向灰度的亮度上,而不是在该方向上线段的长短上 Pen p = new Pen(Color.FromArgb((int)transval[idx], (int)transval[idx], (int)transval[idx])); g.DrawLine(p, right, left); } } } //Pen p = new Pen(Color.Red); //g.DrawLine(p, new PointF(20.5f, 20.5f), new PointF(100.4f, 100.4f)); return(bmp); }
/// <summary> /// 从位图(灰度图)中获取Hog图,位图像素是以从左到右、从下到上的顺序在内存中从低地址排列到高地址 /// 但是C#的Bitmap对象好像把这顺序颠倒过来了,根据实际经验发现,在BitmapData中,位图数据以从左到 /// 右、从上到下的顺序排列,和图像坐标的顺序一致 /// </summary> /// <param name="bitmapSource">源位图</param> /// <param name="cellHeight">单元的高度</param> /// <param name="cellWidth">单元的宽度</param> /// <param name="partNumber">PI弧度等分数</param> /// <returns>Hog图</returns> public static HogGram GetHogFromBitmap(Bitmap bitmapSource, int cellWidth, int cellHeight, int partNumber) { HogGram hogGram = null; if (bitmapSource != null && bitmapSource.PixelFormat == PixelFormat.Format8bppIndexed) { int width = bitmapSource.Width; int height = bitmapSource.Height; hogGram = new HogGram(width, height, cellWidth, cellHeight, partNumber); if (hogGram == null) { return(hogGram); } Rectangle rect = new Rectangle(0, 0, width, height); // 获得位图内容数据 BitmapData dataSource = bitmapSource.LockBits(rect, ImageLockMode.ReadOnly, bitmapSource.PixelFormat); // Stride为位图中每一行以4字节对齐的行宽 int strideSource = dataSource.Stride; unsafe { byte *ptrSource = (byte *)dataSource.Scan0.ToPointer(); byte *ptr1 = null; for (int row = 0; row < height; row++) { ptr1 = ptrSource + strideSource * row; for (int col = 0; col < width; col++) { GradientElement ge = new GradientElement(); if (row == 0 || row == height - 1 || col == 0 || col == width - 1) { ge.rho = 0; ge.theta = 0; } else { // 位图的在内存中的排列是从左到右,从下到上的,但BitmapData貌似做了优化,把位图数据在内存中的排列 // 顺序改为与坐标系一致,即从左到右、从上到下 double gradX = *(ptr1 + 1) - *(ptr1 - 1); double gradY = *(ptr1 + strideSource) - *(ptr1 - strideSource); ge.rho = Math.Sqrt(gradX * gradX + gradY * gradY); ge.theta = Math.Atan2(gradY, gradX); // 注意坐标系是垂直翻转的 } int bmpX = col; int bmpY = row; //int bmpY = height - 1 - row; // 由于BitmapData自动颠倒了位图存储序,所以这个写法是错的。 hogGram.VoteHog(bmpX, bmpY, ge); ptr1++; } } } bitmapSource.UnlockBits(dataSource); } return(hogGram); }
/// <summary> /// 训练正样本 /// </summary> /// <param name="bmp">正样本位图</param> public void TrainPositive(Bitmap bmp) { Bitmap samplebmp = null; double neg_distance = 0; double pos_distance = 0; bool hasinserted = false; // 指明样本是否已插入队列 samplebmp = ImgOper.ResizeImage(bmp, Parameter.DETECT_WINDOW_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height); samplebmp = ImgOper.Grayscale(samplebmp); for (double angle = (-1) * Parameter.ANGLE_BORDER; angle < Parameter.ANGLE_BORDER; angle += Parameter.ANGLE_INTERVAL) { Bitmap bmpclone = ImgOper.RotateImage(samplebmp, angle); bmpclone = ImgOper.ResizeImage(bmpclone, Parameter.DETECT_WINDOW_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height); for (double scale = (-1) * Parameter.SCALE_BORDER; scale < Parameter.SCALE_BORDER; scale += Parameter.SCALE_INTERVAL) { // 往两个方向去,所以是减号 IntPoint lt = new IntPoint((int)(bmpclone.Width * scale / 2), (int)(bmpclone.Height * scale / 2)); IntPoint rt = new IntPoint(bmpclone.Width - 1 - (int)(bmpclone.Width * scale / 2), (int)(bmpclone.Height * scale / 2)); IntPoint rb = new IntPoint(bmpclone.Width - 1 - (int)(bmpclone.Width * scale / 2), bmpclone.Height - 1 - (int)(bmpclone.Height * scale / 2)); IntPoint lb = new IntPoint((int)(bmpclone.Width * scale / 2), bmpclone.Height - 1 - (int)(bmpclone.Height * scale / 2)); Bitmap scalebmp = ImgOper.QuadrilateralTransform(bmpclone, lt, rt, rb, lb); HogGram hogGram = HogGram.GetHogFromBitmap(scalebmp, Parameter.CELL_SIZE.Width, Parameter.CELL_SIZE.Height, Parameter.PART_NUMBER); NormBlockVectorGram blockGram = new NormBlockVectorGram(hogGram, Parameter.BLOCK_SIZE.Width, Parameter.BLOCK_SIZE.Height); Rectangle rect = new Rectangle(0, 0, hogGram.HogSize.Width, hogGram.HogSize.Height); double[] vect = blockGram.GetHogWindowVec(rect); if (Dimension != 0 && vect.Length != Dimension) { throw new Exception("输入正样本的尺寸与其他样本尺寸不一致!"); } ValuedBitmap vbmp = null; if (NegCenter != null && PosCenter != null) { // 计算离正负中心的距离 for (int i = 0; i < vect.Length; i++) { neg_distance += Math.Abs(vect[i] - NegCenter[i]); pos_distance += Math.Abs(vect[i] - PosCenter[i]); } // 与负样本中心重合时,说明是负样本,不能插入正样本队列 if (neg_distance == 0) { return; } // 检测到的正样本加入样本队列的第二道关,如果不够接近正样本中心,就无法加入队列 // 按照Hog检测的判定条件,正距离乘以Parameter.POS_DIST_COEF,使其避开边界 if (neg_distance < pos_distance * Parameter.POS_DIST_COEF) { return; } // 带归一化的系数,如果用pos_distance/neg_distance,值可能会溢出; // 将pos_distance / (pos_distance + neg_distance)作为正样本的评价系数,值越小越接近正样本 vbmp = new ValuedBitmap(scalebmp, pos_distance / (pos_distance + neg_distance)); } else { // 如果正或负样本库还没建立起来,则Val暂时赋值为1 vbmp = new ValuedBitmap(scalebmp, 1); } // 检测到的正样本加入样本队列的第三道关,与正样本评价系数的有序队列比较后,决定是否加入样本队列 hasinserted = InsertValuedBitmap(ref PosMapCollection, vbmp, Parameter.POS_LIMITED_NUMBER); PosLength = PosMapCollection.Count; //// 人工观察正样本插入情况 //if (hasinserted && vbmp != null) //{ // vbmp.VBitmap.Save("Image\\pos_save\\" + poscnt + "_" + vbmp.Val + ".jpg"); // poscnt++; //} // 如果样本已经插入队列,说明样本比较可信,重新计算样本中心 if (hasinserted) { if (PosCenter == null) { Dimension = vect.Length; PosCenter = new double[Dimension]; } for (int i = 0; i < Dimension; i++) { PosCenter[i] = (PosCenter[i] * PosLength + vect[i]) / (PosLength + 1); } } } } }
/// <summary> /// 计算最近正样本距离系数,按照距离而不是相关系数,这样效率高,系数越小,检测对象越接近目标 /// </summary> /// <param name="rect">检测目标区域</param> /// <param name="bmp">位图</param> /// <returns>最近距离系数, double.MaxValue表示计算异常或没计算</returns> private double NearestNeighbour(Rectangle rect, Bitmap bmp) { Bitmap sample = null; Bitmap detect = null; Rectangle gramRect = Rectangle.Empty; HogGram sampleHGram = null; HogGram detectHGram = null; NormBlockVectorGram sampleBlock = null; NormBlockVectorGram detectBlock = null; double[] detectvect = null; double[] samplevect = null; ArrayList posvects = null; ArrayList negvects = null; double minposdist = double.MaxValue; double minnegdist = double.MaxValue; double dist = 0; double nearestdist = double.MaxValue; if (PosLength == 0 || NegLength == 0) { return(nearestdist); } // 正样本载入 posvects = new ArrayList(); for (int i = 0; i < PosLength; i++) { sample = PosMapCollection[i].VBitmap; sample = ImgOper.ResizeImage(sample, Parameter.DETECT_WINDOW_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height); sample = ImgOper.Grayscale(sample); sampleHGram = HogGram.GetHogFromBitmap(sample, Parameter.CELL_SIZE.Width, Parameter.CELL_SIZE.Height, Parameter.PART_NUMBER); sampleBlock = new NormBlockVectorGram(sampleHGram, Parameter.BLOCK_SIZE.Width, Parameter.BLOCK_SIZE.Height); gramRect = new Rectangle(0, 0, sampleHGram.HogSize.Width, sampleHGram.HogSize.Height); samplevect = sampleBlock.GetHogWindowVec(gramRect); posvects.Add(samplevect); } // 负样本载入 negvects = new ArrayList(); for (int i = 0; i < NegLength; i++) { sample = NegMapCollection[i].VBitmap; sample = ImgOper.ResizeImage(sample, Parameter.DETECT_WINDOW_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height); sample = ImgOper.Grayscale(sample); sampleHGram = HogGram.GetHogFromBitmap(sample, Parameter.CELL_SIZE.Width, Parameter.CELL_SIZE.Height, Parameter.PART_NUMBER); sampleBlock = new NormBlockVectorGram(sampleHGram, Parameter.BLOCK_SIZE.Width, Parameter.BLOCK_SIZE.Height); gramRect = new Rectangle(0, 0, sampleHGram.HogSize.Width, sampleHGram.HogSize.Height); samplevect = sampleBlock.GetHogWindowVec(gramRect); negvects.Add(samplevect); } detect = ImgOper.CutImage(bmp, rect.X, rect.Y, rect.Width, rect.Height); detect = ImgOper.ResizeImage(detect, Parameter.DETECT_WINDOW_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height); detect = ImgOper.Grayscale(detect); detectHGram = HogGram.GetHogFromBitmap(detect, Parameter.CELL_SIZE.Width, Parameter.CELL_SIZE.Height, Parameter.PART_NUMBER); detectBlock = new NormBlockVectorGram(detectHGram, Parameter.BLOCK_SIZE.Width, Parameter.BLOCK_SIZE.Height); gramRect = new Rectangle(0, 0, detectHGram.HogSize.Width, detectHGram.HogSize.Height); detectvect = detectBlock.GetHogWindowVec(gramRect); foreach (double[] svect in posvects) { dist = ImgStatCompute.ComputeDistance(detectvect, svect); if (dist < minposdist) { minposdist = dist; } } foreach (double[] svect in negvects) { dist = ImgStatCompute.ComputeDistance(detectvect, svect); if (dist < minnegdist) { minnegdist = dist; } } if (minnegdist != 0 || minposdist != 0) { nearestdist = minposdist / (minposdist + minnegdist); } return(nearestdist); }
/// <summary> /// Hog检测, 被检测到图像自动缩放到BMPLIMITSIZE容忍范围内,并在检测完后将检测框自动放大之前缩小的倍率 /// </summary> /// <param name="bmp">位图</param> public RectangleCollection HogDetect(Bitmap bmp) { RectangleCollection resultCollection = null; if (bmp == null) { return(null); } if (NegCenter == null && PosCenter == null) { return(null); } DateTime dt = DateTime.Now; double elapse = 0; // 针对原图的缩放倍率 double se = 1; if (bmp.Width > Parameter.BMPLIMITSIZE.Width || bmp.Height > Parameter.BMPLIMITSIZE.Height) { se = bmp.Width / (double)Parameter.BMPLIMITSIZE.Width > bmp.Height / (double)Parameter.BMPLIMITSIZE.Height ? bmp.Width / (double)Parameter.BMPLIMITSIZE.Width : bmp.Height / (double)Parameter.BMPLIMITSIZE.Height; bmp = ImgOper.ResizeImage(bmp, (int)(bmp.Width / se), (int)(bmp.Height / se)); } bmp = ImgOper.Grayscale(bmp); //bmp = ImgOper.GaussianConvolution(bmp, GAUSSIAN_SIGMA, GAUSSIAN_SIZE); // 高斯卷积,使得图像平滑 // 所有层的检测结果 ArrayList resultlayers = new ArrayList(); // 初始缩放因子 double scalecoef = 1.0; Bitmap scalebmp = null; int newwidth = (int)(bmp.Width / scalecoef); int newheight = (int)(bmp.Height / scalecoef); // 每层最小距离点的集合 ArrayList idx_layermindistance = new ArrayList(); int cnt = 0; do { scalebmp = ImgOper.ResizeImage(bmp, newwidth, newheight); HogGram hogGram = HogGram.GetHogFromBitmap(scalebmp, Parameter.CELL_SIZE.Width, Parameter.CELL_SIZE.Height, Parameter.PART_NUMBER); NormBlockVectorGram blockGram = new NormBlockVectorGram(hogGram, Parameter.BLOCK_SIZE.Width, Parameter.BLOCK_SIZE.Height); DetectResultLayer detectlayer = new DetectResultLayer(); // !!!!!!检测窗口的像素尺寸必须能被cell尺寸整除!!!!!!像素尺寸除以hog尺寸就是检测窗口的尺寸 detectlayer.DetectResult = blockGram.DetectImgByHogWindow( new Size(Parameter.DETECT_WINDOW_SIZE.Width / Parameter.CELL_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height / Parameter.CELL_SIZE.Height), NegCenter, PosCenter, Parameter.POS_DIST_COEF); if (detectlayer.DetectResult == null) { return(null); } detectlayer.ScaleCoef = scalecoef; resultlayers.Add(detectlayer); // 本层检测结果加入队列 scalecoef *= Parameter.SCALE_COEF; // 逐次缩小图像 newwidth = (int)(bmp.Width / scalecoef); newheight = (int)(bmp.Height / scalecoef); cnt++; } while (newwidth > 2 * Parameter.DETECT_WINDOW_SIZE.Width && newheight > 2 * Parameter.DETECT_WINDOW_SIZE.Height); elapse = DateTime.Now.Subtract(dt).TotalSeconds; // 框出所有可能的物体 WindowResult[] wr = null; Rectangle rect; double mindist = -1; WindowResult min_obj = null; double min_scalecoef = 1; resultCollection = new RectangleCollection(); foreach (DetectResultLayer layer in resultlayers) { wr = layer.DetectResult; for (int i = 0; i < wr.Length; i++) { if (wr[i].label == 1) { if (mindist == -1 || mindist > wr[i].PosDistance) { mindist = wr[i].PosDistance; min_obj = wr[i]; min_scalecoef = layer.ScaleCoef; } rect = new Rectangle((int)(wr[i].ImageRegion.X * layer.ScaleCoef * se), (int)(wr[i].ImageRegion.Y * layer.ScaleCoef * se), (int)(wr[i].ImageRegion.Width * layer.ScaleCoef * se), (int)(wr[i].ImageRegion.Height * layer.ScaleCoef * se)); resultCollection.Add(rect); } } } //rect = new Rectangle((int)(min_obj.ImageRegion.X * min_scalecoef * se), // (int)(min_obj.ImageRegion.Y * min_scalecoef * se), // (int)(min_obj.ImageRegion.Width * min_scalecoef * se), // (int)(min_obj.ImageRegion.Height * min_scalecoef * se)); //resultCollection.Add(rect); return(resultCollection); }
/// <summary> /// 训练负样本 /// </summary> /// <param name="bmp">负样本位图</param> public void TrainNegative(Bitmap bmp) { Bitmap samplebmp = null; double neg_distance = 0; double pos_distance = 0; bool hasinserted = false; // 指明样本是否已插入队列 if (bmp.Width / Parameter.DETECT_WINDOW_SIZE.Width > bmp.Height / Parameter.DETECT_WINDOW_SIZE.Height) { samplebmp = ImgOper.ResizeImage(bmp, (int)(bmp.Width * Parameter.DETECT_WINDOW_SIZE.Height / bmp.Height), Parameter.DETECT_WINDOW_SIZE.Height); } else { samplebmp = ImgOper.ResizeImage(bmp, Parameter.DETECT_WINDOW_SIZE.Width, (int)(bmp.Height * Parameter.DETECT_WINDOW_SIZE.Width / bmp.Width)); } samplebmp = ImgOper.CutImage(samplebmp, 0, 0, Parameter.DETECT_WINDOW_SIZE.Width, Parameter.DETECT_WINDOW_SIZE.Height); samplebmp = ImgOper.Grayscale(samplebmp); HogGram hogGram = HogGram.GetHogFromBitmap(samplebmp, Parameter.CELL_SIZE.Width, Parameter.CELL_SIZE.Height, Parameter.PART_NUMBER); NormBlockVectorGram blockGram = new NormBlockVectorGram(hogGram, Parameter.BLOCK_SIZE.Width, Parameter.BLOCK_SIZE.Height); Rectangle rect = new Rectangle(0, 0, hogGram.HogSize.Width, hogGram.HogSize.Height); double[] vect = blockGram.GetHogWindowVec(rect); if (Dimension != 0 && vect.Length != Dimension) { throw new Exception("输入负样本的尺寸与其他样本尺寸不一致!"); } ValuedBitmap vbmp = null; if (PosCenter != null && NegCenter != null) { // 计算离正负中心的距离 for (int i = 0; i < vect.Length; i++) { neg_distance += Math.Abs(vect[i] - NegCenter[i]); pos_distance += Math.Abs(vect[i] - PosCenter[i]); } // 与正样本中心重合时,说明是正样本,不能插入负样本队列 if (pos_distance == 0) { return; } // 负样本加入样本队列的第二道关,如果不够接近负样本中心,就无法加入队列 // 按照Hog检测的判定条件,正距离乘以Parameter.POS_DIST_COEF,使其避开边界 if (pos_distance * Parameter.POS_DIST_COEF < neg_distance) { return; } // 带归一化的系数,如果用neg_distance / pos_distance,值可能会溢出; // 将neg_distance / (pos_distance + neg_distance)作为负样本的评价系数,值越小越接近负样本 vbmp = new ValuedBitmap(samplebmp, neg_distance / (pos_distance + neg_distance)); } else { // 如果正样本库还没建立起来,则Val暂时赋值为1 vbmp = new ValuedBitmap(samplebmp, 1); } // 负样本加入样本队列的第三道关,与负样本评价系数的有序队列比较后,决定是否加入样本队列 hasinserted = InsertValuedBitmap(ref NegMapCollection, vbmp, Parameter.NEG_LIMITED_NUMBER); NegLength = NegMapCollection.Count; //// 人工观察负样本插入情况 //if (hasinserted && vbmp != null) //{ // vbmp.VBitmap.Save("Image\\neg_save\\" + negcnt + "_" + vbmp.Val + ".jpg"); // negcnt++; //} // 如果样本已经插入队列,说明样本比较可信,重新计算样本中心 if (hasinserted) { if (NegCenter == null) { Dimension = vect.Length; NegCenter = new double[Dimension]; } for (int i = 0; i < Dimension; i++) { NegCenter[i] = (NegCenter[i] * NegLength + vect[i]) / (NegLength + 1); } } }