Обнаружение прямоугольных документов с использованием Hough Transform OpenCV Android
Я пытаюсь определить прямоугольник документа с помощью OpenCV4Android SDK. Сначала я попытался обнаружить его, найдя контуры, но он не работает с многоцветными документами. Вы можете проверить эту ссылку, чтобы получить лучшую идею: обнаружение многоцветного документа с помощью OpenCV4Android
Я много исследовал и обнаружил, что это можно сделать с помощью преобразования houghline. Итак, я следовал следующим способом для обнаружения документа:
Исходное изображение -> cvtColor -> Фильтр GaussianBlur -> Расширить его для повышения резкости -> Примененный алгоритм сегментации изображения водораздела -> Обнаружение жесткого края с порогом динамического отсу -> Затем применить преобразование грубой линии
что я сделал для преобразования линии:
Imgproc.HoughLinesP(watershedMat, lines, 1, Math.PI / 180, 50, 100, 50);
List<Line> horizontals = new ArrayList<>();
List<Line> verticals = new ArrayList<>();
for (int x = 0; x < lines.rows(); x++)
{
double[] vec = lines.get(x, 0);
double x1 = vec[0],
y1 = vec[1],
x2 = vec[2],
y2 = vec[3];
Point start = new Point(x1, y1);
Point end = new Point(x2, y2);
Line line = new Line(start, end);
if (Math.abs(x1 - x2) > Math.abs(y1-y2)) {
horizontals.add(line);
} else if (Math.abs(x2 - x1) < Math.abs(y2 - y1)){
verticals.add(line);
}
}
и из списка выше горизонтальных и вертикальных линий, я нахожу точки пересечения, как показано ниже:
protected Point computeIntersection (Line l1, Line l2) {
double x1 = l1._p1.x, x2= l1._p2.x, y1 = l1._p1.y, y2 = l1._p2.y;
double x3 = l2._p1.x, x4 = l2._p2.x, y3 = l2._p1.y, y4 = l2._p2.y;
double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
// double angle = angleBetween2Lines(l1,l2);
Log.e("houghline","angle between 2 lines = "+angle);
Point pt = new Point();
pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
return pt;
}
и из этих четырех точек пересечения я рисую линии. Итак, пока я могу обнаружить документ через него. см. изображение ниже:
но когда другие объекты связаны с документом, он также пытается их обнаружить. я иду сверху вниз строки и слева направо столбцы, чтобы найти пересечения самого большого прямоугольника. Я получаю следующие проблемы:
Как вы можете видеть на изображениях выше, когда другой объект появляется на экране, он также обнаруживает его. Как обнаружить только документ? и игнорировать другие объекты? Вот мое оригинальное изображение:
Любая помощь будет высоко оценена! заранее спасибо
1 ответ
Общая информация
- Я использую OpenCV 3.2.0 в Windows 10, однако все перечисленные функции должны быть доступны в версии 2.4 и Android.
- Я изменил изображение для лучшей визуализации. Это не влияет на текущий подход к решению проблемы, однако, если бы мы использовали какое-то обнаружение краев, мы абсолютно должны использовать размер изображения оригинала.
- Текущее предоставленное решение использует множество пользовательских функций (определение цвета LAB, анализ размеров контуров и т. Д.), Которые не могут быть использованы здесь. Если вам нужна помощь в определенных областях, вы, конечно, можете обратиться за помощью в комментариях.
Общее наблюдение за проблемой
Есть несколько причин, почему ваши предыдущие подходы не сработали. Прежде чем мы перейдем к решению, вот несколько замечаний, которые необходимо учитывать:
- У вас есть объект, который содержит как более темные, так и более яркие элементы по сравнению с фоном.
- У вас есть объект, который состоит из довольно разных частей, касающихся как яркости, так и цвета, а также общей однородности. Фактически, объект разделен на секцию, которая очень похожа на фон.
- У вас есть фоновые объекты, которые четко отличаются от общего фона (например, черный объект в верхнем правом углу).
- Объект часто захватывается с немного наклоненной перспективы. Это вызывает перспективное преобразование в противном случае прямоугольного объекта.
Решение
Учитывая вышеупомянутые наблюдения, я не думаю, что простое определение порога или обнаружение краев даст какие-либо надежные результаты, особенно если смотреть на различия между различными изображениями одной и той же сцены. В качестве решения я бы предложил обнаружение и классификацию цветов переднего плана и / или фона через цветовое пространство LAB или HSV. Образцы изображений наиболее заметных цветов следует использовать для классификации соответствующих областей. Например, на переднем плане темно-ярко-красный, а также золотисто-желтоватый цвет книги. Фон состоит из довольно однородного сероватого цвета, который можно использовать для его обнаружения. Потенциальный алгоритм:
- Обнаружение и классификация переднего и заднего фона в соответствии с цветовым пространством LAB. Используйте разумный порог цветового расстояния (для меня что-то около 8-10% сработало в пространстве LAB - пространство AB может работать на 5-7%). Если изменение цвета из-за изменяющейся яркости становится проблемой, тогда переключитесь на независимый от яркости подход (например, просто используйте компоненты AB и игнорируйте компонент L)
- Исключить части фона из обнаружения на переднем плане (в классификации может быть некоторое совпадение, поэтому этот порядок предотвратит путаницу).
- На оставшемся двоичном изображении примените поиск контуров и отбросьте контуры с слишком маленькими участками.
- Остальные контуры образуют книгу. Создайте выпуклый корпус, который вы можете использовать в качестве объекта ROI.
Преимущества:
- Очень точный
- Работает в нескольких сценариях (смена фона, разное освещение - если используется правильное цветовое пространство)
Недостатки:
- Трудно реализовать для начинающих (знание LAB или HSV, цветовые расстояния, поддержка многоцветной классификации и т. Д.)
- Обнаружение цвета полностью зависит от фона и переднего плана. Это означает, что если книга изменится и будет, например, синего цвета, образцы изображений должны быть адаптированы.
- Этот подход не сработает, если все верх, низ или боковые стороны книги выглядят как фон. Если это так, эти части книги будут классифицированы как фон.
Сложность общего решения
Существуют причины, по которым существующий подход, пусть и продвинутый, будет недостаточным для общего применения (разные книги, разный опыт и т. Д.).
Если вам нужна универсальная система, которая может автоматически обнаруживать различные книги с различным фоном, у вас возникнут некоторые проблемы. Это достигает уровня сложности, который будет трудно решить. Это напоминает мне обнаружение номерных знаков: различную освещенность, шум, окрашенные предметы, сильно различающиеся фоны, плохую контрастность и т. Д. И даже если вам удастся это сделать, то здесь есть одна загвоздка: такая система будет работать только для определенных типов номерных знаков. То же самое относится и к вашим книгам.
тесты
Поскольку вы опубликовали очень похожий вопрос ( обнаружение многоцветного документа с помощью OpenCV4Android), я позволил себе использовать размещенное там изображение, а также то, что вы предоставили здесь. Поскольку одно из изображений было доступно только с красной областью интереса, я использовал свой уровень мастерства Photoshop> 9000, чтобы удалить красную область интереса:).
Образцы изображений для фоновой классификации
Образцы изображений для переднего плана классификации
Изображений
Фоновая классификация
Классификация переднего плана
Обнаруженные объекты
Обновить
Быстрый LAB ускоренный курс
Поскольку теория цветовых пространств довольно обширна, вам следует сначала ознакомиться с некоторыми основами и ключевыми моментами. Мой быстрый поиск нашел этот сайт, который хорошо объясняет некоторые важные моменты: http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/ - Мы будем использовать плавающий вариант OpenCV, так как он самый простой использовать (неизмененный диапазон LAB, без масштабирования, без сглаживания и т. д.). - Диапазон значений LAB: ось L* (яркость) находится в диапазоне от 0 до 100, а ось b* (атрибуты цвета) варьируется от -128 до +127. Источники и ссылки. Каковы диапазоны координат в цветовом пространстве CIELAB? http://www.colourphil.co.uk/lab_lch_colour_space.shtml
Цветовое расстояние
https://en.wikipedia.org/wiki/Color_difference
По сути, мы используем евклидово расстояние между двумя цветами. Конечно, мы можем опустить компоненты из двух сравниваемых цветов, например, компонент яркости (L).
Чтобы получить интуитивно понятную метрику цветового расстояния, мы можем просто нормализовать цветовые расстояния в диапазоне от 0,0 до 1,0. Таким образом, мы можем интерпретировать цветовые расстояния как отклонение в процентах.
пример
Давайте использовать изображения со страницы учебника, размещенной выше, и использовать их в качестве примера. В примере приложения показано следующее: - преобразование BGR в LAB - (L) расчет расстояния AB - (L) нормализация расстояния AB - классификация цветов в соответствии со значениями BGR/LAB и порогом цветового расстояния - как цвета объектов могут изменяться при различной освещенности условия - как расстояния до других цветов могут увеличиваться / закрываться темнее / светлее становится изображение (это также становится ясно, если вы внимательно прочитаете размещенную ссылку).
Дополнительный совет: пример должен показать, что одного цвета часто недостаточно для обнаружения цветных объектов в сильно меняющихся условиях освещения. Решением может быть использование различных порогов цветового расстояния для каждого цвета путем эмпирического анализа. Альтернативой является использование множества образцов классификации цветов для каждого цвета, который вы хотите найти. Вам нужно будет рассчитать цветовое расстояние до каждого из этих образцов цветов и объединить найденные значения, ИЛИ результаты.
Код и изображения
(изображения взяты с http://www.learnopencv.com/color-spaces-in-opencv-cpp-python/ - учебник Сатья Маллик)
#include <opencv2/opencv.hpp>
// Normalization factors for (L)AB distance calculation
// LAB range:
// L: 0.0 - 100.0
// A: -128.0 - 127.0
// B: -128.0 - 127.0
static const float labNormalizationFactor = (float)(1.f / (std::sqrt(std::pow(100, 2) + std::pow(255, 2) + std::pow(255, 2))));
static const float abNormalizationFactor = (float)(1.f / (std::sqrt(std::pow(255, 2) + std::pow(255, 2))));
float labExample_calculateLabDistance(const cv::Vec3f& c1, const cv::Vec3f& c2)
{
return (float)cv::norm(c1, c2) * labNormalizationFactor;
}
float labExample_calculateAbDistance(const cv::Vec3f& c1, const cv::Vec3f& c2)
{
cv::Vec2f c1Temp(c1(1), c1(2));
cv::Vec2f c2Temp(c2(1), c2(2));
return (float)cv::norm(c1Temp, c2Temp) * abNormalizationFactor;
}
void labExample_calculateLabDistance(
cv::Mat& imgLabFloat,
cv::Mat& distances,
const cv::Vec3f labColor,
const bool useOnlyAbDistance
)
{
// Get size for general usage
const auto& size = imgLabFloat.size();
distances = cv::Mat::zeros(size, CV_32F);
distances = 1.f;
for (int y = 0; y < size.height; ++y)
{
for (int x = 0; x < size.width; ++x)
{
// Read LAB value
const auto& value = imgLabFloat.at<cv::Vec3f>(y,x);
// Calculate distance
float distanceValue;
if (useOnlyAbDistance)
{
distanceValue = labExample_calculateAbDistance(value, labColor);
}
else
{
distanceValue = labExample_calculateLabDistance(value, labColor);
}
distances.at<float>(y,x) = distanceValue;
}
}
}
// Small hacky function to convert a single
// BGR color value to LAB float.
// Since the conversion function is not directly available
// we just use a Mat object to do the conversion.
cv::Vec3f labExample_bgrUchar2LabFloat(const cv::Scalar bgr)
{
// Build Mat with single bgr pixel
cv::Mat matWithSinglePixel = cv::Mat::zeros(1, 1, CV_8UC3);
matWithSinglePixel.setTo(bgr);
// Convert to float and scale accordingly
matWithSinglePixel.convertTo(matWithSinglePixel, CV_32FC3, 1.0 / 255.0);
// Convert to LAB and return value
cv::cvtColor(matWithSinglePixel, matWithSinglePixel, CV_BGR2Lab);
auto retval = matWithSinglePixel.at<cv::Vec3f>(0, 0);
return retval;
}
void labExample_convertImageBgrUcharToLabFloat(cv::Mat& src, cv::Mat& dst)
{
src.convertTo(dst, CV_32FC3, 1.0 / 255.0);
cv::cvtColor(dst, dst, CV_BGR2Lab);
}
void labExample()
{
// Load image
std::string path = "./Testdata/Stackru lab example/";
std::string filename1 = "1.jpg";
std::string fqn1 = path + filename1;
cv::Mat img1 = cv::imread(fqn1, cv::IMREAD_COLOR);
std::string filename2 = "2.jpg";
std::string fqn2 = path + filename2;
cv::Mat img2 = cv::imread(fqn2, cv::IMREAD_COLOR);
// Combine images by scaling the second image so both images have the same number of columns and then combining them.
float scalingFactorX = (float)img1.cols / img2.cols;
float scalingFactorY = scalingFactorX;
cv::resize(img2, img2, cv::Size(), scalingFactorX, scalingFactorY);
std::vector<cv::Mat> mats;
mats.push_back(img1);
mats.push_back(img2);
cv::Mat img;
cv::vconcat(mats, img);
// Lets use some reference colors.
// Remember: OpenCV uses BGR as default color space so all colors
// are BGR by default, too.
cv::Scalar bgrColorRed(52, 42, 172);
cv::Scalar bgrColorOrange(3, 111, 219);
cv::Scalar bgrColorYellow(1, 213, 224);
cv::Scalar bgrColorBlue(187, 95, 0);
cv::Scalar bgrColorGray(127, 127, 127);
// Build LAB image
cv::Mat imgLabFloat;
labExample_convertImageBgrUcharToLabFloat(img, imgLabFloat);
// Convert bgr ref color to lab float.
// INSERT color you want to analyze here:
auto colorLabFloat = labExample_bgrUchar2LabFloat(bgrColorRed);
cv::Mat colorDistancesWithL;
cv::Mat colorDistancesWithoutL;
labExample_calculateLabDistance(imgLabFloat, colorDistancesWithL, colorLabFloat, false);
labExample_calculateLabDistance(imgLabFloat, colorDistancesWithoutL, colorLabFloat, true);
// Color distances. They can differ for every color being analyzed.
float maxColorDistanceWithL = 0.07f;
float maxColorDistanceWithoutL = 0.07f;
cv::Mat detectedValuesWithL = colorDistancesWithL <= maxColorDistanceWithL;
cv::Mat detectedValuesWithoutL = colorDistancesWithoutL <= maxColorDistanceWithoutL;
cv::Mat imgWithDetectedValuesWithL = cv::Mat::zeros(img.size(), CV_8UC3);
cv::Mat imgWithDetectedValuesWithoutL = cv::Mat::zeros(img.size(), CV_8UC3);
img.copyTo(imgWithDetectedValuesWithL, detectedValuesWithL);
img.copyTo(imgWithDetectedValuesWithoutL, detectedValuesWithoutL);
cv::imshow("img", img);
cv::imshow("colorDistancesWithL", colorDistancesWithL);
cv::imshow("colorDistancesWithoutL", colorDistancesWithoutL);
cv::imshow("detectedValuesWithL", detectedValuesWithL);
cv::imshow("detectedValuesWithoutL", detectedValuesWithoutL);
cv::imshow("imgWithDetectedValuesWithL", imgWithDetectedValuesWithL);
cv::imshow("imgWithDetectedValuesWithoutL", imgWithDetectedValuesWithoutL);
cv::waitKey();
}
int main(int argc, char** argv)
{
labExample();
}