Я пишу приложение на C ++ для OpenCV. Приложение в основном анализирует рукописные изображения и распознает символы (не ожидайте буквенно-цифровых символов, это система письма для глухих).
Мои входные изображения содержат символы очень разных форм, но я бы хотел сосредоточиться на кругах.
Следующая картинка иллюстрирует выбор символов в форме круга, с которыми я сейчас работаю (после шумоподавления и бинаризации).
Для распознавания символов в форме круга я использую Hough Circle Transform, которая делает свою работу довольно хорошо. Преобразование применяется после применения медианного фильтра для уменьшения шума и порогового значения для преобразования изображения в двоичную форму.
Моя проблема в том, что иногда Hough Circle Transform обнаруживает круги там, где их нет. (см. следующую картину).
Теперь я искал некоторую «оценку надежности» для обнаруженных кругов. Но мне мало повезло.
Есть ли способ узнать, относится ли обнаруженный круг к реальному кругу?
Я начинаю думать о некоторых решениях самостоятельно, но, может быть, кто-то придумал что-то умнее, я мог бы:
Помните: я имею в виду рукописные (т.е. очень грубо нарисованные) символы.
Я думаю, что некоторая форма оценки расстояния от точек вашего круга до символа — это путь, который я хочу — я сомневаюсь, что есть более сложные альтернативы, которые также практичны.
Однако существуют более и менее сложные и эффективные способы оценки расстояния.
Поскольку ваши изображения символов уже преобразованы в двоичную форму, я бы порекомендовал следующее: Преобразование расстояния на двоичном изображении; имеются эффективные алгоритмы для этого. Затем вам нужно только отобрать изображение расстояния в точках окружности и суммировать расстояния (возможно, нормированные относительно окружности окружности, чтобы получить некоторую неизменность масштаба), чтобы получить оценку ошибки.
Это не удастся, если в ваших изображениях много мелких помех, но те, которые вы опубликовали, выглядят довольно чисто.
Я решил проблему разработки функции, основанной на идее @Abhishek.
Вот псевдокод и его реализация на C ++.
checkCircleSectors(imagePoints, circle, circleExpansionFactor, theta, acceptThreshold)
Divide the circle in n sectors, using angle theta (so n = 2*PI /theta)
(Optional) Slightly expand or reduce the radius of the circle by circleExpansionFactor, if needed.
for each point "curPoint" within the image (imagePoints){
if the distance between curPoint and center.circle is lesser/equal to circle.radius{
let "curAngle" be the angle between circle.center and curPoint, calculated using curPoint as origin.
let "curSector" be the sector of circle which contains curAngle
upgrade the ranking of curSector by one.
}
}
if there are more than acceptThreshold sectors whose rank is zero
the circle is not acceptable
else
the circle is acceptable
Заметки:
Я нашел очень полезным немного расширить радиус круга (Я использую коэффициент расширения 1,25), потому что иногда (особенно в случае рукописных кругов) обнаружение может быть неточным.
Вот моя реализация концепции на C ++:
boolean ImgCheck::checkCircleSectors(vector<Point> tgtPoints, Point tgtCenter, int tgtRadius, float tgtRadiusExp, int tgtStep, int tgtThreshold){
vector<int> circleData( 360 / tgtStep, 0);
int detectionReliability = 0;
tgtRadius = tgtRadius * tgtRadiusExp;
/* Analyze the sectors. */
for(size_t i=0; i<tgtPoints.size(); i++){
Point curCartesianPoint = getCartesianCoordinates(tgtCenter, tgtPoints[i]);
float angleRad = arctangent2(curCartesianPoint);
int angleDeg = angleRad * (180/M_PI);
if(distance(tgtPoints[i],tgtCenter) <= tgtRadius){
circleData.at(angleDeg / tgtStep) += 1;
}
}
/* Count the postive-ranked sectors. */
for(size_t i = 0; i< circleData.size(); i++){
if(circleData[i] > 0)
detectionReliability += 100.0/(360/tgtStep);
}
if(detectionReliability >= tgtThreshold)
return true;
}
return false;
}
Заметки:
Я предпочитаю последние три параметра:
Функция getCartesianCoordinates (исходный код ниже) преобразует координаты OpenCV в декартовы координаты. Требуется два аргумента:
Функция возвращает координаты tgtPoint, преобразованные с использованием tgtOrigin в качестве источника.
Point getCartesianCoordinates(Point tgtOrigin, Point tgtPoint){
Point resPoint = tgtPoint - tgtOrigin;
return Point(resPoint.x, -resPoint.y);
}
Потратив некоторое время на ручное кодирование детекторов эллипса, я пошел по тому же пути в отношении обнаружения «эллипсности» (хорошее соответствие — это руководство). В конечном счете, однако, ручное кодирование таких детекторов является хрупким бизнесом, потому что ваш вклад на самом деле не так хорошо определен; вы, вероятно, обнаружите, что читатели-люди успешно распознают множество «кругов», которые не подойдут ни к какому разумному математическому определению «округлости». Поэтому примите, что фактическое рабочее определение вашего персонажа не является уравнением круга.
Я предлагаю вам рассмотреть совершенно другой подход; вместо детекторов ручного кодирования, тренировать их. Это современный подход к распознаванию рукописного текста, и вы можете выбрать популярные проверенные алгоритмы (набор данных MNIST рукописных арабских цифр является популярным эталоном). Вместо кодирования детекторов создайте себе действительно приличный тренировочный набор; вам не нужно будет вручную кодировать детекторы для каждого символа, и вы создадите что-то действительно надежное и хорошо протестированное, которое может справиться с очень небрежным почерком, пятнами, каракулями и т. д. (например, вы можете найти в наборе данных MNIST ).