图像简单处理
需求:对上面多张这样的图像,将成员识别出来,读取本周活跃值。生成一张全部由成员组成的大图
思路:使用OpenCVSharp进行目标区域边框检测,根据大小过滤目标区域。使用Tesseract对指定位置进行文字识别(貌似识别精度不高,有乱码。)
主要代码
using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Security.Cryptography; using System.Web; using System.Windows; using System.Windows.Documents; using OpenCvSharp; using OpenCvSharp.Dnn; using OpenCvSharp.Internal.Vectors; using Tesseract; namespace ShanHaiJingToolkit.Common { public class CvHelper { public void ReadPictureActivityValue(string imagePath, out int itemWidth, out int itemHeight) { System.IO.DirectoryInfo directoryInfo = new System.IO.DirectoryInfo(imagePath); List<Mat> items = new List<Mat>(); itemWidth = 0; itemHeight = 0; foreach (var file in directoryInfo.GetFiles()) { // 读取图像 using (var src = new Mat(file.FullName, ImreadModes.Color)) { // 转换为灰度图像,因为边缘检测通常在灰度图像上进行 using (var gray = new Mat()) { Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); // 应用Canny边缘检测 using (var edges = new Mat()) { double lowThreshold = 50.0; double highThreshold = 150.0; Cv2.Canny(gray, edges, lowThreshold, highThreshold); Cv2.FindContours(edges, out var contours1, out var hierarchy1, RetrievalModes.List, ContourApproximationModes.ApproxSimple); // 定义矩形颜色(BGR格式)和线条厚度 Scalar color = new Scalar(0, 255, 0); // 绿色 List<OpenCvSharp.Rect> listRects = new List<OpenCvSharp.Rect>(); for (int i = 0; i < contours1.Length; i++) { var points = contours1[i]; var rect = Cv2.BoundingRect(points); var widthScale = (float)rect.Width / src.Width; var heightScale = (float)rect.Height / src.Height; //if ((widthScale > 0.827 && widthScale < 0.871) && (rect.Height > 0.060 && rect.Height < 0.073)) if ((widthScale > 0.827 && widthScale < 0.871) && (heightScale > 0.060 && heightScale < 0.073)) { bool isAdd = false; foreach (var item in listRects) { if (item.TopLeft.DistanceTo(rect.TopLeft) < 10) { isAdd = true; break; } } if (isAdd) continue; listRects.Add(rect); if (itemWidth == 0) { itemWidth = rect.Width; itemHeight = rect.Height; } var newRect = new OpenCvSharp.Rect(rect.Left, rect.Top, itemWidth, itemHeight); // 在图像上绘制矩形 //Cv2.Rectangle(src, newRect.TopLeft, newRect.BottomRight, color, 4); var tempMat = src[newRect]; items.Add(tempMat); } } } } } } var tempImagesDirectory = imagePath + "\\temp\\"; if (!System.IO.Directory.Exists(tempImagesDirectory)) { System.IO.Directory.CreateDirectory(tempImagesDirectory); } //保存到本地 for (int i = 0; i < items.Count; i++) { //using (Mat gray = new Mat()) //{ // Cv2.CvtColor(items[i], gray, ColorConversionCodes.BGR2GRAY); // //二值化 // using (Mat target = new Mat()) // { // Cv2.Threshold(items[i], target, 150, 200, ThresholdTypes.Binary); // Cv2.ImWrite(tempImagesDirectory + $"{i}.png", target); // } //} //降噪高斯模糊,边缘检测 // 定义高斯模糊的核大小和标准差 //OpenCvSharp.Size ksize = new OpenCvSharp.Size(5, 5); // 核大小,必须是奇数 //double sigmaX = 0; // X方向的标准差,如果为0,则由核函数宽度计算得出 //double sigmaY = 0; // Y方向的标准差,如果为0,则由核函数高度计算得出 //// 应用高斯模糊 //using (Mat blurredImage = new Mat()) //{ // Cv2.GaussianBlur(items[i], blurredImage, ksize, sigmaX, sigmaY); // // 定义Canny边缘检测的双阈值 // double threshold1 = 50.0; // 低阈值 // double threshold2 = 150.0; // 高阈值,通常是高于低阈值的2到3倍 // // 边缘检测 // using (Mat edges = new Mat()) // { // Cv2.Canny(blurredImage, edges, threshold1, threshold2); // Cv2.ImWrite(tempImagesDirectory + $"{i}.png", edges); // } //}; using (Mat txtMat = items[i][new OpenCvSharp.Rect((int)(0.181 * itemWidth), itemHeight / 2, (int)(0.353 * itemWidth), itemHeight / 2)]) { Cv2.ImWrite(tempImagesDirectory + $"{i}.png", txtMat); } //Cv2.ImWrite(tempImagesDirectory + $"{i}.png", items[i]); } //读取活跃值 List<Tuple<int, Mat>> targetVals = new List<Tuple<int, Mat>>(); var modelPath = AppDomain.CurrentDomain.BaseDirectory; using (var engine = new TesseractEngine(modelPath, "chi_sim", EngineMode.Default)) { for (int i = 0; i < items.Count; i++) { var imageFile = tempImagesDirectory + $"{i}.png"; // 打开图片并识别文字 var readTxt = string.Empty; using (var img = Pix.LoadFromFile(imageFile)) { using (var page = engine.Process(img)) { readTxt = page.GetText(); } } System.Text.RegularExpressions.MatchCollection matchCollection = System.Text.RegularExpressions.Regex.Matches(readTxt, "\\d+"); if (matchCollection.Count > 0) { int.TryParse(matchCollection[matchCollection.Count - 1].Value, out var val); targetVals.Add(new Tuple<int, Mat>(val, items[i])); if (File.Exists(imageFile)) { File.Delete(imageFile); } } } } //if (items.Count != targetVals.Count) //{ // return false; //} //识别成功的 int count = 0; foreach (var tuple in targetVals) { var imageFile = tempImagesDirectory + $"{count++}-{tuple.Item1}.png"; Cv2.ImWrite(imageFile, tuple.Item2); } //using (var dst = new Mat()) //{ // Cv2.VConcat(items, dst); // Cv2.ImWrite("E:\\ShanHaiJingPic\\result.jpg", dst); // // 定义新的尺寸 // //OpenCvSharp.Size newSize = new OpenCvSharp.Size(src.Cols / 2, src.Rows / 2); // 缩小到原来的一半 // //Cv2.Resize(src, dst, newSize); // //using (var window = new OpenCvSharp.Window("Rectangle Detection")) // //{ // // window.ShowImage(dst); // // Cv2.WaitKey(0); // //} //} } } }
主界面:
<Window x:Class="ShanHaiJingToolkit.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ShanHaiJingToolkit" mc:Ignorable="d" Title="统计部落活跃" Height="1020" Width="1050"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10 0 0 0"> <TextBlock Text="图片路径:"/> <TextBox Text="E:\ShanHaiJingPic\20240415" x:Name="filePath" Width="200"/> <TextBlock Text="需要加活跃数:" Margin="10 0 0 0"/> <TextBox x:Name="txtAdd" Text="0" Width="100"/> <TextBlock Text="生成标题:" Margin="10 0 0 0"/> <TextBox x:Name="txtTitle" Text="31305部落活跃奖励 2024-04-08至04-14" Width="250"/> <Button Content="统计活跃" Margin="10 0 0 0" Click="Button_Click"/> <Button Content="生成大图" Margin="10 0 0 0" Click="Button_Click_1"/> </StackPanel> <GroupBox Header="数据列表" Grid.Row="1"> <ScrollViewer> <WrapPanel x:Name="dataPanel"/> </ScrollViewer> </GroupBox> </Grid> </Window>
using ShanHaiJingToolkit.Common; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace ShanHaiJingToolkit { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void Button_Click(object sender, RoutedEventArgs e) { dataPanel.Children.Clear(); var fileDirectory = filePath.Text?.Trim(); int itemWidth = 0; int itemHeight = 0; List<string> files = new List<string>(); await Task.Run(() => { try { CvHelper cvHelper = new CvHelper(); cvHelper.ReadPictureActivityValue(fileDirectory, out itemWidth, out itemHeight); } catch (Exception ex) { Dispatcher.Invoke(() => { MessageBox.Show("读取出错!"); }); } var tempImagesDirectory = fileDirectory + "\\temp\\"; System.IO.DirectoryInfo directoryInfo = new System.IO.DirectoryInfo(tempImagesDirectory); foreach (var file in directoryInfo.GetFiles()) { files.Add(file.FullName); } }); itemWidth /= 3; itemHeight /= 3; Grid titleGrid = new Grid() { Width = dataPanel.ActualWidth, Height = itemHeight }; TextBlock textBlock = new TextBlock() { Text = txtTitle.Text, FontSize = 30, Foreground = Brushes.YellowGreen, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center, }; titleGrid.Children.Add(textBlock); dataPanel.Children.Add(titleGrid); int.TryParse(txtAdd.Text, out var addVal); int sumHuoYue = 0; int sumJiangli = 0; List<Tuple<int, string>> tuples = new List<Tuple<int, string>>(); foreach (var file in files) { var fileName = file.Substring(file.LastIndexOf('\\') + 1).Replace(".png", ""); int countResult = GetVal(fileName); sumHuoYue += countResult; countResult += addVal; var jiangliCount = GetFinalVal(countResult); sumJiangli += jiangliCount; tuples.Add(new Tuple<int, string>(jiangliCount, file)); } tuples = tuples.OrderByDescending(x => x.Item1).ToList(); foreach (var item in tuples) { Grid grid = new Grid() { Width = itemWidth, Height = itemHeight, }; grid.Children.Add(new Image() { Source = new BitmapImage(new Uri(item.Item2, UriKind.Absolute)), Width = itemWidth, Height = itemHeight, }); grid.Children.Add(new TextBlock() { FontSize = 18, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF087559")), FontWeight = FontWeights.Bold, Text = $"奖励:{item.Item1}芯", HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0), }); dataPanel.Children.Add(grid); } Grid sumGrid = new Grid() { Width = itemWidth, Height = itemHeight, }; var sumTextBlock = new TextBlock() { FontSize = 20, Foreground = Brushes.Red, Text = $"总活跃:{sumHuoYue},总奖励芯数:{sumJiangli}", VerticalAlignment = VerticalAlignment.Center, }; sumGrid.Children.Add(sumTextBlock); dataPanel.Children.Add(sumGrid); //底部 Grid bottomGrid = new Grid() { Width = dataPanel.ActualWidth, Height = itemHeight }; Rectangle rectangle = new Rectangle() { Width = dataPanel.ActualWidth, Height = 2, Fill = Brushes.Black }; bottomGrid.Children.Add(rectangle); dataPanel.Children.Add(bottomGrid); } //VLOOKUP(I2,{0,0;280,3;300,4;320,6;340,8;360,9;380,11},2,1) int GetFinalVal(int val) { if (val < 280) return 0; if (val <= 300 && val >= 280) return 3; if (val <= 320 && val > 300) return 4; if (val <= 340 && val > 320) return 6; if (val <= 360 && val > 340) return 8; if (val <= 380 && val > 360) return 9; if (val > 380) return 11; return 0; } int GetVal(string val) { int result = 0; if (!string.IsNullOrEmpty(val)) { var splits = val.Split('-'); int.TryParse(splits[1], out result); } return result; } public void SaveVisualToPng(Visual visual, string filePath, int dpiX = 96, int dpiY = 96) { // 获取控件的边界大小 Rect bounds = VisualTreeHelper.GetDescendantBounds(visual); // 创建 RenderTargetBitmap,其大小与控件大小一致,并设置DPI RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap( (int)(bounds.Width * dpiX / 96), (int)(bounds.Height * dpiY / 96), dpiX, dpiY, PixelFormats.Pbgra32); // 将控件渲染到 RenderTargetBitmap 上 renderTargetBitmap.Render(visual); // 将 RenderTargetBitmap 编码为 PNG 图片 PngBitmapEncoder pngEncoder = new PngBitmapEncoder(); pngEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap)); // 保存图片到文件 using (Stream stream = File.Create(filePath)) { pngEncoder.Save(stream); } } private void Button_Click_1(object sender, RoutedEventArgs e) { var fileDirectory = filePath.Text?.Trim(); var fileName = fileDirectory + $"\\{txtTitle.Text}.png"; SaveVisualToPng(dataPanel, fileName); MessageBox.Show("大图生成成功!" + fileName); // 使用默认的文件浏览器打开文件夹 Process.Start("explorer.exe", fileDirectory); } } }