C++ SSE均值滤波器,单位为c++;和OpenCV

C++ SSE均值滤波器,单位为c++;和OpenCV,c++,opencv,sse,simd,C++,Opencv,Sse,Simd,我想修改OpenCV均值过滤器的代码以使用Intel Intrinsic。我是SSE新手,我真的不知道从哪里开始。我在网上查阅了很多资源,但没有取得很多成功 以下是节目: #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; using namespace std; int main() { int A[

我想修改OpenCV均值过滤器的代码以使用Intel Intrinsic。我是SSE新手,我真的不知道从哪里开始。我在网上查阅了很多资源,但没有取得很多成功

以下是节目:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

using namespace cv;
using namespace std;

int main()
{
    int A[3][3] = { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } };
    int c = 0;
    int d = 0;
    Mat var1 = imread("images.jpg", 1);
    Mat var2(var1.rows, var1.cols, CV_8UC3, Scalar(0, 0, 0));
    for (int i = 0; i < var1.rows; i++)
    {
        var2.at<Vec3b>(i, 0) = var1.at<Vec3b>(i, 0);
        var2.at<Vec3b>(i, var1.cols - 1) = var1.at<Vec3b>(i, var1.cols - 1);

    }
    for (int i = 0; i < var1.cols; i++)
    {
        var2.at<Vec3b>(0, i) = var1.at<Vec3b>(0, i);
        var2.at<Vec3b>(var1.rows - 1, i) = var1.at<Vec3b>(var1.rows - 1, i);

    }
    for (int i = 0; i < var1.rows; i++) {
        for (int j = 0; j < var1.cols; j++)
        {
            c = 0;
            for (int m = i; m < var1.rows; m++, c++)
            {
                if (c < 3)
                {
                    d = 0;
                    for (int n = j; n < var1.cols; n++, d++)
                    {
                        if (d < 3)
                        {
                            if ((i + 1) < var1.rows && (j + 1) < var1.cols)
                            {
                                var2.at<Vec3b>(i + 1, j + 1)[0] += var1.at<Vec3b>(m, n)[0] * A[m - i][n - j] / 9;
                                var2.at<Vec3b>(i + 1, j + 1)[1] += var1.at<Vec3b>(m, n)[1] * A[m - i][n - j] / 9;
                                var2.at<Vec3b>(i + 1, j + 1)[2] += var1.at<Vec3b>(m, n)[2] * A[m - i][n - j] / 9;
                            }
                        }
                    }
                }
            }
        }
    }
    imshow("window1", var1);
    imshow("window2", var2);
    waitKey(0);
    return(0);
}
 
#包括“opencv2/imgproc/imgproc.hpp”
#包括“opencv2/highgui/highgui.hpp”
使用名称空间cv;
使用名称空间std;
int main()
{
inta[3][3]={{1,1,1},{1,1,1},{1,1,1};
int c=0;
int d=0;
Mat var1=imread(“images.jpg”,1);
Mat var2(var1.rows,var1.cols,CV_8UC3,标量(0,0,0));
for(int i=0;i

我发现困难的部分是理解如何转换最里面的2个循环,其中平均值是计算出来的。任何帮助都将不胜感激。

只是为了好玩,我认为从一个3x3平均值过滤器的简单实现开始,然后逐步优化它,最后是一个SIMD(SSE)实现,测量每个阶段的吞吐量改进可能会很有趣

1-
Mean\u 3\u ref
-参考实施 这只是一个简单的标量实现,我们将使用它作为吞吐量和验证进一步实现的基准:

void Mean_3_3_ref(const Mat &image_in, Mat &image_out)
{
    for (int y = 1; y < image_in.rows - 1; ++y)
    {
        for (int x = 1; x < image_in.cols - 1; ++x)
        {
            for (int c = 0; c < 3; ++c)
            {
                image_out.at<Vec3b>(y, x)[c] = (image_in.at<Vec3b>(y - 1, x - 1)[c] +
                                                image_in.at<Vec3b>(y - 1, x    )[c] +
                                                image_in.at<Vec3b>(y - 1, x + 1)[c] +
                                                image_in.at<Vec3b>(y    , x - 1)[c] +
                                                image_in.at<Vec3b>(y    , x    )[c] +
                                                image_in.at<Vec3b>(y    , x + 1)[c] +
                                                image_in.at<Vec3b>(y + 1, x - 1)[c] +
                                                image_in.at<Vec3b>(y + 1, x    )[c] +
                                                image_in.at<Vec3b>(y + 1, x + 1)[c] + 4) / 9;
            }
        }
    }
}
3-
Mean\u 3\u scalar\u opt
-进一步优化了scalar实现 根据
Mean_3_3_scalar
,还可以通过缓存指向我们正在处理的每一行的指针来消除OpenCV开销:

void Mean_3_3_scalar_opt(const Mat &image_in, Mat &image_out)
{
    for (int y = 1; y < image_in.rows - 1; ++y)
    {
        const uint8_t * const input_1 = image_in.ptr(y - 1);
        const uint8_t * const input0 = image_in.ptr(y);
        const uint8_t * const input1 = image_in.ptr(y + 1);
        uint8_t * const output = image_out.ptr(y);

        int r_1 = input_1[0] + input0[0] + input1[0];
        int g_1 = input_1[1] + input0[1] + input1[1];
        int b_1 = input_1[2] + input0[2] + input1[2];
        int r0 = input_1[3] + input0[3] + input1[3];
        int g0 = input_1[4] + input0[4] + input1[4];
        int b0 = input_1[5] + input0[5] + input1[5];

        for (int x = 1; x < image_in.cols - 1; ++x)
        {
            int r1 = input_1[x * 3 + 3] + input0[x * 3 + 3] + input1[x * 3 + 3];
            int g1 = input_1[x * 3 + 4] + input0[x * 3 + 4] + input1[x * 3 + 4];
            int b1 = input_1[x * 3 + 5] + input0[x * 3 + 5] + input1[x * 3 + 5];

            output[x * 3    ] = (r_1 + r0 + r1 + 4) / 9;
            output[x * 3 + 1] = (g_1 + g0 + g1 + 4) / 9;
            output[x * 3 + 2] = (b_1 + b0 + b1 + 4) / 9;

            r_1 = r0;
            g_1 = g0;
            b_1 = b0;
            r0 = r1;
            g0 = g1;
            b0 = b1;
        }
    }
}
5-
Mean_3_3_SSE
-SSE实施 这是一个相当有效的SIMD实现。它使用与上述标量代码相同的技术,以消除处理连续像素时的冗余:

#include <tmmintrin.h>  // Note: requires SSSE3 (aka MNI)

inline void Load2(const ssize_t offset, const uint8_t* const src, __m128i& vh, __m128i& vl)
{
    const __m128i v = _mm_loadu_si128((__m128i *)(src + offset));
    vh = _mm_unpacklo_epi8(v, _mm_setzero_si128());
    vl = _mm_unpackhi_epi8(v, _mm_setzero_si128());
}

inline void Store2(const ssize_t offset, uint8_t* const dest, const __m128i vh, const __m128i vl)
{
    __m128i v = _mm_packus_epi16(vh, vl);
    _mm_storeu_si128((__m128i *)(dest + offset), v);
}

template <int SHIFT> __m128i ShiftL(const __m128i v0, const __m128i v1) { return _mm_alignr_epi8(v1, v0, SHIFT * sizeof(short)); }
template <int SHIFT> __m128i ShiftR(const __m128i v0, const __m128i v1) { return _mm_alignr_epi8(v1, v0, 16 - SHIFT * sizeof(short)); }

template <int CHANNELS> void Mean_3_3_SSE_Impl(const Mat &image_in, Mat &image_out)
{
    const int nx = image_in.cols;
    const int ny = image_in.rows;
    const int kx = 3 / 2;                               // x, y borders
    const int ky = 3 / 2;
    const int kScale = 3 * 3;                           // scale factor = total number of pixels in sum
    const __m128i vkScale = _mm_set1_epi16((32768 + kScale / 2) / kScale);
    const int nx0 = ((nx + kx) * CHANNELS + 15) & ~15;  // round up total width to multiple of 16
    int x, y;

    for (y = ky; y < ny - ky; ++y)
    {
        const uint8_t * const input_1 = image_in.ptr(y - 1);
        const uint8_t * const input0 = image_in.ptr(y);
        const uint8_t * const input1 = image_in.ptr(y + 1);
        uint8_t * const output = image_out.ptr(y);

        __m128i vsuml_1, vsumh0, vsuml0;
        __m128i vh, vl;

        vsuml_1 = _mm_set1_epi16(0);

        Load2(0, input_1, vsumh0, vsuml0);
        Load2(0, input0, vh, vl);
        vsumh0 = _mm_add_epi16(vsumh0, vh);
        vsuml0 = _mm_add_epi16(vsuml0, vl);
        Load2(0, input1, vh, vl);
        vsumh0 = _mm_add_epi16(vsumh0, vh);
        vsuml0 = _mm_add_epi16(vsuml0, vl);

        for (x = 0; x < nx0; x += 16)
        {
            __m128i vsumh1, vsuml1, vsumh, vsuml;

            Load2((x + 16), input_1, vsumh1, vsuml1);
            Load2((x + 16), input0, vh, vl);
            vsumh1 = _mm_add_epi16(vsumh1, vh);
            vsuml1 = _mm_add_epi16(vsuml1, vl);
            Load2((x + 16), input1, vh, vl);
            vsumh1 = _mm_add_epi16(vsumh1, vh);
            vsuml1 = _mm_add_epi16(vsuml1, vl);

            vsumh = _mm_add_epi16(vsumh0, ShiftR<CHANNELS>(vsuml_1, vsumh0));
            vsuml = _mm_add_epi16(vsuml0, ShiftR<CHANNELS>(vsumh0, vsuml0));
            vsumh = _mm_add_epi16(vsumh, ShiftL<CHANNELS>(vsumh0, vsuml0));
            vsuml = _mm_add_epi16(vsuml, ShiftL<CHANNELS>(vsuml0, vsumh1));

            // round mean
            vsumh = _mm_mulhrs_epi16(vsumh, vkScale);
            vsuml = _mm_mulhrs_epi16(vsuml, vkScale);

            Store2(x, output, vsumh, vsuml);

            vsuml_1 = vsuml0;
            vsumh0 = vsumh1;
            vsuml0 = vsuml1;
        }
    }
}

void Mean_3_3_SSE(const Mat &image_in, Mat &image_out)
{
    const int channels = image_in.channels();

    switch (channels)
    {
        case 1:
            Mean_3_3_SSE_Impl<1>(image_in, image_out);
            break;
        case 3:
            Mean_3_3_SSE_Impl<3>(image_in, image_out);
            break;
        default:
            throw("Unsupported format.");
            break;
    }
}
笔记
  • 我在所有实现中都忽略了边界像素——如果需要,可以使用原始图像中的像素填充边界像素,或者使用其他形式的边缘像素处理

  • 代码不是“工业实力”——它只是为了基准测试而编写的

  • 还有一些可能的进一步优化,例如使用更宽的SIMD(AVX2、AVX512)、利用连续行之间的冗余等-这些留给读者作为练习

  • SSE实现速度最快,但这是以增加复杂性、降低可维护性和降低可移植性为代价的

  • OpenCV
    blur
    函数提供了第二个最佳性能,如果它满足吞吐量要求,它可能是首选解决方案-它是最简单的解决方案,简单就是好的


  • 您首先需要从一个效率更高的标量实现开始—上面的代码效率非常低,而且对一个效率低下的实现进行矢量化是没有意义的。首先去掉所有冗余,然后再看看SIMD。请指出您认为上面代码中效率低下的地方,好吗?两个大问题是:(a)将9个像素乘以1,因此对于RGB,每像素有27个冗余乘法操作,以及(b)对重叠像素进行冗余求和-对于平均内核,您可以保持部分和(例如列),并且每个新像素只能求和一列。你应该能够实现一个3x3平均滤波器,每通道每像素4个加法和1个乘法(定点除以9)。你试过了吗?代码不好。在内部循环中,迭代到var1.cols,只得到3个值。更好的方法是类似于
    const int count=std::min(3,var1.rows-i)
    很好的答案,但我会添加两个东西。1.您的SSE版本需要SSSE3指令集。Steam HW的调查显示,“99.17%”,这通常是存在的,但还不是普遍存在的。2.C++编译器版本,用于基准测试,编译器选项。@ Soonts:谢谢-好点-答案更新。
    void Mean_3_3_blur(const Mat &image_in, Mat &image_out)
    {
        blur(image_in, image_out, Size(3, 3));
    }
    
    #include <tmmintrin.h>  // Note: requires SSSE3 (aka MNI)
    
    inline void Load2(const ssize_t offset, const uint8_t* const src, __m128i& vh, __m128i& vl)
    {
        const __m128i v = _mm_loadu_si128((__m128i *)(src + offset));
        vh = _mm_unpacklo_epi8(v, _mm_setzero_si128());
        vl = _mm_unpackhi_epi8(v, _mm_setzero_si128());
    }
    
    inline void Store2(const ssize_t offset, uint8_t* const dest, const __m128i vh, const __m128i vl)
    {
        __m128i v = _mm_packus_epi16(vh, vl);
        _mm_storeu_si128((__m128i *)(dest + offset), v);
    }
    
    template <int SHIFT> __m128i ShiftL(const __m128i v0, const __m128i v1) { return _mm_alignr_epi8(v1, v0, SHIFT * sizeof(short)); }
    template <int SHIFT> __m128i ShiftR(const __m128i v0, const __m128i v1) { return _mm_alignr_epi8(v1, v0, 16 - SHIFT * sizeof(short)); }
    
    template <int CHANNELS> void Mean_3_3_SSE_Impl(const Mat &image_in, Mat &image_out)
    {
        const int nx = image_in.cols;
        const int ny = image_in.rows;
        const int kx = 3 / 2;                               // x, y borders
        const int ky = 3 / 2;
        const int kScale = 3 * 3;                           // scale factor = total number of pixels in sum
        const __m128i vkScale = _mm_set1_epi16((32768 + kScale / 2) / kScale);
        const int nx0 = ((nx + kx) * CHANNELS + 15) & ~15;  // round up total width to multiple of 16
        int x, y;
    
        for (y = ky; y < ny - ky; ++y)
        {
            const uint8_t * const input_1 = image_in.ptr(y - 1);
            const uint8_t * const input0 = image_in.ptr(y);
            const uint8_t * const input1 = image_in.ptr(y + 1);
            uint8_t * const output = image_out.ptr(y);
    
            __m128i vsuml_1, vsumh0, vsuml0;
            __m128i vh, vl;
    
            vsuml_1 = _mm_set1_epi16(0);
    
            Load2(0, input_1, vsumh0, vsuml0);
            Load2(0, input0, vh, vl);
            vsumh0 = _mm_add_epi16(vsumh0, vh);
            vsuml0 = _mm_add_epi16(vsuml0, vl);
            Load2(0, input1, vh, vl);
            vsumh0 = _mm_add_epi16(vsumh0, vh);
            vsuml0 = _mm_add_epi16(vsuml0, vl);
    
            for (x = 0; x < nx0; x += 16)
            {
                __m128i vsumh1, vsuml1, vsumh, vsuml;
    
                Load2((x + 16), input_1, vsumh1, vsuml1);
                Load2((x + 16), input0, vh, vl);
                vsumh1 = _mm_add_epi16(vsumh1, vh);
                vsuml1 = _mm_add_epi16(vsuml1, vl);
                Load2((x + 16), input1, vh, vl);
                vsumh1 = _mm_add_epi16(vsumh1, vh);
                vsuml1 = _mm_add_epi16(vsuml1, vl);
    
                vsumh = _mm_add_epi16(vsumh0, ShiftR<CHANNELS>(vsuml_1, vsumh0));
                vsuml = _mm_add_epi16(vsuml0, ShiftR<CHANNELS>(vsumh0, vsuml0));
                vsumh = _mm_add_epi16(vsumh, ShiftL<CHANNELS>(vsumh0, vsuml0));
                vsuml = _mm_add_epi16(vsuml, ShiftL<CHANNELS>(vsuml0, vsumh1));
    
                // round mean
                vsumh = _mm_mulhrs_epi16(vsumh, vkScale);
                vsuml = _mm_mulhrs_epi16(vsuml, vkScale);
    
                Store2(x, output, vsumh, vsuml);
    
                vsuml_1 = vsuml0;
                vsumh0 = vsumh1;
                vsuml0 = vsuml1;
            }
        }
    }
    
    void Mean_3_3_SSE(const Mat &image_in, Mat &image_out)
    {
        const int channels = image_in.channels();
    
        switch (channels)
        {
            case 1:
                Mean_3_3_SSE_Impl<1>(image_in, image_out);
                break;
            case 3:
                Mean_3_3_SSE_Impl<3>(image_in, image_out);
                break;
            default:
                throw("Unsupported format.");
                break;
        }
    }
    
    Mean_3_3_ref         62153 µs
    Mean_3_3_scalar      41144 µs =  1.51062x
    Mean_3_3_scalar_opt  26238 µs =  2.36882x
    Mean_3_3_blur        20121 µs =  3.08896x
    Mean_3_3_SSE          4838 µs = 12.84680x