C# winfrom 写了一个增值税票识别助手,可识别照片、扫描件、电子票、形成电子台帐。
首先本小工具使用C# winfrom 实现,其中主要是使用了百度智能云OCR文字识别技术,调用期官网接口,很简单,搭配NPOI Execl操作类库,
利用Spire.pdf类库,把pdf格式发票,转换为png图片格式。自动识别图片、pdf格式发票,发票可以用高拍仪、手机拍照、扫面件等都可以识别。
其他说明:本程序借助百度智能云API作为基础的发票识别技术。
发票识别助手共分5个功能模块,操作相对很简单,第一步点击添加发票按钮,选择要识别的发票信息。注意说明:目前图片格式支持jpg、png、bmp,图片的长和宽要求最短边大于10px,
最长边小于2048px;图像编码后大小必须小于4M,建议不要超过1M;第二步点击识别发票按钮,系统开始识别发票信息,识别完成后,发票信息会自动生成;
介绍一下关键的代码:
一、获取百度云API token,这个是官方给的,直接拿过来用就可以了。
1 public static class AccessToken 2 3 { 4 // 百度云中开通对应服务应用的 API Key 建议开通应用的时候多选服务 5 private static String clientId = ConfigurationManager.AppSettings.Get("APIKey"); 6 // 百度云中开通对应服务应用的 Secret Key 7 private static String clientSecret = ConfigurationManager.AppSettings.Get("SecretKey"); 8 9 public static String getAccessToken() 10 { 11 String authHost = "https://aip.baidubce.com/oauth/2.0/token"; 12 HttpClient client = new HttpClient(); 13 List<KeyValuePair<String, String>> paraList = new List<KeyValuePair<string, string>>(); 14 paraList.Add(new KeyValuePair<string, string>("grant_type", "client_credentials")); 15 paraList.Add(new KeyValuePair<string, string>("client_id", clientId)); 16 paraList.Add(new KeyValuePair<string, string>("client_secret", clientSecret)); 17 18 HttpResponseMessage response = client.PostAsync(authHost, new FormUrlEncodedContent(paraList)).Result; 19 String result = response.Content.ReadAsStringAsync().Result; 20 // Console.WriteLine(result); 21 22 AccessTokenInfo tokenInfo = JsonConvert.DeserializeObject<AccessTokenInfo>(result); 23 24 return tokenInfo.access_token; 25 } 26 } 27 28 public class AccessTokenInfo 29 { 30 public string refresh_token { get; set; } 31 public string expires_in { get; set; } 32 public string session_key { get; set; } 33 public string access_token { get; set; } 34 public string scope { get; set; } 35 public string session_secret { get; set; } 36 }
二、增值税票识别请求过程和参数传递,也是官方给的例子,自己按照需求修改一下就可以了。
1 // 增值税发票识别 2 public static string vatInvoice(string fileName) 3 { 4 string host = "https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice?access_token=" + token; 5 Encoding encoding = Encoding.Default; 6 System.Net.HttpWebRequest request = (HttpWebRequest)WebRequest.Create(host); 7 request.Method = "post"; 8 request.KeepAlive = true; 9 // 图片的base64编码 10 string base64 = getFileBase64(fileName); 11 String str = "image=" + UrlEncode(base64); 12 byte[] buffer = encoding.GetBytes(str); 13 request.ContentLength = buffer.Length; 14 request.GetRequestStream().Write(buffer, 0, buffer.Length); 15 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 16 StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); 17 string result = reader.ReadToEnd(); 18 return result; 19 } 20 21 public static String getFileBase64(String fileName) 22 { 23 FileStream filestream = new FileStream(fileName, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite); 24 byte[] arr = new byte[filestream.Length]; 25 filestream.Read(arr, 0, (int)filestream.Length); 26 string baser64 = Convert.ToBase64String(arr); 27 filestream.Close(); 28 return baser64; 29 } 30 31 public static string UrlEncode(string str) 32 { 33 StringBuilder sb = new StringBuilder(); 34 byte[] byStr = System.Text.Encoding.UTF8.GetBytes(str); //默认System.Text.Encoding.Default.GetBytes(str) 35 for (int i = 0; i < byStr.Length; i++) 36 { 37 sb.Append(@"%" + Convert.ToString(byStr[i], 16)); 38 } 39 return (sb.ToString()); 40 }
三、这里的部分是把pdf格式的发票,自动转换为png格式,提供出百度云api需要的文件格式。
1 private ImageList GetImage(string[] files) 2 { 3 ImageList list = new ImageList(); 4 for (int i = 0; i < files.Length; i++) 5 { 6 list.Images.Add(files[i], Image.FromFile(files[i])); 7 list.ImageSize = new Size(80, 60); 8 } 9 return list; 10 } 11 12 private string[] GetImages() 13 { 14 OpenFileDialog ofd = new OpenFileDialog(); 15 ofd.Multiselect = true;//设置 选择多个文件 16 ofd.InitialDirectory = @"C:\images\";//设置初始目录 TODO:改为系统默认我的文档中的图片文件夹 17 ofd.Multiselect = true; 18 //ofd.Filter = "JPG(*.jpg)|*.jpg|JPEG(*.jpeg)|*.jpeg|PNG(*.png)|*.png|GIF(*.gif)|*.gif|所有文件(*.*)|*.*"; 19 ofd.Title = "请选择要识别的发票的图片"; 20 ofd.Filter = "图片文件(*.jpg *.jpeg *.bmp *.png)|*.jpg;*.jpeg;*.bmp;*.png;*.pdf"; 21 if (ofd.ShowDialog() == DialogResult.OK && ofd.FileNames != null) 22 { 23 string[] files = ofd.FileNames; 24 //pdf文件转换为png图片文件 25 string imageName = ""; 26 for (int i = 0; i < files.Length; i++) 27 { 28 if (Path.GetExtension(files[i]).ToUpper().Contains(".PDF")) 29 { 30 imageName = Path.GetFileNameWithoutExtension(files[i]); 31 files[i] = Common.ConvertPDF2Image(files[i], imageName, 0, 1, ImageFormat.Png); 32 errMsg.AppendText(DateTime.Now.ToLongTimeString().ToString() + " 已将" + imageName + ".pdf自动转换为png图片格式\r\n"); 33 } 34 } 35 return files; 36 } 37 else 38 { 39 return null; 40 } 41 } 42 43 //格式化日期格式 44 public string fmartDate(string date) 45 { 46 date = date.Replace("年", "-"); 47 date = date.Replace("月", "-"); 48 date = date.Replace("日", ""); 49 return date; 50 }
四、获取api返回的数据,输出到dataGridView中。
1 private void 识别发票ToolStripMenuItem_Click(object sender, EventArgs e) 2 { 3 if (this.listView1.Items.Count == 0) 4 { 5 MessageBox.Show("请先选择要识别的发票!", "消息提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); 6 return; 7 } 8 9 Common.ShowProcessing("", this, (obj) => 10 { 11 //这里采用委托的方式解决线程卡死问题 12 this.Invoke(new Action(delegate 13 { 14 foreach (ListViewItem item in this.listView1.Items) 15 { 16 try 17 { 18 var invoiceInfo = JsonConvert.DeserializeObject<dynamic>(vatInvoice(item.SubItems[0].Name)); 19 var items = invoiceInfo.words_result; 20 if (items != null) 21 { 22 //写入数据表格 23 int index = this.dataGridView1.Rows.Add(); 24 this.dataGridView1.Rows[index].Cells[0].Value = items.InvoiceType; 25 this.dataGridView1.Rows[index].Cells[1].Value = items.InvoiceCode; 26 this.dataGridView1.Rows[index].Cells[2].Value = items.InvoiceNum; 27 this.dataGridView1.Rows[index].Cells[3].Value = fmartDate((string)items.InvoiceDate); 28 this.dataGridView1.Rows[index].Cells[4].Value = items.SellerName; 29 this.dataGridView1.Rows[index].Cells[5].Value = items.SellerRegisterNum; 30 this.dataGridView1.Rows[index].Cells[6].Value = items.SellerAddress; 31 this.dataGridView1.Rows[index].Cells[7].Value = items.SellerBank; 32 this.dataGridView1.Rows[index].Cells[8].Value = Common.NumberToZero((string)items.TotalAmount); 33 this.dataGridView1.Rows[index].Cells[9].Value = "0"; 34 if (Common.IsPropertyExist(items, "CommodityTaxRate")) 35 { 36 if (!Common.IsNullOrEmpty(items.CommodityTaxRate[0].word)) 37 { 38 this.dataGridView1.Rows[index].Cells[9].Value = Common.NumberToZero((string)items.CommodityTaxRate[0].word.ToString().Replace("%", "")); 39 } 40 } 41 this.dataGridView1.Rows[index].Cells[10].Value = Common.NumberToZero((string)items.TotalTax); 42 this.dataGridView1.Rows[index].Cells[11].Value = items.AmountInFiguers; 43 this.dataGridView1.Rows[index].Cells[12].Value = items.InvoiceType.ToString().Contains("电子") ? "是" : "否"; 44 this.dataGridView1.Rows[index].Cells[13].Value = items.PurchaserName; 45 this.dataGridView1.Rows[index].Cells[14].Value = "一般计税"; 46 Application.DoEvents(); 47 addMessage(item.SubItems[0].Text + " 识别完成!"); 48 } 49 else 50 { 51 if (invoiceInfo.error_code != null) 52 { 53 addMessage(item.SubItems[0].Text + " -->" + apiErrorMessage((string)invoiceInfo.error_code)); 54 } 55 } 56 } 57 catch (Exception err) 58 { 59 addMessage(item.SubItems[0].Text + err.Message + " 识别出错,已跳过!"); 60 } 61 } 62 })); 63 //这里写处理耗时的代码,代码处理完成则自动关闭该窗口 64 }, null); 65 66 }
五、导出发票明细到EXECL表格中。
1 private void 导出发票信息ToolStripMenuItem_Click(object sender, EventArgs e) 2 { 3 if (this.dataGridView1.Rows.Count == 0) 4 { 5 MessageBox.Show("发票列表信息为空,不能执行导出!", "消息提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); 6 return; 7 } 8 9 string fileName = ""; 10 SaveFileDialog sfd = new SaveFileDialog(); 11 sfd.Filter = "导出发票Excel(*.xls)|*.xls"; 12 sfd.FileName = "发票明细 - " + DateTime.Now.ToString("yyyyMMddHHmmss"); 13 if (sfd.ShowDialog() == DialogResult.OK) 14 { 15 Common.ShowProcessing("正在导出,请稍候...", this, (obj) => 16 { 17 fileName = sfd.FileName; 18 HSSFWorkbook wb = new HSSFWorkbook(); 19 ISheet sheet = wb.CreateSheet("sheet1"); 20 int columnCount = dataGridView1.ColumnCount; //列数 21 int rowCount = dataGridView1.Rows.Count; //行数 22 for (int i = 0; i < columnCount; i++) 23 { 24 sheet.SetColumnWidth(i, 15 * 256); 25 } 26 //报表标题 27 IRow row = sheet.CreateRow(0); 28 row.HeightInPoints = 25; 29 ICell cell = row.CreateCell(0); 30 cell.SetCellValue("发票信息台账"); 31 32 ICellStyle style = wb.CreateCellStyle(); 33 34 style.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Center; 35 style.VerticalAlignment = NPOI.SS.UserModel.VerticalAlignment.Center; 36 37 style.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin; 38 style.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin; 39 style.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin; 40 style.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin; 41 style.FillBackgroundColor = HSSFColor.Black.Index; 42 style.FillForegroundColor = HSSFColor.White.Index; 43 44 IFont font = wb.CreateFont(); 45 font.FontName = "微软雅黑"; 46 font.FontHeightInPoints = 12; 47 font.Boldweight = 700; 48 style.SetFont(font);//将新的样式赋给单元格 49 cell.CellStyle = style; 50 sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, columnCount - 1)); 51 52 //表头 53 IRow row1 = sheet.CreateRow(1); 54 row1.HeightInPoints = 20; 55 56 ICellStyle styleHead = wb.CreateCellStyle(); 57 styleHead.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Center; 58 styleHead.VerticalAlignment = NPOI.SS.UserModel.VerticalAlignment.Center; 59 styleHead.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin; 60 styleHead.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin; 61 styleHead.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin; 62 styleHead.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin; 63 styleHead.FillBackgroundColor = HSSFColor.Black.Index; 64 styleHead.FillForegroundColor = HSSFColor.White.Index; 65 66 IFont font2 = wb.CreateFont(); 67 font2.FontName = "微软雅黑"; 68 font2.FontHeightInPoints = 10; 69 font2.Boldweight = 500; 70 styleHead.SetFont(font2);//将新的样式赋给单元格 71 72 for (int i = 0; i < columnCount; i++) 73 { 74 ICell row1cell = row1.CreateCell(i); 75 row1cell.SetCellValue(dataGridView1.Columns[i].HeaderText.ToString()); 76 row1cell.CellStyle = styleHead; 77 } 78 79 //明细行,从第三列开始 80 int rowindex = 2; 81 ICellStyle styleBody = wb.CreateCellStyle(); 82 styleBody.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Left; 83 styleBody.VerticalAlignment = NPOI.SS.UserModel.VerticalAlignment.Center; 84 styleBody.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin; 85 styleBody.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin; 86 styleBody.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin; 87 styleBody.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin; 88 styleBody.FillBackgroundColor = HSSFColor.Black.Index; 89 styleBody.FillForegroundColor = HSSFColor.White.Index; 90 91 IFont font3 = wb.CreateFont(); 92 font3.FontName = "微软雅黑"; 93 font3.FontHeightInPoints = 9; 94 font3.Boldweight = 500; 95 styleBody.SetFont(font3);//将新的样式赋给单元格 96 for (int i = 0; i < rowCount; i++) 97 { 98 IRow datarow = sheet.CreateRow(rowindex); 99 datarow.Height = 300; 100 for (int j = 0; j < columnCount; j++) 101 { 102 ICell datacell_0 = datarow.CreateCell(j); 103 datacell_0.SetCellValue(this.dataGridView1.Rows[i].Cells[j].Value.ToString()); 104 datacell_0.CellStyle = styleBody; 105 } 106 rowindex += 1; 107 } 108 // 转为字节数组 109 MemoryStream stream = new MemoryStream(); 110 wb.Write(stream); 111 var buf = stream.ToArray(); 112 113 //保存为Excel文件 114 using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) 115 { 116 fs.Write(buf, 0, buf.Length); 117 fs.Flush(); 118 MessageBox.Show("导出 EXECL 成功!", "消息提示"); 119 addMessage("导出发票信息到Execl表完成!"); 120 } 121 }, null); 122 } 123 }
操作说明如下: