Улучшение алгоритма распознавания банки Coca-Cola на изображении
Проблема и основной подход
Ваш текущий метод основан исключительно на цвете и грубой форме (эллипс). Это классический случай, когда "наивный" подход пасует перед реальными условиями: блики, тени, сложный фон. Чтобы кардинально улучшить распознавание, нужно комбинировать несколько признаков и использовать более robust методы. Я предлагаю трёхэтапный подход: предобработка для стабилизации цвета, выделение характерных геометрических признаков банки (прямоугольник с закруглёнными углами + белая полоса) и финальная верификация через отношение площадей.
Шаг 1: Улучшенная цветовая сегментация
Проблема бликов решается не просто расширением диапазона HSV, а адаптивной пороговой обработкой и работой в пространстве LAB. LAB лучше отделяет цвет от яркости. Красный цвет банки имеет стабильные координаты a* и b* даже при изменении освещения.
cv::Mat lab;
cv::cvtColor(img, lab, cv::COLOR_BGR2Lab);
// Извлекаем каналы a и b (красно-зеленый и желто-синий)
std::vector<cv::Mat> lab_channels;
cv::split(lab, lab_channels);
// Красный цвет в LAB: a* > 140, b* > 130 (примерные значения, нужно калибровать)
cv::Mat red_mask;
cv::inRange(lab_channels[1], cv::Scalar(140), cv::Scalar(255), red_mask);
cv::inRange(lab_channels[2], cv::Scalar(130), cv::Scalar(255), red_mask); // комбинируем
// Убираем шум морфологией
cv::morphologyEx(red_mask, red_mask, cv::MORPH_CLOSE, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5)));
Шаг 2: Геометрическая модель банки
Банка Coca-Cola — это не просто эллипс. Её профиль — прямоугольник с сильно скруглёнными углами, а на корпусе есть характерная белая волнистая лента. Мы можем искать пару "красный прямоугольник + белая полоса внутри".
// После нахождения контуров на red_mask
for (auto& c : contours) {
double area = cv::contourArea(c);
if (area < 1000) continue; // отсекаем мелкий мусор
cv::RotatedRect rect = cv::minAreaRect(c);
// Проверяем, что прямоугольник достаточно "прямоугольный" (отношение площади контура к площади описанного прямоугольника)
float rect_area = rect.size.width * rect.size.height;
float solidity = area / rect_area;
if (solidity < 0.7) continue; // слишком изрезанный контур
// Проверяем соотношение сторон (банка ~ 2:1)
float aspect = rect.size.width / rect.size.height;
if (aspect < 1.2 || aspect > 2.2) continue; // не похоже на банку
// Теперь ищем белую полосу внутри этого прямоугольника
cv::Mat roi = img(rect.boundingRect());
cv::Mat white_mask;
cv::inRange(roi, cv::Scalar(200, 200, 200), cv::Scalar(255, 255, 255), white_mask);
int white_pixels = cv::countNonZero(white_mask);
float white_ratio = (float)white_pixels / (rect_area);
if (white_ratio > 0.05 && white_ratio < 0.3) {
// Высокая вероятность, что это банка
cv::drawContours(img, contours, i, cv::Scalar(0, 255, 0), 2);
}
}
Шаг 3: Финальная верификация через HOG + SVM (опционально)
Если ложных срабатываний всё ещё много, используйте дескриптор HOG и обученный классификатор SVM. Это даст точность >95%. Вот минимальный пример загрузки предобученной модели:
cv::HOGDescriptor hog;
hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector()); // для примера, нужно обучить свой
std::vector<cv::Rect> found;
hog.detectMultiScale(roi, found, 0, cv::Size(8,8), cv::Size(32,32), 1.05, 2);
if (found.size() > 0) { /* банка подтверждена */ }
Советы по улучшению
- Используйте калибровку цвета под ваше конкретное освещение. Сделайте 10-20 фото банки в разных условиях и соберите статистику по каналам LAB.
- Добавьте проверку текстуры: банка имеет характерный металлический блеск (высокий градиент в одном направлении). Можно использовать фильтр Габора.
- Не забывайте про масштабирование: если банка слишком маленькая или большая, отбрасывайте кандидатов по площади.
- Для production используйте YOLO или MobileNet, обученный на вашем датасете. Это решит 99% проблем.
Ваш текущий код — хорошая отправная точка, но для реального применения нужно добавить хотя бы два-три дополнительных признака, как я показал выше.
<<Альтернативный подход: использование SIFT и дескрипторов ключевых точек
Если вы не хотите возиться с геометрическими моделями и цветовой калибровкой, попробуйте метод на основе поиска характерных точек (feature matching). Банка Coca-Cola имеет уникальный логотип и шрифт, которые можно использовать как "отпечаток пальца".
cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
std::vector<cv::KeyPoint> kp1, kp2;
cv::Mat desc1, desc2;
sift->detectAndCompute(reference_image, cv::noArray(), kp1, desc1);
sift->detectAndCompute(query_image, cv::noArray(), kp2, desc2);
cv::BFMatcher matcher(cv::NORM_L2);
std::vector<cv::DMatch> matches;
matcher.match(desc1, desc2, matches);
// Фильтруем хорошие совпадения (расстояние < 0.75 * max)
double max_dist = 0;
for (auto& m : matches) max_dist = std::max(max_dist, (double)m.distance);
std::vector<cv::DMatch> good_matches;
for (auto& m : matches) {
if (m.distance < 0.75 * max_dist) good_matches.push_back(m);
}
if (good_matches.size() > 20) {
std::cout << "Банка найдена!" << std::endl;
}
Плюсы: не зависит от цвета, устойчив к поворотам и частичным перекрытиям. Минусы: требует эталонного изображения бан
Ответов пока нет.
Войдите чтобы оставить ответ