OpenCVの文字区域提取

MainWindow.xaml

<Window x:Class="Splash.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="OpenCVの文字区域提取" Icon="OpenCV.ico" WindowState="Maximized" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Button Grid.Row="0" Margin="4" Padding="16,8" MinWidth="160" HorizontalAlignment="Left" Content="选择图像…" Name="ButtonOpen" Click="ButtonOpen_Click"/>
        <TabControl Grid.Row="1" Margin="4">
            <TabItem Header="原图" Padding="16,8">
                <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                    <Image Margin="4" Stretch="None" Name="ImageRaw"/>
                </ScrollViewer>
            </TabItem>

            <TabItem Header="二值化图" Padding="16,8">
                <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                    <Image Margin="4" Stretch="None" Name="ImageBinary"/>
                </ScrollViewer>
            </TabItem>

            <TabItem Header="初次膨胀图" Padding="16,8">
                <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                    <Image Margin="4" Stretch="None" Name="ImageDilate1"/>
                </ScrollViewer>
            </TabItem>

            <TabItem Header="初次腐蚀图" Padding="16,8">
                <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                    <Image Margin="4" Stretch="None" Name="ImageErode1"/>
                </ScrollViewer>
            </TabItem>

            <TabItem Header="二次膨胀图" Padding="16,8">
                <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                    <Image Margin="4" Stretch="None" Name="ImageDilate2"/>
                </ScrollViewer>
            </TabItem>

            <TabItem Header="轮廓图" Padding="16,8">
                <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                    <Image Margin="4" Stretch="None" Name="ImageContour"/>
                </ScrollViewer>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

MainWindow.xaml.cs

/* ----------------------------------------------------------
* 文件名称:MainWindow.xaml.cs
*
* 作者:秦建辉
*
* 微信:splashcn
*
* 博客:http://www.firstsolver.com/wordpress/
*
* 开发环境:
*      Visual Studio V2017
*      .NET Framework 4.7.2
*      OpenCvSharp 4.0.30319
*
* 版本历史:
*		V1.0    2019年01月05日
*				OpenCVの文字区域检测
*				
* 参考资料:
*       http://www.voidcn.com/article/p-rkwiytbi-dk.html
*       https://blog.csdn.net/lgh0824/article/details/76100599
* ---------------------------------------------------------- */
using Com.FirstSolver.Splash;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;

namespace Splash
{
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonOpen_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog
                {
                    Filter = "Image|*.jpg;*.bmp;*.png;*.tif;*.tga;*.ras;*.jp2;*.j2k;*.jpe",
                    DereferenceLinks = true
                };

                this.CenterChild();
                if (dlg.ShowDialog(Owner).Value == true)
                {   // 读取图像文件
                    byte[] data;
                    using (FileStream fs = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        data = new byte[fs.Length];
                        fs.Read(data, 0, (int)fs.Length);
                    }

                    // 显示源图像
                    BitmapImage bi = new BitmapImage();
                    bi.BeginInit();
                    bi.StreamSource = new MemoryStream(data);
                    bi.CacheOption = BitmapCacheOption.OnLoad;
                    bi.EndInit();
                    ImageRaw.Source = bi;

                    ThreadPool.QueueUserWorkItem(new WaitCallback((state) => {
                        // 生成Bgr图像
                        Mat Bgr = Cv2.ImDecode(data, ImreadModes.Color);

                        // 转换成灰度图
                        Mat Gray = Bgr.CvtColor(ColorConversionCodes.BGR2GRAY);

                        // 形态学变换的预处理,得到可以查找矩形的轮廓
                        Mat Dilation = Preprocess(Gray);

                        // 查找和筛选文字区域
                        RotatedRect[] Regions = FindTextRegion(Dilation);

                        // 用绿线画出这些找到的轮廓
                        foreach (RotatedRect rr in Regions)
                        {
                            Point2f[] p = rr.Points();
                            Cv2.Line(Bgr, (int)p[0].X, (int)p[0].Y, (int)p[1].X, (int)p[1].Y, new Scalar(0, 255, 0), 2);
                            Cv2.Line(Bgr, (int)p[1].X, (int)p[1].Y, (int)p[2].X, (int)p[2].Y, new Scalar(0, 255, 0), 2);
                            Cv2.Line(Bgr, (int)p[2].X, (int)p[2].Y, (int)p[3].X, (int)p[3].Y, new Scalar(0, 255, 0), 2);
                            Cv2.Line(Bgr, (int)p[3].X, (int)p[3].Y, (int)p[0].X, (int)p[0].Y, new Scalar(0, 255, 0), 2);
                        }

                        // 线程安全性
                        this.Dispatcher.BeginInvoke(new Action(() => {
                            ImageContour.Source = Bgr.ToBitmapSource();
                        }));
                    }));
                }
            }
            catch (System.Exception exception)
            {
                MessageBoxPlus.Show(this, exception.Message, "图像文件异常", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // 预处理
        private Mat Preprocess(Mat gray)
        {   // Sobel算子,x方向求梯度
            Mat sobel = new Mat();
            Cv2.Sobel(gray, sobel, MatType.CV_8U, 1, 0, 3);

            // 二值化
            Mat Binary = new Mat();
            Cv2.Threshold(sobel, Binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);

            // 膨胀一次,让轮廓突出            
            Mat ElementDilate = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(24, 4));
            Mat Dilate1 = new Mat();
            Cv2.Dilate(Binary, Dilate1, ElementDilate, null, 1);

            // 腐蚀一次,去掉细节,表格线等。这里去掉的是竖直的线            
            Mat ElementErode = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(30, 9));
            Mat Erode1 = new Mat();
            Cv2.Erode(Dilate1, Erode1, ElementErode, null, 1);

            // 再次膨胀,让轮廓明显一些
            Mat Dilate2 = new Mat();
            Cv2.Dilate(Erode1, Dilate2, ElementDilate, null, 3);

            // 显示中间图像
            this.Dispatcher.BeginInvoke(new Action(() => {
                ImageBinary.Source = Binary.ToBitmapSource(); // 二值化图
                ImageDilate1.Source = Dilate1.ToBitmapSource(); // 初次膨胀图
                ImageErode1.Source = Erode1.ToBitmapSource(); // 初次腐蚀图
                ImageDilate2.Source = Dilate2.ToBitmapSource(); // 再次膨胀图
            }));

            return Dilate2;
        }

        // 查找和筛选文字区域
        private RotatedRect[] FindTextRegion(Mat img)
        {   // 查找轮廓
            OutputArray hierarchy = new Mat();
            Cv2.FindContours(img, out Mat[] contours, hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);

            // 筛选那些面积小的
            List<RotatedRect> regions = new List<RotatedRect>();
            foreach (Mat cnt in contours)
            {   // 计算当前轮廓的面积
                double area = Cv2.ContourArea(cnt);

                // 面积小于1000的全部筛选掉
                if (area < 1000) continue;

                // 轮廓近似,作用很小
                double epsilon = 0.001 * Cv2.ArcLength(cnt, true);
                Mat approx = new Mat();
                Cv2.ApproxPolyDP(cnt, approx, epsilon, true);

                // 找到最小矩形,该矩形可能有方向
                RotatedRect rect = Cv2.MinAreaRect(approx);
                int width = rect.BoundingRect().Width;
                int height = rect.BoundingRect().Height;

                // 筛选那些太细的矩形,留下扁的
                if (height > width * 1.2) continue;
                regions.Add(rect);
            }
            return regions.ToArray();
        }
    }
}

运行结果

猜你喜欢

转载自blog.csdn.net/jhqin/article/details/85911555