摄像头车牌图像识别
技术:C#+WinForm+Windows API
概述
使用C#进行车牌检测和车牌识别,车牌检测包括图像分割和特征提取,车牌识别是指对检测到的车牌进行内容识别。利用 Visual Studio 2015 集成开发环境,采用System.Drawing命名空间的类来处理图片、Windows API控制摄像头。
详细
一、运行效果
二、实现过程
①、图像处理模块
图像处理使用了灰度化、灰度均衡化、高斯滤波、边缘检测、二值化等技术实现了车牌图像的定位和检测,再通过对已有图片的特征匹配来实现车牌图片的识别。
//车辆图片处理事件 private void t灰度化() { if (m_Bitmap != null) { int tt = 0; for (int i = 0; i < 256; i++)//清掉数组gray里的数据 { gray[i] = 0; } for (int i = 0; i < 256; i++)//清掉数组rr里的数据 { rr[i] = 0; } for (int i = 0; i < 256; i++)//清掉数组gg里的数据 { gg[i] = 0; } for (int i = 0; i < 256; i++)//清掉数组bb里的数据 { bb[i] = 0; } BitmapData bmData = m_Bitmap.LockBits(new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = bmData.Stride;//获取或设置 Bitmap 对象的跨距宽度(也称为扫描宽度)。 System.IntPtr Scan0 = bmData.Scan0;//获取或设置位图中第一个像素数据的地址。 它也可以看成是位图中的第一个扫描行 unsafe { byte* p = (byte*)(void*)Scan0; int nOffset = stride - m_Bitmap.Width * 3; byte red, green, blue; int nWidth = m_Bitmap.Width; int nHeight = m_Bitmap.Height; for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { blue = p[0]; green = p[1]; red = p[2]; tt = p[0] = p[1] = p[2] = (byte)(.299 * red + .587 * green + .114 * blue); rr[red]++; gg[green]++; bb[blue]++; gray[tt]++; //统计灰度值为tt的象素点数目 p += 3; } p += nOffset; } } m_Bitmap.UnlockBits(bmData); flag = 1; graydo(); } } private void t灰度均衡化() { if (m_Bitmap != null) { BitmapData bmData = m_Bitmap.LockBits(new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); //加入内存进行处理 int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0;//扫描的第一行 int tt = 0; int[] SumGray = new int[256]; for (int i = 0; i < 256; i++) { SumGray[i] = 0; } unsafe { byte* p = (byte*)(void*)Scan0; int nOffset = stride - m_Bitmap.Width * 3; int nHeight = m_Bitmap.Height; int nWidth = m_Bitmap.Width; SumGray[0] = gray[0];//灰度均衡化 for (int i = 1; i < 256; ++i)//灰度级频度数累加 SumGray[i] = SumGray[i - 1] + gray[i]; for (int i = 0; i < 256; ++i) //计算调整灰度值 频率乘以灰度总级数得出该灰度变换后的灰度级 SumGray[i] = (int)(SumGray[i] * 255 / count); for (int i = 0; i < 256; i++) { gray[i] = 0; } for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { tt = p[0] = p[1] = p[2] = (byte)(SumGray[p[0]]); gray[tt]++; p += 3; } p += nOffset; } } m_Bitmap.UnlockBits(bmData); flag = 1; graydo(); } } private void t高斯滤波() { if (m_Bitmap != null) { BitmapData bmData = m_Bitmap.LockBits(new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); for (int i = 0; i < 256; i++) { gray[i] = 0; } unsafe { int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; byte* p = (byte*)(void*)Scan0; byte* pp; int tt; int nOffset = stride - m_Bitmap.Width * 3; int nWidth = m_Bitmap.Width; int nHeight = m_Bitmap.Height; long sum = 0; int[,] gaussianMatrix = { { 1, 2, 3, 2, 1 }, { 2, 4, 6, 4, 2 }, { 3, 6, 7, 6, 3 }, { 2, 4, 6, 4, 2 }, { 1, 2, 3, 2, 1 } };//高斯滤波器所选的n=5模板 for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { if (!(x <= 1 || x >= nWidth - 2 || y <= 1 || y >= nHeight - 2)) { pp = p; sum = 0; int dividend = 79; for (int i = -2; i <= 2; i++) for (int j = -2; j <= 2; j++) { pp += (j * 3 + stride * i); sum += pp[0] * gaussianMatrix[i + 2, j + 2]; if (i == 0 && j == 0) { if (pp[0] > 240)//如果模板中心的灰度大于240 { sum += p[0] * 30; dividend += 30; } else if (pp[0] > 230) { sum += pp[0] * 20; dividend += 20; } else if (pp[0] > 220) { sum += p[0] * 15; dividend += 15; } else if (pp[0] > 210) { sum += pp[0] * 10; dividend += 10; } else if (p[0] > 200) { sum += pp[0] * 5; dividend += 5; } } pp = p; } sum = sum / dividend; if (sum > 255) { sum = 255; } p[0] = p[1] = p[2] = (byte)(sum); } tt = p[0]; gray[tt]++; p += 3; } p += nOffset; } } flag = 1; m_Bitmap.UnlockBits(bmData); graydo(); } } //定位处理事件 private void sobel边缘检测() { if (m_Bitmap != null) { BitmapData bmData = m_Bitmap.LockBits(new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); float valve = 67; for (int i = 0; i < 256; i++) { gray[i] = 0; } unsafe { int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; byte* p = (byte*)(void*)Scan0; byte* pp; int tt; int nOffset = stride - m_Bitmap.Width * 3; int nWidth = m_Bitmap.Width; int nHeight = m_Bitmap.Height; int Sx = 0; int Sy = 0; // float max = 0; double sumM = 0; double sumCount = 0; int[] marginalMx = { -1, 0, 1, -2, 0, 2, -1, 0, 1 }; //sobel模板 int[] marginalMy = { 1, 2, 1, 0, 0, 0, -1, -2, -1 }; int[,] dlta = new int[nHeight, nWidth]; for (int y = 0; y < nHeight; ++y) //sobel算子 { for (int x = 0; x < nWidth; ++x) { if (!(x <= 0 || x >= nWidth - 1 || y <= 0 || y >= nHeight - 1)) { pp = p; Sx = 0; Sy = 0; for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { pp += (j * 3 + stride * i); Sx += pp[0] * marginalMx[(i + 1) * 3 + j + 1]; Sy += pp[0] * marginalMy[(i + 1) * 3 + j + 1]; pp = p; } m[y, x] = (int)(Math.Sqrt(Sx * Sx + Sy * Sy)); if (m[y, x] > valve / 2) //增强白点 { if (p[0] > 240) { m[y, x] += valve; } else if (p[0] > 220) { m[y, x] += (float)(valve * 0.8); } else if (p[0] > 200) { m[y, x] += (float)(valve * 0.6); } else if (p[0] > 180) { m[y, x] += (float)(valve * 0.4); } else if (p[0] > 160) { m[y, x] += (float)(valve * 0.2); } } float tan; if (Sx != 0) { tan = Sy / Sx; } else tan = 10000; if (-0.41421356 <= tan && tan < 0.41421356)//角度为-22.5度到22.5度之间 { dlta[y, x] = 0; // m[y,x]+=valve; } else if (0.41421356 <= tan && tan < 2.41421356)//角度为22.5度到67.5度之间 { dlta[y, x] = 1; //m[y,x] = 0; } else if (tan >= 2.41421356 || tan < -2.41421356)//角度为67.5度到90度之间或-90度到-67.5度 { dlta[y, x] = 2; // m[y,x]+=valve; } else { dlta[y, x] = 3;//m[y,x] = 0; } } else m[y, x] = 0; p += 3; if (m[y, x] > 0) { sumCount++; sumM += m[y, x]; } } p += nOffset; } p = (byte*)(void*)Scan0; //非极大值抑制和阀值 for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { if (m[y, x] > sumM / sumCount * 1.2) { p[0] = p[1] = p[2] = (byte)(m[y, x]); //m[y,x]=1; } else { m[y, x] = 0; p[0] = p[1] = p[2] = 0; } if (x >= 1 && x <= nWidth - 1 && y >= 1 && y <= nHeight - 1 && m[y, x] > valve) { switch (dlta[y, x]) { case 0: if (m[y, x] >= m[y, x - 1] && m[y, x] >= m[y, x + 1])//水平边缘 { p[0] = p[1] = p[2] = 255; } break; case 1: if (m[y, x] >= m[y + 1, x - 1] && m[y, x] >= m[y - 1, x + 1])//正斜45度边缘 { p[0] = p[1] = p[2] = 255; } break; case 2: if (m[y, x] >= m[y - 1, x] && m[y, x] >= m[y + 1, x])//垂直边缘 { p[0] = p[1] = p[2] = 255; } break; case 3: if (m[y, x] >= m[y + 1, x + 1] && m[y, x] >= m[y - 1, x - 1])//反斜45度边缘 { p[0] = p[1] = p[2] = 255; } break; } } if (p[0] == 255) { m[y, x] = 1; } else { m[y, x] = 0; p[0] = p[1] = p[2] = 0; } tt = p[0]; gray[tt]++; p += 3; } // p += nOffset; } m_Bitmap.UnlockBits(bmData); flag = 1; graydo(); } } } private void 车牌定位() { // always_Bitmap = (Bitmap)Bitmap.FromFile(name, false); this.c_Bitmap = Recoginzation.licensePlateLocation(m_Bitmap, always_Bitmap, m); extract_Bitmap_one = c_Bitmap.Clone(new Rectangle(0, 0, c_Bitmap.Width, c_Bitmap.Height), PixelFormat.DontCare); this.panel1.Invalidate(); this.LocatedPanel.Invalidate(); this.ExtractPanel.Invalidate(); } //车牌处理事件 private void c灰度化() { if (c_Bitmap != null) { int tt = 0; for (int i = 0; i < 256; i++)//清掉数组gray里的数据 { gray[i] = 0; } for (int i = 0; i < 256; i++)//清掉数组rr里的数据 { rr[i] = 0; } for (int i = 0; i < 256; i++)//清掉数组gg里的数据 { gg[i] = 0; } for (int i = 0; i < 256; i++)//清掉数组bb里的数据 { bb[i] = 0; } BitmapData bmData = c_Bitmap.LockBits(new Rectangle(0, 0, c_Bitmap.Width, c_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = bmData.Stride;//获取或设置 Bitmap 对象的跨距宽度(也称为扫描宽度)。 System.IntPtr Scan0 = bmData.Scan0;//获取或设置位图中第一个像素数据的地址。 它也可以看成是位图中的第一个扫描行 unsafe { byte* p = (byte*)(void*)Scan0; int nOffset = stride - c_Bitmap.Width * 3; byte red, green, blue; int nWidth = c_Bitmap.Width; int nHeight = c_Bitmap.Height; for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { blue = p[0]; green = p[1]; red = p[2]; tt = p[0] = p[1] = p[2] = (byte)(.299 * red + .587 * green + .114 * blue); rr[red]++; gg[green]++; bb[blue]++; gray[tt]++; //统计灰度值为tt的象素点数目 p += 3; } p += nOffset; } } c_Bitmap.UnlockBits(bmData); flag = 2; this.LocatedPanel.Invalidate(); panel1.Invalidate(); graydo(); } } private void c二值化() { if (c_Bitmap != null) { int Mr = 0;//灰度均值 long sum = 0; int count = 0; for (int i = 0; i < 256; i++)//像素个数与灰度等级的乘积除以像素个数 { sum += gray[i] * i; count += gray[i]; } Mr = (int)(sum / count); int sum1 = 0; int count1 = 0; for (int i = 0; i <= Mr; i++) { sum1 += gray[i] * i; count1 += gray[i]; } int g1 = sum1 / count1; int sum2 = 0; int count2 = 0; for (int i = Mr; i <= 255; i++) { sum2 += gray[i] * i; count2 += gray[i]; } int g2 = sum2 / count2; //求阀值 int va; if (count1 < count2) {//白底黑字 va = Mr - count1 / count2 * Math.Abs(g1 - Mr); } else //黑底白字 va = Mr + count2 / count1 * Math.Abs(g2 - Mr); BitmapData bmData = c_Bitmap.LockBits(new Rectangle(0, 0, c_Bitmap.Width, c_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; byte* p = (byte*)(void*)Scan0; int nOffset = stride - c_Bitmap.Width * 3; int nWidth = c_Bitmap.Width; int nHeight = c_Bitmap.Height; for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { if (p[0] > va) { p[0] = p[1] = p[2] = 255; } else p[0] = p[1] = p[2] = 0; p += 3; } p += nOffset; } } c_Bitmap.UnlockBits(bmData); LocatedPanel.Invalidate(); } }
class Recoginzation { #region 车牌定位算法 public static Bitmap licensePlateLocation(Bitmap m_Bitmap, Bitmap always_Bitmap, float[,] m) { Bitmap c_Bitmap =null ; if (m_Bitmap != null) { BitmapData bmData = m_Bitmap.LockBits(new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int nWidth = m_Bitmap.Width; int nHeight = m_Bitmap.Height; int div = 7; int lv = 11; int pWR, pWL, pWHL, pWHR, pWH; long sM = 0; int ccm = 0; int Wmin = 1; int Wmax = 9; int Bmin = 1; int Bmax = 5; bool getStart; bool[] lineLabel = new bool[(int)(nHeight / div) + 1]; double[] sumC = new double[(int)(nHeight / div) + 1]; int[,] countMatch = new int[(int)(nHeight / div) + 1, (int)(nWidth / lv) + 1]; int[,] mark = new int[(int)(nHeight / div) + 1, nWidth]; unsafe { for (int i = 0; i < (int)(nHeight / div) + 1; i++) { for (int j = 0; j < (int)(nWidth / lv) + 1; j++) { countMatch[i, j] = 0; } } int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; byte* p = (byte*)(void*)Scan0; byte* pp; for (int y = 2 * div; y < nHeight - 2 * div; ) { for (int j = 0; j < nWidth; j++) { mark[y / div, j] = 0; } for (int i = 0; i < div; i++) { getStart = true; for (pWR = pWL = pWHL = pWHR = pWH = 4; pWH < nWidth - 1; pWH++) { if (getStart) //标记每行的第一个白点 { if (m[y + i, pWH] > 0) { getStart = false; pWR = pWL = pWHL = pWHR = pWH; } else continue; } if (pWR - pWL > nWidth / 3 || pWHL - pWR > nWidth / 3 || pWHR - pWHL > nWidth / 3) { goto L; } if (m[y + i, pWH - 1] > 0 && m[y + i, pWH] <= 0)//白-->黑 { pWHR = pWH - 1; if (pWL != pWHL) { if ((Wmin <= (pWR - pWL) && (pWR - pWL) <= Wmax) || (Bmin <= (pWHL - pWR - 1) && (pWHL - pWR - 1) <= Bmax) || (Wmin <= (pWHR - pWHL) && (pWHR - pWHL) <= Wmax)) { if (-pWL + pWHR < 30) //记录该点 if (pWR-pWL+pWHR-pWHL<11) { double rate1 = Wmax / (Math.Abs((pWR - pWL) - (Wmax - Wmin)) / 2 + 1); double rate2 = Wmax / (Math.Abs((pWHR - pWHL) - (Wmax - Wmin)) / 2 + 1); double rate3 = Bmax * 3 / (pWHL - pWR); mark[y / div, pWL + (pWR - pWL) / 2] += (int)(rate3 + rate2 + rate2); } } if (pWR - pWL > 2 * lv) { for (int t = pWL + lv / 2; t < pWR - lv / 2; t += lv) //连续白(或)宽于一个字符宽度 { countMatch[y / div, t / lv] = -1; } //countMatch[y/div,pWR/lv]=-1; } if (pWHL - pWR - 1 > 2 * lv) { for (int t = pWR + lv / 2; t < pWHL - lv / 2; t += lv) { countMatch[y / div, t / lv] -= 1; } //countMatch[y/div,pWHL/lv]=-1; } if (pWHR - pWHL > 2 * lv) { for (int t = pWHL + lv / 2; t < pWHR - lv / 2; t += lv) { countMatch[y / div, t / lv] -= 1; } //countMatch[y/div,pWHR/lv]=-1; } } pWR = pWHR; pWL = pWHL; } else if (m[y + i, pWH - 1] <= 0 && m[y + i, pWH] > 0)//黑-->白 { pWHL = pWH; } } } L: y += div; } ////////////////////////////////////////////////////////////////////////// //去除噪音 ////////////////////////////////////////////////////////////////////////// /// //基与特征点水平间隔的去噪 int toCheck = -1; foreach (int i in sumC) { sumC[i] = 0; } sM = 0; ccm = 0; //累计连续的特征点 for (int i = 2; i < (int)(nHeight / div) - 1; i++) { toCheck = -1; lineLabel[i] = false; //sumLX=0; pWL = pWR = 1; getStart = true; for (int j = 1; j < nWidth; j++) { //标记每行的第一个白点 if (getStart) { if (m[i, j] > 0) { getStart = false; pWR = pWL = j; } else continue; } ///* if (mark[i, j] > 0) { if (toCheck == -1) { toCheck = j; continue; } else { if (j - toCheck <= 1) { if (countMatch[i, j / lv] >= 0) { countMatch[i, j / lv] += (mark[i, toCheck] + mark[i, j]);//两个点相互匹配,累加2 } toCheck = -1; //lineLabel[i]=true; continue; } else { //mark[i,toCheck]-=(int)(div*0.8);//除去该特征点 if (mark[i, toCheck] < (div * 0.7)) { mark[i, toCheck] = 0; } else { countMatch[i, j / lv] += 2 * mark[i, toCheck]; } toCheck = j; continue; } } } } } //阀值化 sM = 0; ccm = 0; int va = (int)(lv * div / 3); int[] countL = new int[(int)(nHeight / div) + 1]; for (int i = 0; i < (int)(nHeight / div) + 1; i++) { bool ok; ok = false; countL[i] = 0; lineLabel[i] = false; for (int j = 0; j < (int)(nWidth / lv) + 1; ++j) { //图像周边特征点为零 if (i == 0 || i == (int)(nHeight / div) || j == 0 || j == (int)(nWidth / lv)) { countMatch[i, j] = 0; continue; } if (countMatch[i, j] > va) {//阀值去噪音 if ((countMatch[i, j - 1] <= va && countMatch[i, j + 1] <= va) ||//去除孤立点(水平) (countMatch[i - 1, j] <= va && (countMatch[i + 1, j] <= va || (countMatch[i + 1, j - 1] <= va && countMatch[i + 1, j + 1] <= va))))//去除孤立点(垂直) { countMatch[i, j] = 0; } else { countL[i] += countMatch[i, j]; ok = true; } } else countMatch[i, j] = 0; } if (ok) { lineLabel[i] = true; sM += countL[i]; ccm++; } } //去除上半部分大面积的噪音 int v1 = 0, v2 = 0; int vm1 = 0, vm2 = 0; int maxL = 0, cv = 0; for (int i = 1; i < (int)(nHeight / div) + 1; i++) { if (lineLabel[i] == true && lineLabel[i - 1] == false) { v1 = i; v2 = i; } else if (lineLabel[i] == false && lineLabel[i - 1] == true) { v2 = i; cv++; if (maxL < v2 - v1) { vm1 = v1; vm2 = v2; maxL = v2 - v1; } } } if (cv > 1 && vm2 - vm1 > 5 && vm1 + (vm2 - vm1 + 1) / 2 < (nHeight / div) / 3 || vm2 - vm1 > nHeight / div / 2) { for (int k = vm1; k <= vm2; k++) { lineLabel[k] = false; } } int p1 = 0, p2 = 0; for (int i = 0; i < (int)(nHeight / div) + 1; i++) { if (lineLabel[i] == true) { p1 = 0; p2 = 0; bool ok = false; for (int j = 1; j < (int)(nWidth / lv) + 1; j++) { if (countMatch[i, j - 1] == 0 && countMatch[i, j] > 0) { p1 = p2 = j; } if (countMatch[i, j - 1] > 0 && countMatch[i, j] == 0) { p2 = j - 1; if (p2 - p1 > 0) { ok = true; } else { p2 = p1 = 0; countMatch[i, j - 1] = 0; } } } if (!ok && p2 == 0 && p1 == 0) { lineLabel[i] = false; } } } ////////////////////////////////////////////////////////////////////////// //使用2×6矩阵粗定位 ////////////////////////////////////////////////////////////////////////// int lLenght = 5, vLenght = 1; int maxAverage = 0; int maxX1 = 0; int maxY1 = 0; for (int i = 0; i < (int)(nHeight / div) + 1; i++) { if (lineLabel[i] == true && lineLabel[i + 1] == true) { for (int j = 0; j < (int)(nWidth / lv) - lLenght; ++j) { int average = countMatch[i, j] + countMatch[i, j + 1] + countMatch[i, j + 2] + countMatch[i, j + 3] + countMatch[i, j + 4] + countMatch[i, j + 5]// +countMatch[i,j+6] + countMatch[i + 1, j] + countMatch[i + 1, j + 1] + countMatch[i + 1, j + 2] + countMatch[i + 1, j + 3] + countMatch[i + 1, j + 4] + countMatch[i + 1, j + 5];// +countMatch[i+1,j+6] ; average = average / (lLenght + 1) / (vLenght + 1); if (maxAverage < average) { maxAverage = average; maxX1 = i; maxY1 = j; } } } } bool jx1 = true, jx2 = true; int x1 = 0, x2 = 0; for (int j = 0; jx1 || jx2; j++) { if (jx1 && lineLabel[maxX1 - j] == false) { jx1 = false; x1 = maxX1 - j; } if (jx2 && lineLabel[maxX1 + j] == false) { jx2 = false; x2 = maxX1 + j; } } for (int i = 0; i < x1; i++) { lineLabel[i] = false; } for (int i = x2; i < (int)(nHeight / div) + 1; i++) { lineLabel[i] = false; } ////////////////////////////////////////////////////////////////////////// //寻找车牌的四边 ////////////////////////////////////////////////////////////////////////// // 位置调整 int lKZValve = (int)(maxAverage / 3); int vKZValve = (int)(maxAverage / 2.5); //int kz1=0,kz2=0; int pX1 = 0, pX2 = 0, pX3 = 0, pX4 = 0, pY1 = 0, pY2 = 0, pY3 = 0, pY4 = 0; //用于搜索边框的范围 int pXU = 0, pXD = 0, pYL = 0, pYR = 0, pXM = 0, pYM = 0; //除去两边噪音 bool l = true, r = true; pY1 = maxY1; pY4 = maxY1 + lLenght; for (int j = 1; l || r; j++) { if (maxY1 - j < 0 && l) { l = false; //pY1=0; } else if (l && countMatch[maxX1, maxY1 - j] < vKZValve && countMatch[maxX1 + 1, maxY1 - j] < vKZValve) { if (maxY1 - j - 2 >= 0 && countMatch[maxX1, maxY1 - j - 2] < vKZValve && countMatch[maxX1 + 1, maxY1 - j - 2] < vKZValve) { l = false; pY1 = maxY1 - j + 1; } } if (maxY1 + lLenght + j > (int)(nWidth / lv) && r) { r = false; pY4 = (int)(nWidth / lv); } else if (r && countMatch[maxX1, maxY1 + lLenght + j] < vKZValve && countMatch[maxX1 + 1, maxY1 + lLenght + j] < vKZValve) { if (maxY1 + lLenght + j + 2 < (int)(nWidth / lv) + 1 && countMatch[maxX1, maxY1 + lLenght + j + 2] < vKZValve && countMatch[maxX1 + 1, maxY1 + lLenght + j + 2] < vKZValve) { r = false; pY4 = maxY1 + lLenght + j - 1; } } } pY2 = (pY1 + 1) * lv; pY3 = (pY4 - 1) * lv; // 进一步去除不必要的边线 bool u = true, d = true; pX1 = maxX1; pX4 = maxX1 + vLenght; while (u || d) { if (u && pX1 - 1 < 0) { u = false; //pX1=0; } else if (u && lineLabel[pX1 - 1]) { bool ok = false; for (int j = pY1; j <= pY4; j++) { if (pX1 - 1 >= 0 && countMatch[pX1 - 1, j] > lKZValve) { ok = true; pX1--; break; } } if (!ok) { u = false; } } else u = false; if (d && pX4 + 1 > (int)(nHeight / div)) { d = false; } else if (d && lineLabel[pX4 + 1]) { bool ok = false; for (int j = pY1; j <= pY4; j++) { if (pX4 + 1 < (int)(nHeight / div) + 1 && countMatch[pX4 + 1, j] > lKZValve) { ok = true; pX4++; break; } } if (!ok) { d = false; } } else d = false; } pXM = pX1 * div + (pX4 - pX1) / 2 * div; pYM = pY1 * lv + (pY4 - pY1) / 2 * lv; //maxX1=x1; vLenght = x2 - x1; //水平再调整 l = true; r = true; while (l || r) { if (pY1 - 1 < 0 && l) { l = false; } else if (l) { bool match = false; for (int i = 0; i <= vLenght; i++) { if (countMatch[x1 + i, pY1 - 1] > vKZValve) { match = true; break; } } if (!match) { l = false; } else pY1--; } if (pY4 + 1 > (int)(nWidth / lv) && r) { r = false; } else if (r) { bool match = false; for (int i = 0; i <= vLenght; i++) { if (countMatch[x1 + i, pY4 + 1] > vKZValve) { match = true; break; } } if (!match) { r = false; } else pY4++; } } for (int i = 0; i < pX1; i++) { lineLabel[i] = false; } for (int i = pX4 + 1; i < (int)(nHeight / div) + 1; i++) { lineLabel[i] = false; } pX2 = x1 * div - div / 2; if (pX2 < 0) { pX2 = 0; } pX3 = x2 * div + div / 2; if (pX3 >= nHeight) { pX3 = nHeight - 1; } pYL = pY1 * lv;//-lv; bool kz = false; for (int i = x1; i <= x2; i++) { if (countMatch[i, pY1] > vKZValve) { kz = true; break; } if (pY1 - 1 >= 0 && countMatch[i, pY1 - 1] > vKZValve) { pYL -= lv; break; } } if (kz) { pYL -= lv / 2; } if (pYL <= 0) { pYL = 0; } pYR = (pY4 + 1) * lv + lv / 2;//+lv; kz = false; for (int i = x1; i <= x2; i++) { if (pY4 + 1 < (int)(nWidth / lv) + 1 && countMatch[i, pY4 + 1] > vKZValve) { kz = true; break; } if (pY4 + 2 < (int)(nWidth / lv) + 1 && countMatch[i, pY4 + 2] > vKZValve) { pYR += lv; break; } } if (kz) { pYR += lv / 2; } if (pYR >= nWidth) { pYR = nWidth - 1; } if (pX4 - pX1 <= 3) { if (pX1 - 1 >= 0) { pXU = (pX1 - 1) * div; } else pXU = 0; if (pX4 + 2 >= (int)(nHeight / div)) { pXD = nHeight; } else pXD = (int)((pX4 + 1.5) * div + div); } else if (4 <= pX4 - pX1 && pX4 - pX1 <= 5) { pXU = pX1 * div - div / 2; pXD = (pX4 + 1) * div + div / 2; } else pXU = pX1 * div; pXD = (int)((pX4 + 1.5) * div - div / 2); pXD += div / 2; if (pXD > nHeight - 1) { pXD = nHeight - 1; } pYL -= lv / 2; if (pYL < 0) { pYL = 0; } //调整截取的边缘 LR(m, pX2, pX3, pYL, pYR, out pYL, out pYR); UD(m, pXU, pXD, pYL, pYR, out pXU, out pXD); /////////////////////////////////////////////////////////////////////////// //在图像上添加辅助线 ////////////////////////////////////////////////////////////////////////// //显示边框 p = (byte*)(void*)Scan0; pp = p; for (int i = 0; i < nHeight; i++) { if (i == pXU || i == pXD) { for (int j = pYL; j <= pYR; j++) { pp = p + i * stride + j * 3; pp[2] = 255; pp[0] = pp[1] = 0; } } else if (pXU < i && i < pXD) { pp = p + i * stride + pYL * 3; pp[2] = 255; pp[0] = pp[1] = 0; pp = p + i * stride + pYR * 3; pp[2] = 255; pp[0] = pp[1] = 0; } } //截取的行线显示在图上 /*p = (byte *)(void *)Scan0; pp = p; for (int i=0;i<(int)(nHeight/div)-1;i++) { //画垂线 for (int k=0;k<nWidth+1;k+=lv) { pp=p+(i*div+div/2)*stride+k*3; pp[2]=255; } //在车牌所在水平区域画出横线 if (lineLabel[i]) { for(int j=0; j < nWidth; ++j ) { pp=p+(i*div+div/2)*stride+j*3; pp[2]=255; } } } */ int ccount; ccount = ccm; ccount = maxAverage; m_Bitmap.UnlockBits(bmData); //maxX = maxX1 * div - pXU; // maxY = maxY1 * lv - pYL; //if (name != null) //{ // Bitmap other_c_Bitmap = (Bitmap)Bitmap.FromFile(name, false); //} //else { //} Rectangle sourceRectangle = new Rectangle(pYL, pXU, pYR - pYL, pXD - pXU); c_Bitmap = always_Bitmap.Clone(sourceRectangle, PixelFormat.DontCare); //extract_Bitmap_one = other_c_Bitmap.Clone(sourceRectangle, PixelFormat.DontCare); //在内存中处理c_Bitmap,提取数据之后,在原来的图片提取彩色图片。 } } if (c_Bitmap == null) Console.WriteLine("nulllllllllllllllllllllllllllllllllllllllll"); return c_Bitmap; } private static bool LR(float[,] m, int xu, int xd, int yl, int yr, out int pYL, out int pYR) { int[] projection = new int[yr - yl + 1]; foreach (int i in projection) { projection[i] = 0; } //垂直投影 for (int i = xu; i <= xd; i++) { for (int j = yl; j <= yr; j++) { if (m[i, j] > 0) { projection[j - yl]++; } } } bool l = true, r = true; int temp_yr = yr, temp_yl = yl; while (l || r) { if (temp_yr - temp_yl <= 60) { l = r = false; } if (l && projection[temp_yl - yl] < 5) { temp_yl++; } else { l = false; } if (r && projection[temp_yr - yl] < 5) { temp_yr--; } else { r = false; } } pYL = temp_yl; pYR = temp_yr; return true; } /* * 调整车牌上下位置 */ private static bool UD(float[,] m, int pXU, int pXD, int pYL, int pYR, out int xu, out int xd) { int[] projection = new int[pXD - pXU + 1]; foreach (int i in projection) { projection[i] = 0; } //水平投影 for (int i = pXU; i <= pXD; i++) { for (int j = pYL; j <= pYR; j++) { if (m[i, j] > 0) { projection[i - pXU]++; } } } bool u = true, d = true; int temp_xd = pXD - 1, temp_xu = pXU + 1; while (u || d) { if (temp_xd - temp_xu <= 60) { u = d = false; } if (u && projection[temp_xu - pXU] < 2) { temp_xu++; } else { u = false; } if (d && projection[temp_xd - pXU] < 2) { temp_xd--; } else { d = false; } } xu = temp_xu; xd = temp_xd; return true; } #endregion }
②、摄像头模块
摄像头模块具备拍摄图像自动保存和即时处理图像的功能。
public class showVideo { // showVideo calls [DllImport("avicap32.dll")] public static extern IntPtr capCreateCaptureWindowA(byte[] lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, int nID); [DllImport("avicap32.dll")] public static extern bool capGetDriverDescriptionA(short wDriver, byte[] lpszName, int cbName, byte[] lpszVer, int cbVer); [DllImport("User32.dll")] public static extern bool SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam); [DllImport("User32.dll")] public static extern bool SendMessage(IntPtr hWnd, int wMsg, short wParam, int lParam); [DllImport("User32.dll")] public static extern bool SendMessage(IntPtr hWnd, int wMsg, short wParam, FrameEventHandler lParam); [DllImport("User32.dll")] public static extern bool SendMessage(IntPtr hWnd, int wMsg, int wParam, ref BITMAPINFO lParam); [DllImport("User32.dll")] public static extern bool SendMessage(IntPtr hWnd, int wMsg, int wParam, ref CAPDRIVERCAPS lParam); [DllImport("User32.dll")] public static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int wFlags); [DllImport("avicap32.dll")] public static extern int capGetVideoFormat(IntPtr hWnd, IntPtr psVideoFormat, int wSize); [DllImport("User32.dll")] public static extern bool SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); // Constants public const int WM_USER = 0x400; public const int WS_CHILD = 0x40000000; public const int WS_VISIBLE = 0x10000000; public const int WM_CAP_START = WM_USER; public const int SWP_NOMOVE = 0x2; public const int SWP_NOZORDER = 0x4; public const int WM_CAP_STOP = WM_CAP_START + 68; public const int WM_CAP_DRIVER_CONNECT = WM_USER + 10; public const int WM_CAP_DRIVER_DISCONNECT = WM_USER + 11; public const int WM_CAP_SET_CALLBACK_FRAME = WM_USER + 5; public const int WM_CAP_SET_PREVIEW = WM_USER + 50; public const int WM_CAP_SET_PREVIEWRATE = WM_USER + 52; public const int WM_CAP_SET_VIDEOFORMAT = WM_USER + 45; public const int WM_CAP_SAVEDIB = WM_USER + 25; public const int WM_CAP_SET_OVERLAY = WM_USER + 51; public const int WM_CAP_GET_CAPS = WM_USER + 14; public const int WM_CAP_DLG_VIDEOFORMAT = WM_USER + 41; public const int WM_CAP_DLG_VIDEOSOURCE = WM_USER + 42; public const int WM_CAP_DLG_VIDEODISPLAY = WM_USER + 43; public const int WM_CAP_EDIT_COPY = WM_USER + 30; public const int WM_CAP_SET_SEQUENCE_SETUP = WM_USER + 64; public const int WM_CAP_GET_SEQUENCE_SETUP = WM_USER + 65; public const int WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20; public const int WM_CAP_SEQUENCE = WM_CAP_START + 62; // Structures [StructLayout(LayoutKind.Sequential)] public struct VIDEOHDR { [MarshalAs(UnmanagedType.I4)] public int lpData; [MarshalAs(UnmanagedType.I4)] public int dwBufferLength; [MarshalAs(UnmanagedType.I4)] public int dwBytesUsed; [MarshalAs(UnmanagedType.I4)] public int dwTimeCaptured; [MarshalAs(UnmanagedType.I4)] public int dwUser; [MarshalAs(UnmanagedType.I4)] public int dwFlags; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public int[] dwReserved; } [StructLayout(LayoutKind.Sequential)] public struct CAPDRIVERCAPS { [MarshalAs(UnmanagedType.U2)] public UInt16 wDeviceIndex; [MarshalAs(UnmanagedType.Bool)] public bool fHasOverlay; [MarshalAs(UnmanagedType.Bool)] public bool fHasDlgVideoSource; [MarshalAs(UnmanagedType.Bool)] public bool fHasDlgVideoFormat; [MarshalAs(UnmanagedType.Bool)] public bool fHasDlgVideoDisplay; [MarshalAs(UnmanagedType.Bool)] public bool fCaptureInitialized; [MarshalAs(UnmanagedType.Bool)] public bool fDriverSuppliesPalettes; [MarshalAs(UnmanagedType.I4)] public int hVideoIn; [MarshalAs(UnmanagedType.I4)] public int hVideoOut; [MarshalAs(UnmanagedType.I4)] public int hVideoExtIn; [MarshalAs(UnmanagedType.I4)] public int hVideoExtOut; } [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFOHEADER { [MarshalAs(UnmanagedType.I4)] public Int32 biSize; [MarshalAs(UnmanagedType.I4)] public Int32 biWidth; [MarshalAs(UnmanagedType.I4)] public Int32 biHeight; [MarshalAs(UnmanagedType.I2)] public short biPlanes; [MarshalAs(UnmanagedType.I2)] public short biBitCount; [MarshalAs(UnmanagedType.I4)] public Int32 biCompression; [MarshalAs(UnmanagedType.I4)] public Int32 biSizeImage; [MarshalAs(UnmanagedType.I4)] public Int32 biXPelsPerMeter; [MarshalAs(UnmanagedType.I4)] public Int32 biYPelsPerMeter; [MarshalAs(UnmanagedType.I4)] public Int32 biClrUsed; [MarshalAs(UnmanagedType.I4)] public Int32 biClrImportant; } [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFO { [MarshalAs(UnmanagedType.Struct, SizeConst = 40)] public BITMAPINFOHEADER bmiHeader; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public Int32[] bmiColors; } public delegate void FrameEventHandler(IntPtr lwnd, IntPtr lpVHdr); // Public methods public static object GetStructure(IntPtr ptr, ValueType structure) { return Marshal.PtrToStructure(ptr, structure.GetType()); } public static object GetStructure(int ptr, ValueType structure) { return GetStructure(new IntPtr(ptr), structure); } public static void Copy(IntPtr ptr, byte[] data) { Marshal.Copy(ptr, data, 0, data.Length); } public static void Copy(int ptr, byte[] data) { Copy(new IntPtr(ptr), data); } public static int SizeOf(object structure) { return Marshal.SizeOf(structure); } } //Web Camera Class public class WebCamera { // Constructur public WebCamera(IntPtr handle, int width, int height) { mControlPtr = handle; mWidth = width; mHeight = height; } // delegate for frame callback public delegate void RecievedFrameEventHandler(byte[] data); public event RecievedFrameEventHandler RecievedFrame; private IntPtr lwndC; // Holds the unmanaged handle of the control private IntPtr mControlPtr; // Holds the managed pointer of the control private int mWidth; private int mHeight; private showVideo.FrameEventHandler mFrameEventHandler; // Delegate instance for the frame callback - must keep alive! gc should NOT collect it // Close the web camera public void CloseWebcam() { this.capDriverDisconnect(this.lwndC); } // start the web camera public void StartWebCam() { byte[] lpszName = new byte[100]; byte[] lpszVer = new byte[100]; showVideo.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100); this.lwndC = showVideo.capCreateCaptureWindowA(lpszName, showVideo.WS_VISIBLE + showVideo.WS_CHILD, 0, 0, mWidth, mHeight, mControlPtr, 0); if (this.capDriverConnect(this.lwndC, 0)) { this.capPreviewRate(this.lwndC, 66); this.capPreview(this.lwndC, true); showVideo.BITMAPINFO bitmapinfo = new showVideo.BITMAPINFO(); bitmapinfo.bmiHeader.biSize = showVideo.SizeOf(bitmapinfo.bmiHeader); bitmapinfo.bmiHeader.biWidth = 352; bitmapinfo.bmiHeader.biHeight = 288; bitmapinfo.bmiHeader.biPlanes = 1; bitmapinfo.bmiHeader.biBitCount = 24; this.capSetVideoFormat(this.lwndC, ref bitmapinfo, showVideo.SizeOf(bitmapinfo)); this.mFrameEventHandler = new showVideo.FrameEventHandler(FrameCallBack); this.capSetCallbackOnFrame(this.lwndC, this.mFrameEventHandler); showVideo.SetWindowPos(this.lwndC, 0, 0, 0, mWidth, mHeight, 6); } } ///<summary> ///录像 ///</summary> ///<param name="path">要保存avi文件的路径</param> public void Kinescope(string path) { IntPtr hBmp = Marshal.StringToHGlobalAnsi(path); showVideo.SendMessage(lwndC, showVideo.WM_CAP_FILE_SET_CAPTURE_FILEA, IntPtr.Zero, hBmp); showVideo.SendMessage(lwndC, showVideo.WM_CAP_SEQUENCE, IntPtr.Zero, IntPtr.Zero); } ///<summary> ///停止录像 ///</summary> public void StopKinescope() { showVideo.SendMessage(lwndC, showVideo.WM_CAP_STOP, IntPtr.Zero, IntPtr.Zero); } // private functions public bool copyToClipBoard() //抓图到剪切板 { return showVideo.SendMessage(lwndC, showVideo.WM_CAP_EDIT_COPY, 0, 0); //Clipboard.GetDataObject(); } public void grabImage(string path) //抓图到文件 { IntPtr hBmp = Marshal.StringToHGlobalAnsi(path); showVideo.SendMessage(lwndC, showVideo.WM_CAP_SAVEDIB, 0, hBmp.ToInt32()); } public void setCaptureSource() //弹出色彩设置对话框 { showVideo.CAPDRIVERCAPS caps = new showVideo.CAPDRIVERCAPS(); showVideo.SendMessage(lwndC, showVideo.WM_CAP_GET_CAPS, showVideo.SizeOf(caps), ref caps); if (caps.fHasDlgVideoSource) { showVideo.SendMessage(lwndC, showVideo.WM_CAP_DLG_VIDEOSOURCE, 0, 0); } } public void setCaptureFormat() //弹出视频格式设置对话框 { showVideo.CAPDRIVERCAPS caps = new showVideo.CAPDRIVERCAPS(); showVideo.SendMessage(lwndC, showVideo.WM_CAP_GET_CAPS, showVideo.SizeOf(caps), ref caps); if (caps.fHasDlgVideoSource) { showVideo.SendMessage(lwndC, showVideo.WM_CAP_DLG_VIDEOFORMAT, 0, 0); } } private bool capDriverConnect(IntPtr lwnd, short i) { return showVideo.SendMessage(lwnd, showVideo.WM_CAP_DRIVER_CONNECT, i, 0); } private bool capDriverDisconnect(IntPtr lwnd) { return showVideo.SendMessage(lwnd, showVideo.WM_CAP_DRIVER_DISCONNECT, 0, 0); } private bool capPreview(IntPtr lwnd, bool f) { return showVideo.SendMessage(lwnd, showVideo.WM_CAP_SET_PREVIEW, f, 0); } private bool capPreviewRate(IntPtr lwnd, short wMS) { return showVideo.SendMessage(lwnd, showVideo.WM_CAP_SET_PREVIEWRATE, wMS, 0); } private bool capSetCallbackOnFrame(IntPtr lwnd, showVideo.FrameEventHandler lpProc) { return showVideo.SendMessage(lwnd, showVideo.WM_CAP_SET_CALLBACK_FRAME, 0, lpProc); } private bool capSetVideoFormat(IntPtr hCapWnd, ref showVideo.BITMAPINFO BmpFormat, int CapFormatSize) { return showVideo.SendMessage(hCapWnd, showVideo.WM_CAP_SET_VIDEOFORMAT, CapFormatSize, ref BmpFormat); } private void FrameCallBack(IntPtr lwnd, IntPtr lpVHdr) { showVideo.VIDEOHDR videoHeader = new showVideo.VIDEOHDR(); byte[] VideoData; videoHeader = (showVideo.VIDEOHDR)showVideo.GetStructure(lpVHdr, videoHeader); VideoData = new byte[videoHeader.dwBytesUsed]; showVideo.Copy(videoHeader.lpData, VideoData); if (this.RecievedFrame != null) this.RecievedFrame(VideoData); } }
③、特征训练模块
特征训练模块用于处理车牌图像信息和精确提取其中的字符图像用于特征匹配。
public partial class PictureTakeForm : Form { public Bitmap thisBitmap; public Bitmap bmp = new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height); public Bitmap changeSizePic; public frmScreen f; private String name; int[] gray = new int[256]; int[] rr = new int[256]; int[] gg = new int[256]; int[] bb = new int[256]; // public static string licensePlateBath = "G:\\licensePlate\\"; public static string charSourceBath = Environment.CurrentDirectory + @"\char\"; public static string provinceSourceBath = Environment.CurrentDirectory + @"\font\"; public PictureTakeForm() { InitializeComponent(); } private void button6_Click(object sender, EventArgs e) { Graphics g = Graphics.FromImage(bmp); g.CopyFromScreen(0, 0, 0, 0, new Size(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height)); f = new frmScreen(bmp,this); f.Show(); } private void panel1_Paint(object sender, PaintEventArgs e) { if (thisBitmap != null) { this.panel1.AutoScroll = true; this.panel1.AutoScrollMinSize = new Size((int)(thisBitmap.Width), (int)thisBitmap.Height); Console.WriteLine(" aaaaaaaaaaaaaaaa)"); Console.WriteLine(thisBitmap.Width+" "+this.thisBitmap.Height); Graphics g = e.Graphics; if (thisBitmap.Height < this.panel1.Height && thisBitmap.Width < this.panel1.Width) { g.DrawImage(thisBitmap, new Rectangle((this.panel1.Width/2)-thisBitmap.Width/2, this.panel1.Height/2-thisBitmap.Height/2, (int)(thisBitmap.Width), (int)(thisBitmap.Height))); } else { g.DrawImage(thisBitmap, new Rectangle(this.panel1.AutoScrollPosition.X, this.panel1.AutoScrollPosition.Y, (int)(thisBitmap.Width), (int)(thisBitmap.Height))); } } } private void button1_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "Jpeg文件|*.jpg|Bitmap文件|*.bmp| 所有合适文件|*.bmp/*.jpg"; openFileDialog.FilterIndex = 2; openFileDialog.RestoreDirectory = true;//该值指示对话框在关闭前是否还原当前目录 if (DialogResult.OK == openFileDialog.ShowDialog()) { name = openFileDialog.FileName; thisBitmap = (Bitmap)Bitmap.FromFile(name, false);//使用该文件中的嵌入颜色管理信息,从指定的文件创建thisBitmap this.panel1.AutoScroll = true; this.panel1.AutoScrollMinSize = new Size((int)(thisBitmap.Width), (int)thisBitmap.Height); this.label1.Text = name; panel1.Invalidate();//使panel1内的图像重新绘制 } } private void button2_Click(object sender, EventArgs e) { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "Bitmap文件|*.bmp| Jpeg文件|*.jpg| 所有合适文件|*.bmp/*.jpg"; saveFileDialog.FilterIndex = 1; saveFileDialog.RestoreDirectory = true; if (DialogResult.OK == saveFileDialog.ShowDialog()) { thisBitmap.Save(saveFileDialog.FileName); } } //灰度化 private void button4_Click(object sender, EventArgs e) { if (thisBitmap != null) { thisBitmap= GeneralTools.grayfy(thisBitmap); panel1.Invalidate(); } } //灰度均衡化 private void button8_Click(object sender, EventArgs e) { if (thisBitmap != null) { thisBitmap = GeneralTools.grayBalance(thisBitmap); panel1.Invalidate(); } } //二值化 private void button5_Click(object sender, EventArgs e) { if (thisBitmap != null) { thisBitmap = GeneralTools.colorDevided(thisBitmap); panel1.Invalidate(); } } //精确提取 private void button9_Click(object sender, EventArgs e) { if (thisBitmap != null) { BitmapData bmData = thisBitmap.LockBits(new Rectangle(0, 0, thisBitmap.Width, thisBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0;//封锁到内存中的数据的首地址 byte* p = (byte*)(void*)Scan0; //把首地址赋值给指针p byte* p1 = (byte*)(void*)Scan0; //把首地址赋值给指针p1 byte* p2 = (byte*)(void*)Scan0; //把首地址赋值给指针p2 int nOffset = stride - thisBitmap.Width * 3;//每行图像的实际数据 int[] Yedge = new int[2]; int[] Xedge = new int[2]; int nWidth = thisBitmap.Width; int nHeight = thisBitmap.Height; int[] countHeight = new int[nHeight]; int[] countWidth = new int[nWidth]; int Yheight = nHeight, YBottom = 0; int XLeft = nWidth, XRight = 0; for (int i = 0; i < nHeight; i++) { countHeight[i] = 0; } for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { if ((p[0] == 0 && p[3] == 255) || (p[0] == 255 && p[3] == 0)) { countHeight[y]++; } p += 3; } p += nOffset; } //竖直方向 //上边沿 for (int y = 0; y < nHeight; ++y) { if (countHeight[y] >= 2 )//12,6,11 { if (Yheight > y) { Yheight = y; YBottom = y; }//此if语句只执行了一次 if ((y - YBottom) == 1) YBottom = y + 1; } } //下边沿 for (int y = nHeight; y >0; --y) { if (countHeight[y-1] >= 2)//12,6,11 { if (YBottom< y) { YBottom = y; }//此if语句只执行了一次 } } for (int y = Yheight; y < YBottom; ++y) { for (int x = 0; x < nWidth-1; ++x) { if ((p1[0] == 0 && p1[3] == 255) || (p1[0] == 255 && p1[3] == 0)) { if (XLeft > x) { XLeft = x+1;//加一的目的是从数字边沿开始截取,二不是边沿左侧一个像素 Console.WriteLine(XLeft + " XLEFT"); } if (XRight < x) { XRight = x; Console.WriteLine(XRight + " XRIGHT"); } } p1 += 3; } Console.WriteLine(y + " 循环变量y"); p1 += nOffset+3; } //for (int y = Yheight; y < YBottom; y++) //{ // for (int x = XLeft; x < XRight; x++) // { // p2[0] = 255; // p2 += 3; // } // p2 += nOffset; //} thisBitmap.UnlockBits(bmData); if (XRight > XLeft && YBottom > Yheight) { Console.WriteLine(XLeft + " " + XRight + " " + Yheight + "[oooooooooooooo]" + YBottom); Rectangle sourcePic = new Rectangle(XLeft, Yheight, XRight - XLeft, YBottom - Yheight); thisBitmap = thisBitmap.Clone(sourcePic, PixelFormat.Format24bppRgb); Console.WriteLine(XLeft + " " + XRight + " " + Yheight + "ttttttttttttttttt" + YBottom); panel1.Invalidate(); } else { } } } } //调整大小 private void button3_Click(object sender, EventArgs e) { if (thisBitmap != null) { changeSizePic= new System.Drawing.Bitmap(thisBitmap, 9, 16); thisBitmap=changeSizePic; panel1.Invalidate(); } } //旋转图片 private void button7_Click(object sender, EventArgs e) { } //二值图片反色 private void button10_Click(object sender, EventArgs e) { if (thisBitmap != null) { BitmapData bmData = thisBitmap.LockBits(new Rectangle(0, 0, thisBitmap.Width, thisBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = bmData.Stride;//获取或设置 Bitmap 对象的跨距宽度(也称为扫描宽度)。 System.IntPtr Scan0 = bmData.Scan0;//获取或设置位图中第一个像素数据的地址。 它也可以看成是位图中的第一个扫描行 unsafe { byte* p = (byte*)(void*)Scan0; int nOffset = stride - thisBitmap.Width * 3; int nWidth = thisBitmap.Width; int nHeight = thisBitmap.Height; for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { if (p[0] == 255) p[0] = p[1] = p[2] = 0; else p[0] = p[1] = p[2] = 255; p += 3; } p += nOffset; } } thisBitmap.UnlockBits(bmData); panel1.Invalidate(); } } //private void 直接保存(object sender, EventArgs e) //{ // if (this.textBox1 != null) // { // String formatName = null; // formatName = GeneralTools.getFormatName(bathFlag, this.textBox1.Text); // Console.WriteLine(formatName + " " + bathFlag); // this.thisBitmap.Save(licensePlateBath+this.textBox1.Text+".bmp"); // } //} private void 汉字保存(object sender, EventArgs e) { if (this.textBox1 != null) { String formatName = null; formatName = GeneralTools.getFormatName(provinceSourceBath, this.textBox1.Text); Console.WriteLine(formatName + " " + provinceSourceBath); this.thisBitmap.Save(provinceSourceBath + formatName + ".bmp"); } } private void button12_Click(object sender, EventArgs e) { String formatName = null; formatName = GeneralTools.getFormatName(charSourceBath, this.textBox1.Text); Console.WriteLine(formatName + " " + charSourceBath); this.thisBitmap.Save(charSourceBath + formatName + ".bmp"); } }
三、项目结构图
四、补充
可以使用压缩包里的车牌图片来测试功能。
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
手机上随时阅读、收藏该文章 ?请扫下方二维码