除了 Haar 级联之外,还有哪些算法或方法可用于自定义对象检测?
What algorithms or approaches apart from Haar cascades could be used for custom objects detection?
我需要完成计算机视觉任务才能检测水瓶或汽水罐。我将获取 'frontal' 个瓶子、汽水罐或任何其他随机物体的图像(一个接一个),我的算法应该 确定它是瓶子、罐子还是其中任何一个 .
对象检测场景的一些细节:
- 如前所述,我将每 image/video 帧测试一个对象。
- 并不是所有的水壶都是一样的。塑料、盖子或标签可能有颜色差异。 可能有些人找不到标签或盖子。
- 汽水罐也有同样的变化。不过,不会对起皱的汽水罐进行测试。
- 对象之间的尺寸差异可能很小。
- 我可以使用绿色(或任何自定义颜色)背景。
- 我会在图片上做任何需要的滤镜。
- 这将是 运行 Raspberry Pi。
以防万一,每个示例:
我已经测试了几次 OpenCV 人脸检测算法,我知道它工作得很好,但我需要获得一个特殊的 Haar Cascades 特征 XML 文件来检测每个自定义对象的这种方法。
因此,我想到的不同替代方案是:
- Creating a custom Haar Classifier.
- Considering shapes.
- 考虑大纲。
我想要一个简单的算法,我认为甚至不需要创建自定义 Haar 分类器。你有什么建议?
更新
我强烈考虑了 shape/aspect 比率方法。
但是我想我遇到了一些问题,因为瓶子有不同的尺寸甚至形状。 但是这让我想到或设置了以下注意事项:
- 我正在使用 THRESH_BINARY 方法应用阈值。 (感谢答案)。
- 我会在检测时使用白色背景。
- 汽水罐都是一样大的。
- 因此,高精度的汽水罐边界框可能会区分罐头。
我取得的成就:
阈值真的帮助了我,我注意到在白色背景测试中我会获得罐头:
这就是它获得的瓶子:
因此,较暗的区域占据主导地位是显而易见的。在某些情况下,罐头可能会变成假阴性。对于瓶子,光线和角度可能会导致结果不一致,但我真的认为这可能是一种更短的方法。
所以,我现在很困惑我应该如何评估 黑暗 主导地位,我读过 findContours
导致它,但我很迷茫关于如何抓住这样的功能。例如,对于汽水罐,它可能会找到多个轮廓,所以我不知道要评估什么。
注意:我愿意测试与 Open CV 不同的任何其他算法或库。
我在这里看到了一些基本的想法:
- 检查对象(准确地说 - object boundind rect)width/height 比率。对于罐头,它大约是 2-2.5,对于瓶子,我认为它会 >3。这是一个非常简单的想法,它应该很容易快速测试,我认为它应该具有很好的准确性。对于某些值,例如 2.75(假设我给出的值是正确的,这很可能不是真的),您可以使用一些不同的算法。
- 检查您的对象是否包含 glass/transparence 个区域 - 如果是,那么它肯定是一个瓶子。 Here 您可以阅读更多相关信息。
- 使用抓取算法获取对象 mask/more 精确形状并检查此形状顶部宽度是否与底部宽度相似 - 如果是,则为罐头,否 - 瓶子(瓶子有螺旋盖在顶部)。
由于您想识别罐头与瓶子而不是百事可乐与可乐,与 Haar 和像 SIFT/SURF/ORB
这样的 features2d 匹配器相比,形状匹配可能是可行的方法
独特的背景颜色会让事情变得更简单。
首先根据仅包含背景的图像创建直方图
int channels[] = {0,1,2}; // use all the channels
int rgb_bins = 32; // quantize to 32 colors per channel
int histSize[] = {rgb_bins, rgb_bins, rgb_bins};
float _range[] = {0,255};
float* ranges[] = {_range, _range, _range};
cv::SparseMat bghist;
cv::calcHist(&bg_image, 1, channels, cv::noArray(),bghist, 3, histSize, ranges );
然后使用 calcBackProject 创建 bg 而不是 bg 的掩码
cv::MatND temp_ND;
cv::calcBackProject( &bottle_image, 1, channels, bghist, temp_ND, ranges );
cv::Mat bottle_mask, bottle_backproj;
if( feeling_lazy ){
cv::normalize(temp_ND, bottle_backproj, 0, 255, cv::NORM_MINMAX, CV_8U);
//a small blur here could work nicely
threshold( bottle_backproj, bottle_mask, 0, 255, THRESH_OTSU );
bottle_mask = cv::Scalar(255) - bottle_mask; //invert the mask
} else {
//finding just the right value here might be better than the above method
int magic_threshold = 64;
temp_ND.convertTo( bottle_backproj, CV_8U, 255.);
//I expect temp_ND to be CV_32F ranging from 0-1, but I might be wrong.
threshold( bottle_backproj, bottle_mask, magic_threshold, 255, THRESH_BINARY_INV );
}
然后:
将bottle_mask或bottle_backproj与几个样品瓶masks/backprojections进行比较,使用matchTemplate和置信度阈值来确定它是否匹配。
matchTemplate(bottle_mask, bottle_template, result, CV_TM_CCORR_NORMED);
double confidence; minMaxLoc( result, NULL, &confidence);
或者使用 matchShapes,虽然我从来没有让它正常工作。
double confidence = matchShapes(bottle_mask, bottle_template, CV_CONTOURS_MATCH_I3);
或使用 linemod,它很难设置,但对于像这样形状不是很复杂的图像效果很好。除了链接文件之外,我还没有找到此方法的任何工作示例,所以这就是我所做的。
首先create/train带有一些样本图像的检测器
//some magic numbers
std::vector<int> T_at_level;
T_at_level.push_back(4);
T_at_level.push_back(8);
//add some padding so linemod doesn't scream at you
const int T = 32;
int width = bottle_mask.cols;
if( width % T != 0)
width += T - width % T;
int height = bottle_mask.rows;
if( height % T != 0)
height += T - height % T;
//in this case template_backproj is created specifically from a sample bottle_backproj
cv::Rect padded_roi( (width - template_backproj.cols)/2, (height - template_backproj.rows)/2, template_backproj.cols, template_backproj.rows);
cv::Mat padded_backproj = zeros( width, height, template_backproj.type());
padded_backproj( padded_roi ) = template_backproj;
cv::Mat padded_mask = zeros( width, height, template_mask.type());
padded_mask( padded_roi ) = template_mask;
//you might need to erode padded_mask by a few pixels.
//initialize detector
std::vector< cv::Ptr<cv::linemod::Modality> > modalities;
modalities.push_back( cv::makePtr<cv::linemod::ColorGradient>() ); //for those that don't have a kinect
cv::Ptr<cv::linemod::Detector> new_detector = cv::makePtr<cv::linemod::Detector>(modalities, T_at_level);
//add sample images to the detector
std::vector<cv::Mat> template_images;
templates.push_back( padded_backproj);
cv::Rect ignore_me;
const std::string class_id = "bottle";
template_id = new_detector->addTemplate(template_images, class_id, padded_mask, &ignore_me);
然后做一些匹配
std::vector<cv::Mat> sources_vec;
sources_vec.push_back( padded_backproj );
//padded_backproj doesn't need to be the same size as the trained template images, but it does need to be padded the same way.
float matching_threshold = 0.8; //a higher number makes the algorithm faster
std::vector<cv::linemod::Match> matches;
std::vector<cv::String> class_ids;
new_detector->match(sources_vec, matching_threshold, matches,class_ids);
float confidence = matches.size() > 0? matches[0].similarity : 0;
正如 cyriel 所建议的,纵横比 (width/height) 可能是一种有用的衡量标准。这是一些 OpenCV Python 代码,可以找到轮廓(希望包括瓶子或罐头的轮廓)并为您提供纵横比和其他一些测量值:
# src image should have already had some contrast enhancement (such as
# cv2.threshold) and edge finding (such as cv2.Canny)
contours, hierarchy = cv2.findContours(src, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
num_points = len(contour)
if num_points < 5:
# The contour has too few points to fit an ellipse. Skip it.
continue
# We could use area to help determine the type of object.
# Small contours are probably false detections (not really a whole object).
area = cv2.contourArea(contour)
bounding_ellipse = cv2.fitEllipse(contour)
center, radii, angle_degrees = bounding_ellipse
# Let's define an ellipse's normal orientation to be landscape (width > height).
# We must ensure that the ellipse's measurements match this orientation.
if radii[0] < radii[1]:
radii = (radii[1], radii[0])
angle_degrees -= 90.0
# We could use the angle to help determine the type of object.
# A bottle or can's angle is probably approximately a multiple of 90 degrees,
# assuming that it is at rest and not falling.
# Calculate the aspect ratio (width / height).
# For example, 0.5 means the object's height is 2 times its width.
# A bottle is probably taller than a can.
aspect_ratio = radii[0] / radii[1]
要检查透明度,您可以使用直方图分析或背景减法将图片与已知背景进行比较。
轮廓的力矩可用于确定其质心(重心):
moments = cv2.moments(contour)
m00 = moments['m00']
m01 = moments['m01']
m10 = moments['m10']
centroid = (m10 / m00, m01 / m00)
您可以将其与中心进行比较。如果对象的一端较大 ("heavier"),则质心将比中心更靠近该端。
所以,我的主要检测方法是:
Bottles are transparent and cans are opaque
一般算法包括:
Take a grayscale picture.
Apply a binary threshold.
Select a convenient ROI from it.
Obtain it's color mean and even the standard deviation.
Distinguish.
实现基本上简化为这个函数(其中 CAN
和 BOTTLE
是先前定义的):
int detector(int x, int y, int width, int height, int thresholdValue, CvCapture* capture) {
Mat img;
Rect r;
vector<Mat> channels;
r = Rect(x,y,width,height);
if ( !capture ) {
fprintf( stderr, "ERROR: capture is NULL \n" );
getchar();
return -1;
}
img = Mat(cvQueryFrame( capture ));
cvtColor(img,img,CV_RGB2GRAY);
threshold(img, img, 127, 255, THRESH_BINARY);
// ROI
Mat roiImage = img(r);
split(roiImage, channels);
Scalar m = mean(channels[0]);
float media = m[0];
printf("Media: %f\n", media);
if (media < thresholdValue) {
return CAN;
}
else {
return BOTTLE;
}
}
可以看出,应用了 THRESH_BINARY
阈值,使用的是 纯白色背景 。然而,我在整个方法和算法中面临的主要和关键问题是 环境中的光度变化,即使是很小的变化。
有时我会注意到 THRESH_BINARY_INV
可能会有更多帮助,但我想知道我是否可以使用一些 certian 阈值参数,或者应用其他过滤器是否可能导致消除环境闪电问题。
我真的很欣赏 纵横比计算 从边界框或寻找轮廓的方法,但我发现在调整条件时这很简单。
我会使用基于迁移学习的深度学习。
想法是这样的:给定一个高度复杂且训练有素的神经网络,该网络是在类似的分类任务上训练的(通常是在大型 public 数据集上,如 imagenet),您可以冻结其大部分权重,只训练最后一层。那里有很多教程。你不需要有深度学习的背景。
我需要完成计算机视觉任务才能检测水瓶或汽水罐。我将获取 'frontal' 个瓶子、汽水罐或任何其他随机物体的图像(一个接一个),我的算法应该 确定它是瓶子、罐子还是其中任何一个 .
对象检测场景的一些细节:
- 如前所述,我将每 image/video 帧测试一个对象。
- 并不是所有的水壶都是一样的。塑料、盖子或标签可能有颜色差异。 可能有些人找不到标签或盖子。
- 汽水罐也有同样的变化。不过,不会对起皱的汽水罐进行测试。
- 对象之间的尺寸差异可能很小。
- 我可以使用绿色(或任何自定义颜色)背景。
- 我会在图片上做任何需要的滤镜。
- 这将是 运行 Raspberry Pi。
以防万一,每个示例:
我已经测试了几次 OpenCV 人脸检测算法,我知道它工作得很好,但我需要获得一个特殊的 Haar Cascades 特征 XML 文件来检测每个自定义对象的这种方法。
因此,我想到的不同替代方案是:
- Creating a custom Haar Classifier.
- Considering shapes.
- 考虑大纲。
我想要一个简单的算法,我认为甚至不需要创建自定义 Haar 分类器。你有什么建议?
更新
我强烈考虑了 shape/aspect 比率方法。
但是我想我遇到了一些问题,因为瓶子有不同的尺寸甚至形状。 但是这让我想到或设置了以下注意事项:
- 我正在使用 THRESH_BINARY 方法应用阈值。 (感谢答案)。
- 我会在检测时使用白色背景。
- 汽水罐都是一样大的。
- 因此,高精度的汽水罐边界框可能会区分罐头。
我取得的成就:
阈值真的帮助了我,我注意到在白色背景测试中我会获得罐头:
这就是它获得的瓶子:
因此,较暗的区域占据主导地位是显而易见的。在某些情况下,罐头可能会变成假阴性。对于瓶子,光线和角度可能会导致结果不一致,但我真的认为这可能是一种更短的方法。
所以,我现在很困惑我应该如何评估 黑暗 主导地位,我读过 findContours
导致它,但我很迷茫关于如何抓住这样的功能。例如,对于汽水罐,它可能会找到多个轮廓,所以我不知道要评估什么。
注意:我愿意测试与 Open CV 不同的任何其他算法或库。
我在这里看到了一些基本的想法:
- 检查对象(准确地说 - object boundind rect)width/height 比率。对于罐头,它大约是 2-2.5,对于瓶子,我认为它会 >3。这是一个非常简单的想法,它应该很容易快速测试,我认为它应该具有很好的准确性。对于某些值,例如 2.75(假设我给出的值是正确的,这很可能不是真的),您可以使用一些不同的算法。
- 检查您的对象是否包含 glass/transparence 个区域 - 如果是,那么它肯定是一个瓶子。 Here 您可以阅读更多相关信息。
- 使用抓取算法获取对象 mask/more 精确形状并检查此形状顶部宽度是否与底部宽度相似 - 如果是,则为罐头,否 - 瓶子(瓶子有螺旋盖在顶部)。
由于您想识别罐头与瓶子而不是百事可乐与可乐,与 Haar 和像 SIFT/SURF/ORB
这样的 features2d 匹配器相比,形状匹配可能是可行的方法独特的背景颜色会让事情变得更简单。
首先根据仅包含背景的图像创建直方图
int channels[] = {0,1,2}; // use all the channels
int rgb_bins = 32; // quantize to 32 colors per channel
int histSize[] = {rgb_bins, rgb_bins, rgb_bins};
float _range[] = {0,255};
float* ranges[] = {_range, _range, _range};
cv::SparseMat bghist;
cv::calcHist(&bg_image, 1, channels, cv::noArray(),bghist, 3, histSize, ranges );
然后使用 calcBackProject 创建 bg 而不是 bg 的掩码
cv::MatND temp_ND;
cv::calcBackProject( &bottle_image, 1, channels, bghist, temp_ND, ranges );
cv::Mat bottle_mask, bottle_backproj;
if( feeling_lazy ){
cv::normalize(temp_ND, bottle_backproj, 0, 255, cv::NORM_MINMAX, CV_8U);
//a small blur here could work nicely
threshold( bottle_backproj, bottle_mask, 0, 255, THRESH_OTSU );
bottle_mask = cv::Scalar(255) - bottle_mask; //invert the mask
} else {
//finding just the right value here might be better than the above method
int magic_threshold = 64;
temp_ND.convertTo( bottle_backproj, CV_8U, 255.);
//I expect temp_ND to be CV_32F ranging from 0-1, but I might be wrong.
threshold( bottle_backproj, bottle_mask, magic_threshold, 255, THRESH_BINARY_INV );
}
然后:
将bottle_mask或bottle_backproj与几个样品瓶masks/backprojections进行比较,使用matchTemplate和置信度阈值来确定它是否匹配。
matchTemplate(bottle_mask, bottle_template, result, CV_TM_CCORR_NORMED);
double confidence; minMaxLoc( result, NULL, &confidence);
或者使用 matchShapes,虽然我从来没有让它正常工作。
double confidence = matchShapes(bottle_mask, bottle_template, CV_CONTOURS_MATCH_I3);
或使用 linemod,它很难设置,但对于像这样形状不是很复杂的图像效果很好。除了链接文件之外,我还没有找到此方法的任何工作示例,所以这就是我所做的。
首先create/train带有一些样本图像的检测器
//some magic numbers
std::vector<int> T_at_level;
T_at_level.push_back(4);
T_at_level.push_back(8);
//add some padding so linemod doesn't scream at you
const int T = 32;
int width = bottle_mask.cols;
if( width % T != 0)
width += T - width % T;
int height = bottle_mask.rows;
if( height % T != 0)
height += T - height % T;
//in this case template_backproj is created specifically from a sample bottle_backproj
cv::Rect padded_roi( (width - template_backproj.cols)/2, (height - template_backproj.rows)/2, template_backproj.cols, template_backproj.rows);
cv::Mat padded_backproj = zeros( width, height, template_backproj.type());
padded_backproj( padded_roi ) = template_backproj;
cv::Mat padded_mask = zeros( width, height, template_mask.type());
padded_mask( padded_roi ) = template_mask;
//you might need to erode padded_mask by a few pixels.
//initialize detector
std::vector< cv::Ptr<cv::linemod::Modality> > modalities;
modalities.push_back( cv::makePtr<cv::linemod::ColorGradient>() ); //for those that don't have a kinect
cv::Ptr<cv::linemod::Detector> new_detector = cv::makePtr<cv::linemod::Detector>(modalities, T_at_level);
//add sample images to the detector
std::vector<cv::Mat> template_images;
templates.push_back( padded_backproj);
cv::Rect ignore_me;
const std::string class_id = "bottle";
template_id = new_detector->addTemplate(template_images, class_id, padded_mask, &ignore_me);
然后做一些匹配
std::vector<cv::Mat> sources_vec;
sources_vec.push_back( padded_backproj );
//padded_backproj doesn't need to be the same size as the trained template images, but it does need to be padded the same way.
float matching_threshold = 0.8; //a higher number makes the algorithm faster
std::vector<cv::linemod::Match> matches;
std::vector<cv::String> class_ids;
new_detector->match(sources_vec, matching_threshold, matches,class_ids);
float confidence = matches.size() > 0? matches[0].similarity : 0;
正如 cyriel 所建议的,纵横比 (width/height) 可能是一种有用的衡量标准。这是一些 OpenCV Python 代码,可以找到轮廓(希望包括瓶子或罐头的轮廓)并为您提供纵横比和其他一些测量值:
# src image should have already had some contrast enhancement (such as
# cv2.threshold) and edge finding (such as cv2.Canny)
contours, hierarchy = cv2.findContours(src, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
num_points = len(contour)
if num_points < 5:
# The contour has too few points to fit an ellipse. Skip it.
continue
# We could use area to help determine the type of object.
# Small contours are probably false detections (not really a whole object).
area = cv2.contourArea(contour)
bounding_ellipse = cv2.fitEllipse(contour)
center, radii, angle_degrees = bounding_ellipse
# Let's define an ellipse's normal orientation to be landscape (width > height).
# We must ensure that the ellipse's measurements match this orientation.
if radii[0] < radii[1]:
radii = (radii[1], radii[0])
angle_degrees -= 90.0
# We could use the angle to help determine the type of object.
# A bottle or can's angle is probably approximately a multiple of 90 degrees,
# assuming that it is at rest and not falling.
# Calculate the aspect ratio (width / height).
# For example, 0.5 means the object's height is 2 times its width.
# A bottle is probably taller than a can.
aspect_ratio = radii[0] / radii[1]
要检查透明度,您可以使用直方图分析或背景减法将图片与已知背景进行比较。
轮廓的力矩可用于确定其质心(重心):
moments = cv2.moments(contour)
m00 = moments['m00']
m01 = moments['m01']
m10 = moments['m10']
centroid = (m10 / m00, m01 / m00)
您可以将其与中心进行比较。如果对象的一端较大 ("heavier"),则质心将比中心更靠近该端。
所以,我的主要检测方法是:
Bottles are transparent and cans are opaque
一般算法包括:
Take a grayscale picture.
Apply a binary threshold.
Select a convenient ROI from it.
Obtain it's color mean and even the standard deviation.
Distinguish.
实现基本上简化为这个函数(其中 CAN
和 BOTTLE
是先前定义的):
int detector(int x, int y, int width, int height, int thresholdValue, CvCapture* capture) {
Mat img;
Rect r;
vector<Mat> channels;
r = Rect(x,y,width,height);
if ( !capture ) {
fprintf( stderr, "ERROR: capture is NULL \n" );
getchar();
return -1;
}
img = Mat(cvQueryFrame( capture ));
cvtColor(img,img,CV_RGB2GRAY);
threshold(img, img, 127, 255, THRESH_BINARY);
// ROI
Mat roiImage = img(r);
split(roiImage, channels);
Scalar m = mean(channels[0]);
float media = m[0];
printf("Media: %f\n", media);
if (media < thresholdValue) {
return CAN;
}
else {
return BOTTLE;
}
}
可以看出,应用了 THRESH_BINARY
阈值,使用的是 纯白色背景 。然而,我在整个方法和算法中面临的主要和关键问题是 环境中的光度变化,即使是很小的变化。
有时我会注意到 THRESH_BINARY_INV
可能会有更多帮助,但我想知道我是否可以使用一些 certian 阈值参数,或者应用其他过滤器是否可能导致消除环境闪电问题。
我真的很欣赏 纵横比计算 从边界框或寻找轮廓的方法,但我发现在调整条件时这很简单。
我会使用基于迁移学习的深度学习。
想法是这样的:给定一个高度复杂且训练有素的神经网络,该网络是在类似的分类任务上训练的(通常是在大型 public 数据集上,如 imagenet),您可以冻结其大部分权重,只训练最后一层。那里有很多教程。你不需要有深度学习的背景。