图像简单处理
需求:对上面多张这样的图像,将成员识别出来,读取本周活跃值。生成一张全部由成员组成的大图
思路:使用OpenCVSharp进行目标区域边框检测,根据大小过滤目标区域。使用Tesseract对指定位置进行文字识别(貌似识别精度不高,有乱码。)
主要代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | 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); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用