双三次插值算法的C++实现与SSE指令优化

article/2025/9/23 16:13:11

在上篇文章中,我们讲解了常见的最邻近插值算法、双线性插值算法和双三次插值算法的原理与实现,三种插值算法中双三次插值算法的插值效果最好,但其也是三种算法中计算复杂度最高、耗时最长的算法。本文在给出双三次插值C++代码的基础上,着重讲解如何使用SSE指令来优化该算法,并使用双三次插值来实现图像的缩放,比较SSE指令优化前后的耗时。

1. 基于C++与Opencv的代码实现

算法原理在上篇文章中已经讲了,此处直接贴出代码:

float cubic_w_f(float x, float a)
{if (x <= 1){return 1 - (a + 3)*x*x + (a + 2)*x*x*x;}else if (x < 2){return -4 * a + 8 * a*x - 5 * a*x*x + a*x*x*x;}return 0.0;
}void cal_cubic_coeff(float x, float y, float *coeff)
{float u = x - floor(x);float v = y - floor(y);u += 1;v += 1;float a = -0.15;float A[4];A[0] = cubic_w_f(abs(u), a);A[1] = cubic_w_f(abs(u - 1), a);A[2] = cubic_w_f(abs(u - 2), a);A[3] = cubic_w_f(abs(u - 3), a);for (int s = 0; s < 4; s++){float C = cubic_w_f(abs(v - s), a);coeff[s * 4] = A[0] * C;coeff[s * 4 + 1] = A[1] * C;coeff[s * 4 + 2] = A[2] * C;coeff[s * 4 + 3] = A[3] * C;}}uchar cubic_inner(Mat src, float x_float, float y_float, float a)
{float coeff[16];cal_cubic_coeff(x_float, y_float, coeff);  //计算权重系数float sum = 0.0;int x0 = floor(x_float) - 1;int y0 = floor(y_float) - 1;for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){sum += coeff[i * 4 + j] * src.ptr<uchar>(y0 + i)[x0 + j];}}return ((uchar)sum);}

2. SSE指令优化算法

首先,我们来看一下浮点型坐标点周围的4*4个整型点分别在x方向与y方向上与该浮点型坐标点的像素距离,假设浮点型坐标点的x坐标的小数部分为u,y坐标的小数部分为v,那么x方向与y方向上的距离如下图所示(每一格的像素距离为1)。

从左到右,x方向距离分别为1+u、u、1-u、2-u:

从上到下,y方向距离分别为1+v、v、1-v、2-v:

从而得到各个距离的取值范围:

1 ≤ dx0=1+u ≤ 2

0 ≤ dx1=u ≤ 1

0 ≤ dx2=1-u ≤ 1

1 ≤ dx3=2-u ≤ 2

1 ≤ dy0=1+v ≤ 2

0 ≤ dy1=v ≤ 1

0 ≤ dy2=1-v ≤ 1

1 ≤ dy3=2-v ≤ 2

双三次插值算法的权重计算公式为:

我们可以根据取值范围提前确定dxi与dyj的权重函数表达式(之前是分段函数),便于SSE指令的并行计算:

对于dx0、dx3、dy0、dy3,其权重函数表达式为:

对于dx1、dx2、dy1、dy2,其权重函数表达式为:

因此dx0、dx3、dy0、dy3的权重可以并行计算,dx1、dx2、dy1、dy2的权重同样也可以并行计算,假设浮点型坐标为(x, y),权重的SSE指令并行计算代码如下:

float u = x_float - floor(x_float);   //计算x坐标额小数部分
float v = y_float - floor(y_float);   //计算y坐标额小数部分
float a_mul_4 = (a + a) + (a + a);   //提前计算权重公式中的4a
float a_mul_5 = a_mul_4 + a;         //提前计算权重公式中的5a
float a_mul_8 = a_mul_4 + a_mul_4;   //提前计算权重公式中的8a
float a_add_3 = a + 3;   //提前计算权重公式中的a+3
float a_add_2 = a + 2;   //提前计算权重公式中的a+2
__m128 a_m = _mm_set1_ps(a);  //a a a a
__m128 m_1 = _mm_set1_ps(1.0); //1.0 1.0 1.0 1.0
__m128 a_mul_4_m = _mm_set1_ps(a_mul_4);  //4a 4a 4a 4a 
__m128 a_mul_5_m = _mm_set1_ps(a_mul_5);  //5a 5a 5a 5a
__m128 a_mul_8_m = _mm_set1_ps(a_mul_8);  //8a 8a 8a 8a 
__m128 a_add_3_m = _mm_set1_ps(a_add_3);  //a+3 a+3 a+3 a+3
__m128 a_add_2_m = _mm_set1_ps(a_add_2);  //a+2 a+2 a+2 a+2__m128 C30_A30 = _mm_set_ps(2 - v, 1 + v, 2 - u, 1 + u);   //dy3 dy0 dx3 dx0
__m128 C21_A21 = _mm_set_ps(1 - v, v, 1 - u, u);   //dy2 dy1 dx2 dx1__m128 tmp0 = _mm_sub_ps(_mm_mul_ps(a_m, C30_A30), a_mul_5_m);   //a*d - 5a
tmp0 = _mm_add_ps(a_mul_8_m, _mm_mul_ps(C30_A30, tmp0));       //8a + d*(a*d- 5a)
tmp0 = _mm_sub_ps(_mm_mul_ps(C30_A30, tmp0), a_mul_4_m);    //d*(8a + d*(a*d- 5a)) - 4a = w(dy3) w(dy0) w(dx3) w(dx0)__m128 tmp1 = _mm_sub_ps(_mm_mul_ps(a_add_2_m, C21_A21), a_add_3_m);   //(a+2)*d - (a+3)
tmp1 = _mm_mul_ps(_mm_mul_ps(C21_A21, C21_A21), tmp1);    //d*d*((a+2)*d - (a+3))
tmp1 = _mm_add_ps(m_1, tmp1);     //1 + d*d*((a+2)*d - (a+3)) = w(dy2) w(dy1) w(dx2) w(dx1)

以上代码运行之后得到权重如下(高位-->低位):

tmp0:w(dy3) w(dy0) w(dx3) w(dx0)

tmp1:w(dy2) w(dy1) w(dx2) w(dx1)

全部的w(dxi)与w(dyj)都已计算完毕,但以上并不是我们想要的排列顺序,我们想要的排列顺序如下:

w(dy3) w(dy2) w(dy1) w(dy0)

w(dx3) w(dx2) w(dx1) w(dx0)

因此我们需要对tmp0与tmp1进行重新打包与排列:

__m128 A_m = _mm_unpacklo_ps(tmp0, tmp1);    //交替打包tmp0与tmp1的低位数据:tmp1[1] tmp0[1] tmp1[0] tmp0[0] = w(dx2) w(dx3) w(dx1) w(dx0)
__m128 C_m = _mm_unpackhi_ps(tmp0, tmp1);    //交替打包tmp0与tmp1的高位数据:tmp1[3] tmp0[3] tmp1[2] tmp0[2] = w(dy2) w(dy3) w(dy1) w(dy0)
A_m = _mm_shuffle_ps(A_m, A_m, _MM_SHUFFLE(2, 3, 1, 0));   //重新排列A_m中数据的顺序:w(dx3) w(dx2) w(dx1) w(dx0)
C_m = _mm_shuffle_ps(C_m, C_m, _MM_SHUFFLE(2, 3, 1, 0));   //重新排列C_m中数据的顺序:w(dy3) w(dy2) w(dy1) w(dy0)

接下来就可以计算W(i, j)=w(dxi)*w(dyj)了,由于i和j都取0、1、2、3,因此有4*4=16个W(i, j),对应周围的4*4个整型点。代码如下:

__declspec(align(16)) float C[4];_mm_store_ps(C, C_m);  //w(dy3) w(dy2) w(dy1) w(dy0)__m128 m128_C = _mm_set1_ps(C[0]);   //w(dy0) w(dy0) w(dy0) w(dy0)__m128 coeff0 = _mm_mul_ps(A_m, m128_C);  //W(3,0) W(2,0) W(1,0) W(0,0) = w(dx3)*w(dy0) w(dx2)*w(dy0) w(dx1)*w(dy0) w(dx0)*w(dy0)m128_C = _mm_set1_ps(C[1]);  //w(dy1) w(dy1) w(dy1) w(dy1)__m128 coeff1 = _mm_mul_ps(A_m, m128_C);  //w(dx3)*w(dy1) w(dx2)*w(dy1) w(dx1)*w(dy1) w(dx0)*w(dy1)m128_C = _mm_set1_ps(C[2]);   //w(dy2) w(dy2) w(dy2) w(dy2)__m128 coeff2 = _mm_mul_ps(A_m, m128_C);  //w(dx3)*w(dy2) w(dx2)*w(dy2) w(dx1)*w(dy2) w(dx0)*w(dy2)m128_C = _mm_set1_ps(C[3]);   //w(dy3) w(dy3) w(dy3) w(dy3)__m128 coeff3 = _mm_mul_ps(A_m, m128_C);  //w(dx3)*w(dy3) w(dx2)*w(dy3) w(dx1)*w(dy3) w(dx0)*w(dy3)

最后,就可以计算4*4个整型点的像素加权和了:

//计算4*4个整型点组成的矩形点阵的左上角点的坐标,也即(x0, y0)
int x0 = floor(x_float) - 1;
int y0 = floor(y_float) - 1;__m128 sum_m = _mm_setzero_ps();   //0 0 0 0uchar *src_p = src.ptr<uchar>(y0);  //4*4矩形点阵的第一行首地址
__m128 src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]); //4*4矩形点阵的第一行点像素值:A(x0+3,y0) A(x0+2,y0) A(x0+1,y0) A(x0,y0)                       
sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff0));  //累加:W(3,0)*A(x0+3,y0) W(2,0)*A(x0+2,y0) W(1,0)*A(x0+1,y0) W(0,0)*A(x0,y0) src_p = src.ptr<uchar>(y0 + 1);   //4*4矩形点阵的第二行首地址
src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);  //4*4矩形点阵的第二行点像素值:A(x0+3,y1) A(x0+2,y1) A(x0+1,y1) A(x0,y1)
sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff1)); //累加:W(3,1)*A(x0+3,y1) W(2,1)*A(x0+2,y1) W(1,1)*A(x0+1,y1) W(0,1)*A(x0,y1)src_p = src.ptr<uchar>(y0 + 2);   //4*4矩形点阵的第三行首地址
src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);   //4*4矩形点阵的第三行点像素值:A(x0+3,y2) A(x0+2,y2) A(x0+1,y2) A(x0,y2)
sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff2)); //累加:W(3,2)*A(x0+3,y2) W(2,2)*A(x0+2,y2) W(1,2)*A(x0+1,y2) W(0,2)*A(x0,y2)src_p = src.ptr<uchar>(y0 + 3);   //4*4矩形点阵的第四行首地址
src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);   //4*4矩形点阵的第四行点像素值:A(x0+3,y3) A(x0+2,y3) A(x0+1,y3) A(x0,y3)
sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff3)); //累加:W(3,3)*A(x0+3,y3) W(2,3)*A(x0+2,y3) W(1,3)*A(x0+1,y3) W(0,3)*A(x0,y3)float *p = (float *)&sum_m;
uchar sum = (uchar)(p[0] + p[1] + p[2] + p[3]);   //最后再把sum_m中的四个累加和加起来,即得到最终的插值结果

完整的SSE指令优化的双三次插值代码如下:

uchar cubic_inner_SSE(Mat src, float x_float, float y_float, float a)
{//计算权重系数float u = x_float - floor(x_float);float v = y_float - floor(y_float);float a_mul_4 = (a + a) + (a + a);   //4afloat a_mul_5 = a_mul_4 + a;         //5afloat a_mul_8 = a_mul_4 + a_mul_4;   //8afloat a_add_3 = a + 3;float a_add_2 = a + 2;__m128 a_m = _mm_set1_ps(a);__m128 m_1 = _mm_set1_ps(1.0);__m128 a_mul_4_m = _mm_set1_ps(a_mul_4);__m128 a_mul_5_m = _mm_set1_ps(a_mul_5);__m128 a_mul_8_m = _mm_set1_ps(a_mul_8);__m128 a_add_3_m = _mm_set1_ps(a_add_3);__m128 a_add_2_m = _mm_set1_ps(a_add_2);__m128 C30_A30 = _mm_set_ps(2 - v, 1 + v, 2 - u, 1 + u);   //C3 C0 A3 A0__m128 C21_A21 = _mm_set_ps(1 - v, v, 1 - u, u);   //C2 C1 A2 A1__m128 tmp0 = _mm_sub_ps(_mm_mul_ps(a_m, C30_A30), a_mul_5_m);   //a*xx - a_mul_5tmp0 = _mm_add_ps(a_mul_8_m, _mm_mul_ps(C30_A30, tmp0));       //a_mul_8 + xx*(a*xx - a_mul_5)tmp0 = _mm_sub_ps(_mm_mul_ps(C30_A30, tmp0), a_mul_4_m);    //xx*(a_mul_8 + xx*(a*xx - a_mul_5)) - a_mul_4  = C3 C0 A3 A0__m128 tmp1 = _mm_sub_ps(_mm_mul_ps(a_add_2_m, C21_A21), a_add_3_m);   //a_add_2*xx - a_add_3tmp1 = _mm_mul_ps(_mm_mul_ps(C21_A21, C21_A21), tmp1);    //xx*xx*(a_add_2*xx - a_add_3)tmp1 = _mm_add_ps(m_1, tmp1);     //1 + xx*xx*(a_add_2*xx - a_add_3) = C2 C1 A2 A1__m128 A_m = _mm_unpacklo_ps(tmp0, tmp1);    //tmp1[1] tmp0[1] tmp1[0] tmp0[0] = A2 A3 A1 A0__m128 C_m = _mm_unpackhi_ps(tmp0, tmp1);    //tmp1[3] tmp0[3] tmp1[2] tmp0[2] = C2 C3 C1 C0A_m = _mm_shuffle_ps(A_m, A_m, _MM_SHUFFLE(2, 3, 1, 0));   //A3 A2 A1 A0C_m = _mm_shuffle_ps(C_m, C_m, _MM_SHUFFLE(2, 3, 1, 0));   //C3 C2 C1 C0__declspec(align(16)) float C[4];_mm_store_ps(C, C_m);__m128 m128_C = _mm_set1_ps(C[0]);__m128 coeff0 = _mm_mul_ps(A_m, m128_C);m128_C = _mm_set1_ps(C[1]);__m128 coeff1 = _mm_mul_ps(A_m, m128_C);m128_C = _mm_set1_ps(C[2]);__m128 coeff2 = _mm_mul_ps(A_m, m128_C);m128_C = _mm_set1_ps(C[3]);__m128 coeff3 = _mm_mul_ps(A_m, m128_C);///int x0 = floor(x_float) - 1;int y0 = floor(y_float) - 1;__m128 sum_m = _mm_setzero_ps();uchar *src_p = src.ptr<uchar>(y0);__m128 src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff0));src_p = src.ptr<uchar>(y0 + 1);src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff1));src_p = src.ptr<uchar>(y0 + 2);src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff2));src_p = src.ptr<uchar>(y0 + 3);src_m = _mm_set_ps(src_p[x0 + 3], src_p[x0 + 2], src_p[x0 + 1], src_p[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff3));float *p = (float *)&sum_m;uchar sum = (uchar)(p[0] + p[1] + p[2] + p[3]);return sum;
}

接下来,我们分别调用以上实现的cubic_inner函数和cubic_inner_SSE函数来实现图像缩放功能,实现代码如下:

//图像缩放函数
void resize_img(Mat src, Mat &dst, float row_m, float col_m)
{const int row = (int)(src.rows*row_m);const int col = (int)(src.cols*col_m);const float x_a = 1.0 / col_m;const float y_a = 1.0 / row_m;Mat dst_tmp = Mat::zeros(row, col, CV_8UC1);for (int i = 0; i < row; i++){uchar *p = dst_tmp.ptr<uchar>(i);float y = i*y_a;for (int j = 0; j < col; j++){float x = j*x_a;//p[j] = cubic_inner(src, x, y, -0.5);    //原函数p[j] = cubic_inner_SSE(src, x, y, -0.5);  //SSE优化函数}}dst_tmp.copyTo(dst);
}

自己实现了一个微秒级计时的类,用于记录函数的运行时间:

class Timer_Us
{private:LARGE_INTEGER cpuFreq;LARGE_INTEGER startTime;LARGE_INTEGER endTime;public:double rumTime;void get_frequence(void);void start_timer(void);void stop_timer(char *str);Timer_Us();    //构造函数~Timer_Us();   //析构函数
};void Timer_Us::get_frequence(void)
{QueryPerformanceFrequency(&cpuFreq);   //获取时钟频率
}void Timer_Us::start_timer(void)
{QueryPerformanceCounter(&startTime);    //开始计时
}void Timer_Us::stop_timer(char *str)
{QueryPerformanceCounter(&endTime);    //结束计时rumTime = (((endTime.QuadPart - startTime.QuadPart) * 1000.0f) / cpuFreq.QuadPart);cout <<str<< rumTime << " ms" << endl;
}Timer_Us::Timer_Us()    //构造函数
{QueryPerformanceFrequency(&cpuFreq);
}Timer_Us::~Timer_Us()    //析构函数
{}

最后是测试函数,调用以上实现的图像缩放函数,对248*236的Lena图像的宽和高都放大到原来的三倍,并记录SSE指令优化插值前后的耗时。

void resize_img_test(void)
{Mat img = imread("lena.tif", CV_LOAD_IMAGE_GRAYSCALE);Timer_Us timer;float mul = 3;  //宽和高都放大三倍Mat img_resize;timer.start_timer();  //开始计时resize_img(img, img_resize2, mul, mul, 2);timer.stop_timer("cubic resize time:");  //结束计时,并显示运行耗时imshow("cubic img_resize", img_resize);waitKey();
}

运行以上代码,调用原C++实现的cubic_inner函数,耗时约35.5022 ms,如果是调用SSE指令优化的cubic_inner_SSE函数,耗时约17.3297 ms。因此SSE优化之后,耗时减少约一半,优化效果还是比较理想的。

原图

放大的图像

实际上以上实现的图像缩放函数resize_img还有很大的优化空间,比如函数里面有两层循环,外面一层是行遍历,里面一层是列遍历,在双三次插值过程中,有一些参数的计算对于同一行数据来说是一样的,因此可以把这部分计算过程从内循环提到外循环来做,如此以来,每一行只需要计算一次这些参数,可以减少不少耗时。进一步优化的resize_img函数代码如下。调用该函数对同样的Lena图像进行宽、高各三倍的放大,耗时减少为10 ms左右,优化效果还是比较显著的。

void resize_img_cubic(Mat src, Mat &dst, float row_m, float col_m)
{const int row = (int)(src.rows*row_m);const int col = (int)(src.cols*col_m);const float x_a = 1.0 / col_m;const float y_a = 1.0 / row_m;Mat dst_tmp = Mat::zeros(row, col, CV_8UC1);__declspec(align(16)) float A[4];    //内存对齐float C[4];float a = -0.15;//这些参数不变,直接提到循环外面计算float a_mul_4 = (a + a) + (a + a);   //4afloat a_mul_5 = a_mul_4 + a;         //5afloat a_mul_8 = a_mul_4 + a_mul_4;   //8afloat a_add_3 = a + 3;float a_add_2 = a + 2;float xx;for (int i = 0; i < row; i++){uchar *p = dst_tmp.ptr<uchar>(i);//以下这些是提到外循环计算的参数float y = i*y_a;int y0 = floor(y) - 1;float v = y - floor(y);xx = 1 + v;C[0] = -a_mul_4 + xx*(a_mul_8 + xx*(a*xx - a_mul_5));   //1<u<2xx = v;  // 0<v<1;C[1] = 1 + xx*xx*(a_add_2*xx - a_add_3);xx = 1 - v;  // 0<v<1C[2] = 1 + xx*xx*(a_add_2*xx - a_add_3);xx = 2 - v;  // 1<v<2C[3] = -a_mul_4 + xx*(a_mul_8 + xx*(a*xx - a_mul_5));__m128 m128_C0 = _mm_set1_ps(C[0]);__m128 m128_C1 = _mm_set1_ps(C[1]);__m128 m128_C2 = _mm_set1_ps(C[2]);__m128 m128_C3 = _mm_set1_ps(C[3]);uchar *src_p0 = src.ptr<uchar>(y0);uchar *src_p1 = src.ptr<uchar>(y0+1);uchar *src_p2 = src.ptr<uchar>(y0+2);uchar *src_p3 = src.ptr<uchar>(y0+3);for (int j = 0; j < col; j++){float x = j*x_a;float u = x - floor(x);xx = 1 + u;A[0] = -a_mul_4 + xx*(a_mul_8 + xx*(a*xx - a_mul_5)); //-a_mul_4 + a_mul_8*u - a_mul_5*u*u + a*u*u*u;   //1<u<2xx = u;  // 0<u<1;A[1] = 1 + xx*xx*(a_add_2*xx - a_add_3);   //1 - a_add_3*xx*xx + a_add_2*xx*xx*xx;xx = 1 - u;  // 0<u<1A[2] = 1 + xx*xx*(a_add_2*xx - a_add_3);//1 - a_add_3*xx*xx + a_add_2*xx*xx*xx;xx = 2 - u;  // 1<u<2A[3] = -a_mul_4 + xx*(a_mul_8 + xx*(a*xx - a_mul_5));//-a_mul_4 + a_mul_8*xx - a_mul_5*xx*xx + a*xx*xx*xx;__m128 m128_A = _mm_load_ps(A);__m128 coeff0 = _mm_mul_ps(m128_A, m128_C0);__m128 coeff1 = _mm_mul_ps(m128_A, m128_C1);__m128 coeff2 = _mm_mul_ps(m128_A, m128_C2);__m128 coeff3 = _mm_mul_ps(m128_A, m128_C3);int x0 = floor(x) - 1;__m128 src_m = _mm_set_ps(src_p0[x0 + 3], src_p0[x0 + 2], src_p0[x0 + 1], src_p0[x0]);__m128 sum_m = _mm_mul_ps(src_m, coeff0);src_m = _mm_set_ps(src_p1[x0 + 3], src_p1[x0 + 2], src_p1[x0 + 1], src_p1[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff1));src_m = _mm_set_ps(src_p2[x0 + 3], src_p2[x0 + 2], src_p2[x0 + 1], src_p2[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff2));src_m = _mm_set_ps(src_p3[x0 + 3], src_p3[x0 + 2], src_p3[x0 + 1], src_p3[x0]);sum_m = _mm_add_ps(sum_m, _mm_mul_ps(src_m, coeff3));float *p1 = (float *)&sum_m;p[j] = (uchar)(p1[0] + p1[1] + p1[2] + p1[3]);}}dst_tmp.copyTo(dst);}

学习代码优化有一段时间了,包括代码自身结构优化、SSE指令优化、CUDA优化等。感触最深的是,代码优化是一个精益求精的过程,一步步地优化之后,往往优化代码与原来的代码相比已经面目全非了,因此优化之后的代码可读性非常差,如果不对自己的优化思路作详细记录,过一段时间可能自己都看不懂自己的优化代码了,这是非常尴尬的,所以详细记录与注释还是非常有必要的。当然,本人的水平有限,以上代码的优化只是一个抛砖引玉的过程,也许还有更大的优化空间,如果读者有更好的优化idea,欢迎给我留言讨论。

微信公众号如下,欢迎扫码关注,欢迎私信技术交流:


http://chatgpt.dhexx.cn/article/njXf5UVX.shtml

相关文章

双三次插值 - 插值图像任意位置亚像素C++

双三次插值 - 插值图像任意位置亚像素C 一、概念 双三次插值又称立方卷积插值。三次卷积插值是一种更加复杂的插值方式。该算法利用待采样点周围16个点的灰度值作三次插值&#xff0c;不仅考虑到4 个直接相邻点的灰度影响&#xff0c;而且考虑到各邻点间灰度值变化率的影响。…

matlab 给图像双三次,图像灰度的双三次插值的MATLAB实现

相比C/C实现&#xff0c;图像灰度的双三次插值的MATLAB实现要方便的多&#xff0c;下面是MATLAB语言实现 clc,clear; ffimread(C:\Program Files\MATLAB\R2013a\bin\work\lena.bmp); [mm,nn]size(ff);%将图像隔行隔列抽取元素&#xff0c;得到缩小的图像f mmm/2;nnn/2; fzeros(…

java 双三次线性插值_三种常见的图像处理双三次插值算法

三种常见的图像处理双三次插值算法 双立方插值计算涉及16像素,间(i’, j’)像中的包括 小数部分的像素坐标。dx表示X方向的小数坐标。dy表示Y方向的小数坐标。 详细 能够看下图: 依据上述图示与双立方插值的数学表达式能够看出。双立方插值本质上图像16个像素点 权重卷积之和…

双三次插值 python实现_Python:用GPU实现双三次插值

它不是GPU(而是尝试利用线程和CPU的向量单元)&#xff0c;但是pyvips比scipy快很多&#xff0c;您可以测试一下。在 我做了个基准&#xff1a;import sys import time import scipy.ndimage import pyvips scale 10 n_loops 10 start time.time() test_image scipy.ndimage…

双三次插值及Matlab实现

双三次插值及Matlab实现 一、简单实例 采用简单实例进行对双三次插值的介绍&#xff0c;由于双三次插值对于目标图像的某一像素进行估计时&#xff0c;所采用的像素信息为其周围16个像素点信息&#xff0c;因此不同于最近邻插值和双线性插值&#xff0c;此时假设有 5 5 5\tim…

【opencv】最近邻插值、双线性插值、双三次插值(三次样条插值)

目录 1. 最近邻插值2. 双线性插值1&#xff09;简单理解2&#xff09;一般性 3. 双三次插值&#xff08;三次样条插值&#xff09;总结 b站 视频讲解 &#xff1a; https://www.bilibili.com/video/BV1wh411E7j9/ 1. 最近邻插值 举个简单例子&#xff1a;一个 3 3 3 \times 3…

挑战图像处理100问(27)——双三次插值

当我们需要对图像进行放大或缩小时&#xff0c;为了避免图像变形或失真&#xff0c;我们需要进行插值。插值是一种基于已知数据点&#xff0c;在这些数据点之间进行估计的方法。在图像处理中&#xff0c;常见的插值方法包括最邻近插值、双线性插值、双三次插值等。 双三次插值…

【图像超分辨(SR)】通俗直观彻底理解双线性插值、双三次插值及其作用

写在前面的一点话 网上讲解基本双线性插值、双三次线性插值的文章很多&#xff0c;但大部分都是只在讲为什么是这样&#xff0c;并不算非常通俗&#xff08;起码对我来说需要额外查很多资料来补充理解&#xff09;。很少有文章能够给初学者一些比较直观的理解&#xff0c;因此…

插值法(最邻近,双线性,双三次)的原理及实现

插值法(最邻近&#xff0c;双线性&#xff0c;双三次&#xff09;的原理及实现 常用的插值方法有最邻近插值法、双现象插值法和双三次插值法等&#xff0c;主要用于图像的放大或缩小。 缩小图像&#xff08;或称为下采样&#xff08;subsampled&#xff09; 或降采样&#xf…

表示关系x =y =z的c语言表达式为________.,为表示关系x≥y≥z,应使用C语言表达式____。...

为表焦距决定了画面的透视关系。 示关使用GREGRE ≥y≥桡神经沟位于 应C语言表关于脊柱正确的描写是() 建筑物或者其他设施以及建筑物上的搁置物、达式悬挂物发生( )造成他人损害的&#xff0c;所有人或者管理人应当承担民事责任,但能够证明自己没有过错的除外。 为表成对的脑颅…

C语言运算表达式整理

C语言的运算规则是按照运算符的优先级进行的&#xff0c;所以我们首先看一下C的运算优先级别 总结 最简单的优先级就是 算数运算符>关系运算符>逻辑运算符>赋值运算符 然后我们还要了解一个短路规则&#xff08;特性&#xff09; 短路规则&#xff1a;在整个逻辑表…

与数学式子对应的C语言表达式是(),与数学式子3乘以x的n次方/(2x-1)对应的C语言表达式是...

满意答案 lengwei241 2013.03.25 采纳率:43% 等级:13 已帮助:11778人 18 与数学式子3乘以x的n次方/(2x-1)对应的C语言表达式是 3*x^n/(2*x-1) 3*x**n/(2*x-1) 3*pow(x,n)*(1/(2*x-1)) 3*pow(n,x)/(2*x-1) 答案是3*pow(x,n)*(1/(2*x-1)) 为什么 解答:其中pow(x,n)为C语言…

C语言表达式和表达式的值

今天突然发现如果能很好的理解表达式和表达式的值的概念&#xff0c;可以使编程代码变的更整洁。很多时候我们总是忘记从最基本的概念开始考虑问题。我觉得我们有必要把C/C标准看下&#xff0c;在里面肯定有很多我们一直困惑的问题的答案。 C语言是基于结构化程序设计思想的程序…

以下c语言表达式与代数式计算结果,设有定义:float a=2,b=4,h=3;,以下C语言表达式中与代数式计算结果不相符的是...

设有定义:float a=2,b=4,h=3;,以下C语言表达式中与代数式计算结果不相符的是 设有式计算结Women in Britain, having some qualifications, were enfranchised until ______. 定义达式代数The usual rhyme scheme for the English or Shakespearean sonnet is( ). In “For …

与数学式对应的c语言表达式为,数学表达式“|x|4”对应的C语言表达式是“x-4||x4”。...

数学表达式“|x|4”对应的C语言表达式是“x-4||x4”。 数学式《莺莺传》故事的时代为( ) 表达人力资本的投资是提高技术水平的必要途径。() 对达式教育投资是人力资本的投资唯一重点。() 微商是以移动终端为载体&#xff0c;应的C语言表通过社交媒介手段&#xff0c;进行传播的…

C语言 运算符、表达式

运算符、表达式 1、 C语言运算符 算术运算符和算术表达式 &#xff08;加&#xff09;、—&#xff08;减&#xff09;、*&#xff08;乘&#xff09;、/&#xff08;除&#xff09;、%&#xff08;取余&#xff0c;也叫模运算符&#xff0c;该运算符两侧都要求为整数&#xff…

c语言表达式语法问题,c语言表达式语法规则是什么_后端开发

c语言统计单词个数的方法_后端开发 在一篇文章中单词都是用空格隔开的&#xff0c;所以单词个数空格数1&#xff0c;c语言统计单词个数实际上转换成了统计文章中的空格数&#xff0c;可以用while和if来进行统计。 c语言表达式语法规则是什么 C语言表达式语法规则如下&#xff1…

c语言表达式成立,若有int y;则表示命题“y是偶数”成立的c语言表达式是多少...

满意答案 hoome_09 2017.12.28 采纳率&#xff1a;44% 等级&#xff1a;8 已帮助&#xff1a;263人 1. 若有int y&#xff1b;则表示命题“y是奇数”成立的C语言表达式是 (y%2 1) 。 2. 在C语言中&#xff0c;表示逻辑“真”值用(true) 表示。 3. 条件“2&#xff1c;x&…

C语言表达式用法快来看看

点击上方蓝字关注我&#xff0c;了解更多咨询 表达式是C语言的主体。在C语言中&#xff0c;表达式由操作符和操作数组成。最简单的表达式可以只含有一个操作数。根据表达式所含操作符的个数&#xff0c;可以把表达式分为简单表达式和复杂表达式两种&#xff0c;简单表达式是只含…