OpenCV中的HAL方法調用流程分析 在OpenCV中有一些所謂HAL(Hardware Acceleration Layer)實現,看名字好像和硬體相關,其實也不盡然,可以理解為比常規的OCV實現更快的版本就好了。此文要做的就是要找到其實現或者切入流程,打通整個函數調用邏輯。本文將以 和`Gau ...
OpenCV中的HAL方法調用流程分析
在OpenCV中有一些所謂HAL(Hardware Acceleration Layer)實現,看名字好像和硬體相關,其實也不盡然,可以理解為比常規的OCV實現更快的版本就好了。此文要做的就是要找到其實現或者切入流程,打通整個函數調用邏輯。本文將以resize
和GaussianBlur
兩個函數來分析。
resize
首先定位到imgproc模塊的imgproc.hpp文件,找到其中的CV_EXPORTS_W void resize( InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR );
方法。因為我們在外部使用的時候都是引用頭文件來使用,也就是頭文件的函數是我們使用的入口函數。而OCV實現會有許多的分支,一下難以確定,所以我們從入口來找是比較方便的。然後跳轉到該函數的實現,如果IDE不支持,可以在對應的resize.cpp搜索有相同的函數聲明函數就是對應函數的實現,如下:
void cv::resize( InputArray _src, OutputArray _dst, Size dsize,
double inv_scale_x, double inv_scale_y, int interpolation )
{
CV_INSTRUMENT_REGION();
Size ssize = _src.size();
CV_Assert( !ssize.empty() );
if( dsize.empty() )
{
CV_Assert(inv_scale_x > 0); CV_Assert(inv_scale_y > 0);
dsize = Size(saturate_cast<int>(ssize.width*inv_scale_x),
saturate_cast<int>(ssize.height*inv_scale_y));
CV_Assert( !dsize.empty() );
}
else
{
inv_scale_x = (double)dsize.width/ssize.width;
inv_scale_y = (double)dsize.height/ssize.height;
CV_Assert(inv_scale_x > 0); CV_Assert(inv_scale_y > 0);
}
if (interpolation == INTER_LINEAR_EXACT && (_src.depth() == CV_32F || _src.depth() == CV_64F))
interpolation = INTER_LINEAR; // If depth isn't supported fallback to generic resize
CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat() && _src.cols() > 10 && _src.rows() > 10,
ocl_resize(_src, _dst, dsize, inv_scale_x, inv_scale_y, interpolation))
Mat src = _src.getMat();
_dst.create(dsize, src.type());
Mat dst = _dst.getMat();
if (dsize == ssize)
{
// Source and destination are of same size. Use simple copy.
src.copyTo(dst);
return;
}
hal::resize(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, inv_scale_x, inv_scale_y, interpolation);
}
我們看到該函數實現做了三件事:
- 參數檢查
- 檢測是否有OpenCL支持或啟用
- 使用hal空間的resize函數來實現
跳轉到hal的實現,同樣位於resize.cpp,部分代碼:
namespace hal {
void resize(int src_type,
const uchar * src_data, size_t src_step, int src_width, int src_height,
uchar * dst_data, size_t dst_step, int dst_width, int dst_height,
double inv_scale_x, double inv_scale_y, int interpolation)
{
CV_INSTRUMENT_REGION();
CV_Assert((dst_width > 0 && dst_height > 0) || (inv_scale_x > 0 && inv_scale_y > 0));
if (inv_scale_x < DBL_EPSILON || inv_scale_y < DBL_EPSILON)
{
inv_scale_x = static_cast<double>(dst_width) / src_width;
inv_scale_y = static_cast<double>(dst_height) / src_height;
}
CALL_HAL(resize, cv_hal_resize, src_type, src_data, src_step, src_width, src_height, dst_data, dst_step, dst_width, dst_height, inv_scale_x, inv_scale_y, interpolation);
//剩下部分代碼是常規實現
然後我們就看到這裡有CALL_HAL
這樣一個巨集,跳轉到其實現,位於hal_replacement.hpp,
#define CALL_HAL(name, fun, ...) \
int res = __CV_EXPAND(fun(__VA_ARGS__)); \
if (res == CV_HAL_ERROR_OK) \
return; \
else if (res != CV_HAL_ERROR_NOT_IMPLEMENTED) \
CV_Error_(cv::Error::StsInternal, \
("HAL implementation " CVAUX_STR(name) " ==> " CVAUX_STR(fun) " returned %d (0x%08x)", res, res));
我們可以看到,它實際上調用了fun
函數,如果該函數返回CV_HAL_ERROR_OK
,那麼就會return
,顯然hal::resize
也會返回;否則,會調用CV_Error_
,這個並不會讓函數結束或者和程式異常一樣直接終止整個函數,以後再細講。反正其結果就是會讓hal::resize
繼續往下執行,下麵就是常規的實現,並不會在此巨集里就return
。
然後我們在hal_replacement.hpp找到cv_hal_resize
的定義為
#define cv_hal_resize hal_ni_resize
然後繼續找到hal_ni_resize
的實現為
inline int hal_ni_resize(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, int dst_height, double inv_scale_x, double inv_scale_y, int interpolation) { return CV_HAL_ERROR_NOT_IMPLEMENTED; }
到這裡,我們發現該函數直接返回CV_HAL_ERROR_NOT_IMPLEMENTED
,按照上面的分析,hal::resize
繼續往下執行。那麼,hal的實現是怎麼切入進來的呢?
我們發現,hal_replacement.hpp中的CALL_HAL
巨集上有一句#include "custom_hal.hpp"
,好奇怪,include不一般都放在開頭嘛?然後我們看下這個custom_hal.cpp,發現它只有一句#include "carotene/tegra_hal.hpp"
,我們繼續跟蹤下去。因為前面分析的函數為hal_ni_resize
,直接findhal_ni_resize
,沒有結果。然後我們findcv_hal_resize
,發現有:
#undef cv_hal_resize
#define cv_hal_resize TEGRA_RESIZE
頓時就感覺快打通了,這裡竟然把cv_hal_resize
給undef掉了,我們知道在hal_replacement.hpp中是#define cv_hal_resize hal_ni_resize
的,並且從文件的位置來看,這個def就會被undef掉,然後重新定義為TEGRA_RESIZE
,find它,發現其定義:
#define TEGRA_RESIZE(src_type, src_data, src_step, src_width, src_height, dst_data, dst_step, dst_width, dst_height, inv_scale_x, inv_scale_y, interpolation) \
( \
interpolation == CV_HAL_INTER_LINEAR ? \
CV_MAT_DEPTH(src_type) == CV_8U && CAROTENE_NS::isResizeLinearOpenCVSupported(CAROTENE_NS::Size2D(src_width, src_height), CAROTENE_NS::Size2D(dst_width, dst_height), ((src_type >> CV_CN_SHIFT) + 1)) && \
inv_scale_x > 0 && inv_scale_y > 0 && \
(dst_width - 0.5)/inv_scale_x - 0.5 < src_width && (dst_height - 0.5)/inv_scale_y - 0.5 < src_height && \
(dst_width + 0.5)/inv_scale_x + 0.5 >= src_width && (dst_height + 0.5)/inv_scale_y + 0.5 >= src_height && \
std::abs(dst_width / inv_scale_x - src_width) < 0.1 && std::abs(dst_height / inv_scale_y - src_height) < 0.1 ? \
CAROTENE_NS::resizeLinearOpenCV(CAROTENE_NS::Size2D(src_width, src_height), CAROTENE_NS::Size2D(dst_width, dst_height), \
src_data, src_step, dst_data, dst_step, 1.0/inv_scale_x, 1.0/inv_scale_y, ((src_type >> CV_CN_SHIFT) + 1)), \
CV_HAL_ERROR_OK : CV_HAL_ERROR_NOT_IMPLEMENTED : \
interpolation == CV_HAL_INTER_AREA ? \
CV_MAT_DEPTH(src_type) == CV_8U && CAROTENE_NS::isResizeAreaSupported(1.0/inv_scale_x, 1.0/inv_scale_y, ((src_type >> CV_CN_SHIFT) + 1)) && \
std::abs(dst_width / inv_scale_x - src_width) < 0.1 && std::abs(dst_height / inv_scale_y - src_height) < 0.1 ? \
CAROTENE_NS::resizeAreaOpenCV(CAROTENE_NS::Size2D(src_width, src_height), CAROTENE_NS::Size2D(dst_width, dst_height), \
src_data, src_step, dst_data, dst_step, 1.0/inv_scale_x, 1.0/inv_scale_y, ((src_type >> CV_CN_SHIFT) + 1)), \
CV_HAL_ERROR_OK : CV_HAL_ERROR_NOT_IMPLEMENTED : \
/*nearest neighbour interpolation disabled due to rounding accuracy issues*/ \
/*interpolation == CV_HAL_INTER_NEAREST ? \
(src_type == CV_8UC1 || src_type == CV_8SC1) && CAROTENE_NS::isResizeNearestNeighborSupported(CAROTENE_NS::Size2D(src_width, src_height), 1) ? \
CAROTENE_NS::resizeNearestNeighbor(CAROTENE_NS::Size2D(src_width, src_height), CAROTENE_NS::Size2D(dst_width, dst_height), \
src_data, src_step, dst_data, dst_step, 1.0/inv_scale_x, 1.0/inv_scale_y, 1), \
CV_HAL_ERROR_OK : \
(src_type == CV_8UC3 || src_type == CV_8SC3) && CAROTENE_NS::isResizeNearestNeighborSupported(CAROTENE_NS::Size2D(src_width, src_height), 3) ? \
CAROTENE_NS::resizeNearestNeighbor(CAROTENE_NS::Size2D(src_width, src_height), CAROTENE_NS::Size2D(dst_width, dst_height), \
src_data, src_step, dst_data, dst_step, 1.0/inv_scale_x, 1.0/inv_scale_y, 3), \
CV_HAL_ERROR_OK : \
(src_type == CV_8UC4 || src_type == CV_8SC4 || src_type == CV_16UC2 || src_type == CV_16SC2 || src_type == CV_32SC1) && \
CAROTENE_NS::isResizeNearestNeighborSupported(CAROTENE_NS::Size2D(src_width, src_height), 4) ? \
CAROTENE_NS::resizeNearestNeighbor(CAROTENE_NS::Size2D(src_width, src_height), CAROTENE_NS::Size2D(dst_width, dst_height), \
src_data, src_step, dst_data, dst_step, 1.0/inv_scale_x, 1.0/inv_scale_y, 4), \
CV_HAL_ERROR_OK : CV_HAL_ERROR_NOT_IMPLEMENTED :*/ \
CV_HAL_ERROR_NOT_IMPLEMENTED \
)
這個巨集的定義大概做了這些事情:
- 如果是雙線性插值,為CV_8U數據類型,且尺寸以及通道滿足一定的要求,那麼就
resizeLinearOpenCV
去真正實現resize,且返回CV_HAL_ERROR_OK
,不滿足這些條件的雙線性插值就不支持,返回CV_HAL_ERROR_NOT_IMPLEMENTED
,這樣,就會走hal::resize
的普通實現 - 如果是AREA插值,情況也是和雙線性插值類似
- 其他插值方式則目前不支持。但是從註釋的這些代碼來看,應該是計劃支持的,只是現在還沒做好而已。
這個巨集的定義用到了不常註意的逗號運算符來實現CV_HAL_ERROR_OK
值的返回,逗號運算符返回的是其最右邊的值。
然後我們就可以跳轉到resizeLinearOpenCV
以及resizeAreaOpenCV
來追蹤真正的快速實現方法了。
可以發現,切入的關鍵就在於那個undef和define操作了。
GaussianBlur
同樣,我們在smooth.cpp中找到cv_hal_gaussianBlur
方法的實現,發現其hal巨集為cv_hal_gaussianBlur
,然後到tegra_hal.hpp
中findcv_hal_gaussianBlur
,發現沒有結果。這說明高斯模糊沒有對應的hal快速版本。然後發現carotene庫中有高斯模糊相關的代碼,看起來應該有實現?我們通過寫demo以及在源碼中打log的方式,發現這些實現函數確實沒有被調用,都是在CALL_HAL
巨集那裡就返回CV_HAL_ERROR_NOT_IMPLEMENTED
了。應該是這些實現還不夠好,比如我發現已有的一些代碼還沒有考慮到高斯核繫數,所以沒有切入進去,慢慢等待吧。