1. Microsoft Developer Network (MSDN) [Электрон. ресурс]. ‑ Режим доступа: http://msdn.ru/
2. Ковалева И.Л., "Алгоритмы обработки изображений", БНТУ, 2007
Небольшой мануал по проге:
- Текст должен быть черным по белому
- Картинки для теста есть в архиве.
- Ориентация текста не под углом.
- Размер картинки желательно не меньше чем, те, что лежат в архиве, потому что при маленьком изображении плохо распознается из-за сливания пикселей.
- Распознавание персептроном с несколькими сумматорами и алгоритмом обучения без учета правильности ответа (она может это спросить=)
- Для обучения персептрона надо открыть изображение "Картинка для обучения.png" из папки "тестовые изображения" или создать аналогичную самостоятельно и открыть ее. Потом нажать "сегментация", Потом "Обучить", можно сохранить обучение, нажав "сохранить". Теперь можно открывать изображение, которое будет распознаваться. Для распознавания надо нажать "сегментация", потом "распознать".
- Если проводилось сохранение обучения, то можно не обучать. Для распознавания в таком случае надо делать следующие: открываешь распознаваемое изображение, нажимаешь "Сегментация", нажимаешь "загрузить", нажимаешь "распознать".
- В записке в графической части нужно вставить некоторые свои данные, я их отметил красным.
- путь к EXE-шнику: \WordSearcher\WordSearcher\bin\Debug\ WordSearcher.exe
Если будут какие-то баги или вопросы, сообщай - исправлю.
С уважением, Свирко Юрий
Mail: sv1r4.sd@gmail.com
Phone: 8-033-63-123-60
ЛИСТИНГ ПРОГРАММЫusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WordSearcher
{
public partial class FormMain : Form
{
/// <summary>
/// Флаг разрешения распознавания
/// </summary>
bool enableRecognize = false;
/// <summary>
/// Размер битмапа со словом "Указ"
/// к этому размеру подгоняются все отсалдьные распознаваемые битмапы
/// </summary>
private static Size imSize = new Size(65, 25);
/// <summary>
/// Состояние формы
/// </summary>
private FormState formState = FormState.Empty;
/// <summary>
/// Масив битмапов сос словами текста
/// </summary>
private List<Bitmap> words;
/// <summary>
/// Объект класса для распознавания(персептрон)
/// </summary>
private Recognizer r = new Recognizer(imSize, 750, 2);
/// <summary>
/// Метод для делания активными неактивными кнопок управления
/// в зависимости от этапа обработк изображения
/// </summary>
/// <param name="fs">текущее состояние</param>
private void ButtonsEnabled(FormState fs)
{
formState = fs;
switch (fs)
{
case FormState.Empty:
buttonOpen.Enabled = true;
buttonSegment.Enabled = false;
buttonRecognize.Enabled = false;
buttonTeach.Enabled = false;
buttonLoadTeaching.Enabled = false;
buttonSaveTeaching.Enabled = false;
pictureBoxMain.Image = null;
dataGridViewSegments.Rows.Clear();
break;
case FormState.Open:
buttonOpen.Enabled = true;
buttonSegment.Enabled = true;
buttonRecognize.Enabled = false;
buttonTeach.Enabled = false;
buttonLoadTeaching.Enabled = false;
buttonSaveTeaching.Enabled = false;
dataGridViewSegments.Rows.Clear();
break;
case FormState.Segmented:
buttonOpen.Enabled = true;
buttonSegment.Enabled = true;
if (enableRecognize)
buttonRecognize.Enabled = true;
else
buttonRecognize.Enabled = false;
buttonTeach.Enabled = true;
buttonLoadTeaching.Enabled = true;
buttonSaveTeaching.Enabled = false;
break;
case FormState.Teached:
buttonOpen.Enabled = true;
buttonSegment.Enabled = false;
buttonRecognize.Enabled = true;
buttonTeach.Enabled = false;
buttonLoadTeaching.Enabled = false;
buttonSaveTeaching.Enabled = true;
enableRecognize = true;
break;
case FormState.Deserialized:
buttonOpen.Enabled = true;
buttonSegment.Enabled = false;
buttonRecognize.Enabled = true;
buttonTeach.Enabled = false;
buttonLoadTeaching.Enabled = false;
buttonSaveTeaching.Enabled = true;
enableRecognize = true;
break;
case FormState.Recognized:
buttonOpen.Enabled = true;
buttonSegment.Enabled = false;
buttonRecognize.Enabled = true;
buttonTeach.Enabled = false;
buttonLoadTeaching.Enabled = true;
buttonSaveTeaching.Enabled = true;
break;
}
}
public FormMain()
{
InitializeComponent();
}
private void buttonOpen_Click(object sender, EventArgs e)
{
try
{
Bitmap b;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
//Если изобраение имеет индексированный формат
//то переводим его в обычный, потомутчо с индексировнным не работат
//setpixel
b = new Bitmap(openFileDialog1.FileName);
if (b.PixelFormat == System.Drawing.Imaging.PixelFormat.Format1bppIndexed ||
b.PixelFormat == System.Drawing.Imaging.PixelFormat.Format4bppIndexed ||
b.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
b = new Bitmap(b);
pictureBoxMain.Image = b;
this.ButtonsEnabled(FormState.Open);
}
else
{
this.ButtonsEnabled(FormState.Empty);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void buttonSegment_Click(object sender, EventArgs e)
{
//Получаем набор битмапов соответствующих словам текста
words = Segmentation.GetWords((Bitmap)pictureBoxMain.Image);
dataGridViewSegments.RowCount = words.Count;
int i =0;
//Перебираем слов и отображаем в таблице
foreach (Bitmap word in words)
{
dataGridViewSegments.Rows[i].Cells[0].Value = word;
i++;
}
ButtonsEnabled(FormState.Segmented);
}
private void buttonRecognize_Click(object sender, EventArgs e)
{
int i = 0;
foreach (Bitmap word in words)
{
dataGridViewSegments.Rows[i].Cells[1].Value = r.Recognize(Recognizer.NormalizeBitmap(word,imSize));
i++;
}
ButtonsEnabled(FormState.Recognized);
}
private void buttonTeach_Click(object sender, EventArgs e)
{
//Перебираем слова и обучаем ими
//т.к. класса для распознаваня два то
//первый класс обучаем просто изображение слова
//а второй обучаем противопорложным изображение(т.е инвертируем цвета исходного изображения)
for (int i = 0; i < 5; i++)
{
foreach (Bitmap word in words)
{
r.Teach(Recognizer.NormalizeBitmap(word, imSize), 0);
r.Teach(Recognizer.InverseBitmap(Recognizer.NormalizeBitmap(word, imSize)), 1);
}
}
ButtonsEnabled(FormState.Teached);
}
private void buttonSaveTeaching_Click(object sender, EventArgs e)
{
r.SerializeParams();
ButtonsEnabled(FormState.Serialized);
}
private void buttonLoadTeaching_Click(object sender, EventArgs e)
{
r.DeserializeParams();
ButtonsEnabled(FormState.Deserialized);
}
private void FormMain_Load(object sender, EventArgs e)
{
this.ButtonsEnabled(FormState.Empty);
}
}
// <summary>
/// Состояния изображения
/// </summary>
enum FormState
{
/// <summary>
/// изображение не открыто
/// </summary>
Empty,
/// <summary>
/// Изображение открыто
/// </summary>
Open,
/// <summary>
/// Сегментировано
/// </summary>
Segmented,
/// <summary>
/// Персептрон обучен
/// </summary>
Teached,
/// <summary>
/// Параметры персептрона сохранены
/// </summary>
Serialized,
/// <summary>
/// Параметры персептрона загружены
/// </summary>
Deserialized,
/// <summary>
/// Распознано
/// </summary>
Recognized
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace WordSearcher
{
/// <summary>
/// Реализует распозноание изображений
/// на базе персептрона
/// </summary>
class Recognizer
{
/// <summary>
/// матрица знаков входов персептрона
/// </summary>
private int[,] xa;
/// <summary>
/// Массив лямд
/// </summary>
private int[,] l;
/// <summary>
/// Массив имен классов
/// </summary>
private string[] classes = {"Указ",
"Не указ"};
/// <summary>
/// Массив имен классов
/// </summary>
public string[] ClassesList
{
get { return classes; }
}
/// <summary>
/// Инициализирует xa-матрицу
/// </summary>
/// <param name="sz">размер изображения</param>
/// <param name="aCount">количесвто а-элементов</param>
/// <param name="lCount">количесвто классов</param>
public Recognizer(Size sz, int aCount, int lCount)
{
Random r = new Random();
//Создание матрцы ха
xa = new int[sz.Height * sz.Width, aCount];
//Создание матрицы лямд
l = new int[lCount,aCount];
//Первоначальная
//иницализация лямд еденицами
for (int i = 0; i < l.GetLength(0); i++)
{
for (int j = 0; j < l.GetLength(1); j++)
{
l[i, j] = 1;
}
}
//заполнение матрицы
//для каждого рецептора(строчки)
//назначаетя только один а-элемент(столбец) со знаком + или -
for (int i = 0; i < xa.GetLength(0); i++)
{
xa[i, r.Next(aCount)] = (int)Math.Pow(-1, r.Next(1, 3));
}
}
/// <summary>
/// Обучение персептрона
/// </summary>
/// <param name="b">битмап для обучения</param>
/// <param name="classindex">имя класса к ккоторому относиться изображение</param>
public void Teach(Bitmap b, int classindex)
{
int[] x = new int[b.Height * b.Width];
int k = 0;
//Инициализация входных рецепторов
for (int i = 0; i < b.Width; i++)
{
for (int j = 0; j < b.Height; j++)
{
if (b.GetPixel(i, j) == Color.FromArgb(0, 0, 0))
x[k] = 1;
k++;
}
}
//Вектор сумм рецепторов
int[] sumx = new int[xa.GetLength(1)];
//Вектор выходов А-элементов
int[] outa = new int[xa.GetLength(1)];
//суммирование сигналов от рецепторов
for (int i = 0; i < xa.GetLength(1); i++)
{
for (int j = 0; j < xa.GetLength(0); j++)
{
sumx[i] += x[j] * xa[j, i];
}
//Если сумма больше нуля выход а элемента 1
if (sumx[i] > 0)
outa[i] = 1;
}
//изменение коэфициетов лямда
for (int i = 0; i < outa.Length; i++)
{
//Если а-элемент возбужден то изменяем лямды
if (outa[i] == 1)
{
//перебор всех классов
for (int j = 0; j < l.GetLength(0); j++)
{
//Увеличение на 1 лямд для класса который обучается
//и уменьшение для всех осатльных
if (classindex == j)
l[j, i]++;
else
l[j, i]--;
}
}
}
}
/// <summary>
/// Распознавание изобржения
/// </summary>
/// <param name="b">битмап изображения</param>
/// <returns>имя класса к которому отнесено изображение</returns>
public string Recognize(Bitmap b)
{
int[] x = new int[b.Height * b.Width];
int k = 0;
//Инициализация входных рецепторов
for (int i = 0; i < b.Width; i++)
{
for (int j = 0; j < b.Height; j++)
{
if (b.GetPixel(i, j) == Color.FromArgb(0, 0, 0))
x[k] = 1;
k++;
}
}
//Вектор суммрецепторов
int[] sumx = new int[xa.GetLength(1)];
//Вектор выходов А-элементов
int[] outa = new int[xa.GetLength(1)];
//суммирование сигналов от рецепторов
for (int i = 0; i < xa.GetLength(1); i++)
{
for (int j = 0; j < xa.GetLength(0); j++)
{
sumx[i] += x[j] * xa[j, i];
}
//Если сумма больше нуля выход а элемента 1
if (sumx[i] > 0)
outa[i] = 1;
}
//Создание масива значений сумматоров
//каждый для отдельного класса
int[] sum = new int[l.GetLength(0)];
//Нахождение значений сумматоров для каждого класса
for (int i = 0; i < sum.Length; i++)
{
for (int j = 0; j < xa.GetLength(1); j++)
{
sum[i] += outa[j] * l[i, j];
}
}
//нахождение максимального значения сумматор
//именно оно соответствует распознанному классу
int max = sum[0];
int maxindex = 0;
for (int i = 1; i < sum.Length; i++)
{
if (max < sum[i])
{
max = sum[i];
maxindex = i;
}
}
//Возвращается имя класса с максимальным значением сумматора
return classes[maxindex];
}
/// <summary>
/// Сериализация массива лямд(сохранение в файл) для сохранения обученяи персептрона
/// </summary>
public void SerializeParams()
{
try
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream("l.dat", FileMode.Create);
bf.Serialize(fs, l);
fs.Close();
bf = new BinaryFormatter();
fs = new FileStream("xa.dat", FileMode.Create);
bf.Serialize(fs, xa);
fs.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// Десериализация массива лямд(чтение из файла)
/// </summary>
public void DeserializeParams()
{
try
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream("l.dat", FileMode.Open);
l = (int[,])bf.Deserialize(fs);
fs.Close();
bf = new BinaryFormatter();
fs = new FileStream("xa.dat", FileMode.Open);
xa = (int[,])bf.Deserialize(fs);
fs.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// Подгонка битмапа по размеру и его бинаризация
/// </summary>
/// <param name="b">входной битмап</param>
/// <param name="sz">новый размер битмапа</param>
/// <returns>нормализованный битмап</returns>
public static Bitmap NormalizeBitmap(Bitmap b, Size sz)
{
//Подгонка размера
Bitmap inImg = new Bitmap(b, sz);
//Создание выходного битмапа на основе подогнанного
Bitmap outImg = new Bitmap(inImg);
//находим среднее значение яркости
int sum = 0;
for (int i = 0; i < outImg.Width; i++)
{
for (int j = 0; j < outImg.Height; j++)
{
Color cl = ((Bitmap)inImg).GetPixel(i,j);
sum += (cl.R + cl.G + cl.B) / 3;
}
}
int sredn = sum / (inImg.Width * inImg.Height);
//Просматриваем изображнеи и бинаризуем его
for (int i = 0; i < outImg.Width; i++)
{
for (int j = 0; j < outImg.Height; j++)
{
Color cl = ((Bitmap)inImg).GetPixel(i,j);
int gray = (cl.R + cl.G + cl.B) / 3;
if (gray > sredn)
outImg.SetPixel(i, j, Color.FromArgb(255, 255, 255));
else
outImg.SetPixel(i, j, Color.FromArgb(0, 0, 0));
}
}
return outImg;
}
/// <summary>
/// Инверсия цвета битмапа
/// </summary>
/// <param name="b"></param>
/// <returns></returns>
public static Bitmap InverseBitmap(Bitmap b)
{
Bitmap outImg = new Bitmap(b.Width, b.Height);
for (int i = 0; i < b.Width; i++)
{
for (int j = 0; j < b.Height; j++)
{
Color c = b.GetPixel(i,j);
outImg.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));
}
}
return outImg;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace WordSearcher
{
class Segmentation
{
/// <summary>
/// Разбиение битмапа с текстоми на строки
/// </summary>
/// <param name="b">исходный битмап</param>
/// <returns>коллекция строк</returns>
public static List<Bitmap> GetStrings(Bitmap text)
{
List<Bitmap> strs = new List<Bitmap>();
List<int> whiteLineIndexes = new List<int>();
//Находим все белые горзонатльные линии на ихображении
//и запоминаем их индексы
for (int j = 0; j < text.Height; j++)
{
bool whiteLineFound = true;
for (int i = 0; i < text.Width; i++)
{
if (text.GetPixel(i, j) != Color.FromArgb(255, 255, 255))
{
whiteLineFound = false;
break;
}
}
if (whiteLineFound)
whiteLineIndexes.Add(j);
}
//Выделение строк между белыми несоседними линиями
for (int i = 0; i < whiteLineIndexes.Count-1; i++)
{
if (whiteLineIndexes[i + 1] - whiteLineIndexes[i] > 4)
{
strs.Add(text.Clone(
new Rectangle(
0,
whiteLineIndexes[i],
text.Width,
whiteLineIndexes[i + 1] - whiteLineIndexes[i]+1),
System.Drawing.Imaging.PixelFormat.Format24bppRgb));
}
}
return strs;
}
/// <summary>
/// Получить список слов отдельной строки
/// </summary>
/// <param name="str">битмап со строкой текста</param>
/// <returns>спсиок слов строки</returns>
public static List<Bitmap> GetStringWords(Bitmap str)
{
List<Bitmap> words = new List<Bitmap>();
List<int> whiteLineIndexes = new List<int>();
//Находим все белые вертикальные линии на изображении
//и запоминаем их индексы
for (int i = 0; i < str.Width; i++)
{
bool whiteLineFound = true;
for (int j = 0; j < str.Height; j++)
{
if (str.GetPixel(i, j).R < 100)
{
whiteLineFound = false;
break;
}
}
if (whiteLineFound)
whiteLineIndexes.Add(i);
}
//Ширина пробела
int spaceWidth = 0;
int sum = 0;
int n = 0;
//Вычисление ширины пробела
for (int i = 0; i < whiteLineIndexes.Count - 1; i++)
{
int d = whiteLineIndexes[i + 1] - whiteLineIndexes[i];
if (d > 1)
{
sum += d;
n++;
}
}
//Ширина пробела необходимо при дальнейшем выделении слов
//коэф. подобран вручную
spaceWidth = (int)Math.Round(sum * 0.45 / n + 0.1);
//начальная координата слова
int wordBegin = 0;
//конечная координат слова
int wordEnd = 0;
//флаг указывающий на то найденно ли начало слова или нет
//перволдится обратно в фолс после нахождения конца слова
bool wordFound = false;
//Счетчик ширины белой полоски
int whiteWidth = 0;
//Выделение слов
for (int i = 0; i < whiteLineIndexes.Count - 1; i++)
{
//если линии не соседние и флаг wordFound фолс т.е.
//слово еще не найдено
//запоминаем координату певрой линии это будет
//координатой началом слова
if ((whiteLineIndexes[i + 1] - whiteLineIndexes[i] > 1) &&
!wordFound)
{
//обнуление счетчика идущих подряд белыхз линий
whiteWidth = 0;
//флаг найденного слова в тру
wordFound = true;
//инициализируем начальную координату слова
wordBegin = whiteLineIndexes[i];
}
//инициализируем конечную координату слова
//если найдены не сосдение линии
//но не обрезаем битмап и не добавлям его в коллекцию
//т.к. необходисмо зделать проверку на ширину пробела
if ((whiteLineIndexes[i + 1] - whiteLineIndexes[i] > 1) &&
wordFound)
{
whiteWidth = 0;
wordEnd = whiteLineIndexes[i + 1];
}
//Если найденны соседние белые линии
//инкремируем счетчик белых линий и сравниваем ширину идущих подрд белых линий
//с ранее высчитаной средней шириной пробела
if (whiteLineIndexes[i + 1] - whiteLineIndexes[i] == 1)
{
whiteWidth++;
if ((whiteWidth >= spaceWidth) &&
(wordEnd - wordBegin > 1))
{
//Обрезаем и добавляем слово в коллекцию
words.Add(TrimBitmap(
str.Clone(
new Rectangle(
wordBegin,
0,
wordEnd - wordBegin + 1,
str.Height),
System.Drawing.Imaging.PixelFormat.Format24bppRgb)
)
);
//обнуляем счетчики
//и флаги
whiteWidth = 0;
wordFound = false;
wordBegin = 0;
wordEnd = 0;
}
}
}
return words;
}
/// <summary>
/// Получить битмапы всех слов в тексте
/// </summary>
/// <param name="text">битмап с текстом</param>
/// <returns>коллекция всех слов в тексте</returns>
public static List<Bitmap> GetWords(Bitmap text)
{
List<Bitmap> strs = GetStrings(text);
List<Bitmap> words = new List<Bitmap>();
foreach (Bitmap str in strs)
{
foreach (Bitmap word in GetStringWords(str))
{
words.Add(word);
}
}
return words;
}
/// <summary>
/// Обрезка белых полей вокруг изображения на битмапе
/// </summary>
/// <param name="bmp"></param>
/// <returns></returns>
public static Bitmap TrimBitmap(Bitmap bmp)
{
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
bool go = true;
//проход сверху
for (int j = 0; (j < bmp.Height) && go; j++)
{
for (int i = 0; (i < bmp.Width) && go; i++)
{
if (bmp.GetPixel(i, j) != Color.FromArgb(255, 255, 255))
{
go = false;
top = j;
}
}
}
go = true;
//проход снизу
for (int j = bmp.Height - 1; (j >= 0) && go; j--)
{
for (int i = 0; (i < bmp.Width) && go; i++)
{
if (bmp.GetPixel(i, j) != Color.FromArgb(255, 255, 255))
{
go = false;
bottom = j;
}
}
}
go = true;
//проход слева
for (int i = 0; (i < bmp.Width) && go; i++)
{
for (int j = 0; (j < bmp.Height) && go; j++)
{
if (bmp.GetPixel(i, j) != Color.FromArgb(255, 255, 255))
{
go = false;
left = i;
}
}
}
go = true;
//проход спарва
for (int i = bmp.Width - 1; (i >= 0) && go; i--)
{
for (int j = 0; (j < bmp.Height) && go; j++)
{
if (bmp.GetPixel(i, j) != Color.FromArgb(255, 255, 255))
{
go = false;
right = i;
}
}
}
return bmp.Clone(new Rectangle(left, top, right - left + 1, bottom - top + 1), System.Drawing.Imaging.PixelFormat.Format24bppRgb);
}
}
}
ОПИСЬ ЛИСТОВ ГРАФИЧЕСКОЙ ЧАСТИ
Лист 1 – Схема приложения
Функциональная схема приложения
Лист 2 – Диаграмма классов
Лист 3 – Результаты работы программы.
Лист 4 – Схема алгоритма сегментации текста.
... 0.98; gotoxy(10,24); writeln('Press any key to continue...'); readkey; graphiki; readkey; closegraph; End. ПРИЛОЖЕНИЕ Б Текст программы распознавания символов Program Final_of_work; uses graph; const BiH=50; {-------высота картинки в пикселях------} BiW=160; {-------ширина картинки в ...
... Правильное понимание таблицы читателем невозможно без учета информации о взаимном расположении строк, колонок и ячеек таблицы. Поэтому при автоматизированном распознавании табличных форм необходимо в выходном документе сохранить то же взаимное расположение этих структурных табличных элементов, что и в исходной таблице. Строки и колонки таблиц могут иметь иерархическую структуру (рис. 3), причем ...
... случае присоединения к компьютерной системе сканера, в окне папки «Панель управления» появляется соответствующий значок, позволяющий производить настройку. Таким образом, в большинстве программ работа со сканером производится при посредстве специального диалогового окна, обеспечивающего непосредственное взаимодействие со сканером. После того как пользователь дает команду на сканирование документа ...
... с приглашением по запросу (в машинной графике)required parameter обязательный параметрrequired space обязательный пробел (в системах подготовки текстов)requirements specification 1. техническое задание 2. описание требований к программному средствуrerun перезапуск, повторный запускreschedule переупорядочивать очередь (о диспетчере операционной системы)reschedule interval период переупорядочения ...
0 комментариев