Halide 高效的图像处理语言 简化图像编程

这篇具有很好参考价值的文章主要介绍了Halide 高效的图像处理语言 简化图像编程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Halide 高效的图像处理语言 简化图像编程

github源码

Halide是用C++作为宿主语言的一个图像处理相关的DSL(Domain Specified Language)语言,全称领域专用语言。
主要的作用为在软硬层面上(与算法本身的设计无关)实现对算法的底层加速,我们有必要对其有一定的了解。
因为不论是 传统的图像处理方法亦或是深度学习应用 都使用到了halide的思想。

在OpenCV(传统图像处理库)中部分算法使用了Halide后端,而TVM(神经网络编译器)也是用了Halide的思想去优化神经网络算子。

只要我们稍微留意一下Instagram便不难发现,图像处理软件有着的广阔的市场前景。
Facebook正在试图用10亿元收购这家从事图像处理业务的公司。以往人们主要通过计算机来向网络上传照片;
但现在,越来越多的人却选择直接通过手机发送。因此图像处理也逐渐在移动终端变得流行。
同时,现在的数码图片普遍非常大,不借助好的软件工具,即使是在台式机上也需要花费很长的时间业处理。
工程师可以通过一定的技巧来加速图像处理算法,但不幸的是,这些技巧会使代码几乎没有可读性,也很难被复用。
想要向图像处理程序中增加一个函数,或者将算法移植到一个不同的平台上,往往需要彻底地修改整个代码。

MIT计算机科学和人工智能实验室(CSAIL)的研究人员开发了一门名为“Hlaide”的新的编程语言,旨在改变图像处理编程的现状。
比起用传统语言编写的代码,用Halide编写的程序不仅更容易阅读、编写和修改,
而且,由于Halide能够自动优化代码(对常规的编程语言,这个过程需要花费数小时来手工完成),因此,程序的运行速度也显著提高。
在测试中,MIT的研究人员用Halide重写了几个常用的图像处理算法(这些算法都事先经过熟练的程序员优化)。
Halide版本的算法在代码量上大体要多出1/3,但性能却有极大的提升,加速比能够加速达2倍、3倍甚至6倍。
在一个算法中,Halide程序虽然比原程序长,但却取得是了70倍的加速比。
Jonathan Ragan-Kelley(电子工程与计算机科学技术学院研究生)和Andrew Adams(CSAIL博士后)领导了Halide的开发,
并且还将原代码在网上公布。在这个月的Siggraph(最重要的图形会议)上,他们提交了一篇关于Halide的文章,
这篇文章由他俩与MIT计算工教授Saman Amarasingh、Fredo Durand,以及他们在Adobe和斯坦福的同事共同写的。

并行流水线

图像处理之所以需要如此大的计算量的原因之一是,它通常需要一系列离散操作。当光打到手机摄像头的传感器上之后,手机扫描图像数据,查找可能导致异常像素点的数据并加以纠正。之后,将传感器信息转化为像素的颜色信息,并进行颜色校正以和比对度高速高速,以使图像更接近人眼的观看效果。至此,手机已经进行了大量的处理工作,最后还要再对所有数据进行一次清理处理。

这么工作也仅仅是使图像在手机屏幕上得以显示。之后,我们还需要更多的处理步骤,以便用软件完成诸去除红眼、柔化阴影或增加饱和度(甚至是使图像看起来更有老式一次性照片的感觉)等操作。此外,高级的修改操作常常需要软件能够撤销现有操作,恢复到上一阶段的状态。

在今天的多核心芯片上,将图像的不同区域分配到不同的计算核心上并行执行,能够使图像处理更加高效。但是,通常各个核心在完成计算任务后,会将结果返回给主存。由于数据传输要比计算慢很多,所以这会抵销并行化带来的性能提升。

因此,软件工程师试图使每个核心在将它们的计算结果返回到主存前,尽可能的保持忙碌。这意味着,在最终汇总所有数据前,每个计算核心都必须在它所分得的数据上,执行图像处理流水线中的若干个步骤。高效的图像处理代码之所以难以编写,原因在于我们必须跟踪不同核心上处理的像素点之间的所有依赖关系。计算核心数量、计算核心的处理能力、分配给每个核心的局部内存大小以及核心何时将数据移出芯片外,在这些因素的选择之间必须作出折衷,而折衷方案则会因机器而异。因此一台设备而言优化的程序,换在另一台机器上可能起不到加速效果。

分而治之

使用Halide,程序员也还得自己考虑在特定机器上高效实现并行化的方案,但是仅就描述图像处理算法而言,程序员则完全不用担心这些。一个Halide程序有两部分组成:一部分是算法,另部分是“调度“(schedule)。调度指定了每个计算核心在各个处理步骤中需要处理的图像块的大小和形状。同时,它还指定的数据的依赖性——例如,某个核心上的某个处理步骤需要用到在另外的核心上之前步骤的计算结果。一旦调度确定,Halide将自动处理调度中描述的操作。

程序员如果想要将程序移植到一台不同的机器上,他不需要改变算法描述,只需变更调度。程序员如果想要向流水线中增加一个新的处理步骤,也只需要插入一个关于这个操作过程的新的描述,而无需修改已有的代码。(但是,流水线中的新步骤需要在调度中有一个与之对应的设定)

“当你有一个想法,想要将某个操作步骤而并行化,或者需要将某些步骤作某种修改时,如果你需要手动编码实现,那么你会发现,想要正确地表达自己的这个想法是件很困难的事情。”Ragan_Kelley说,“如果你有一个最的优化思路想要应用,很可能你必须花上三天时间去调度,因为你必须将原算法的操作步骤打散。而利用Halide,你只需理性一行代码就能正确地表达出你的想法。”

虽然比起普通的画像处理程序,Halide程序更易于编写和阅读,但是,由于对并行化的调度是自动完成的,因此,相当比大多数精心手工编写的代码,它们仍然能够经常获得性能上的提升。更重要的是,Halide代码非常容易修改,它使程序员能够轻松地通过实验去验证自己初步的想法能否提升性能。

“你可以随心所欲的尝试不同的整改,并且你总会发现一些好的东西,”Adams说,“之后,当你经过深思熟虑之后,你将会发现它们为什么好的道理。”

“使用一个领域相关的语言——像他们现在选择去研究开发的这门语言——能够带来许多激动人心的事情,但是,成功的例子屈指可数,”John Owens,加州理工大学戴维斯分校的一位电气与计算机工程助理教授如是说,“我认为这将会是一个美妙的成功故事。它具备你期望从一个完整系统上找到的所有的部件,而且它所针对的确实一个非常重要的领域。”

Halide为什么可以优化算法

Halide的特点是其图像算法的计算的实现(Function和Expression)和这些计算在计算硬件单元上的调度(Schedule)是分离的,其调度以Function为单位。
最终将整个图像算法转换为高效率的多层for循环,for循环的分部数据范围划分和数据加载都是由Halide来完成的,
而且可以实现数据的加载和算法计算的Overlay,掩盖数据加载导致的延迟。Halide的Schedule可以由程序员来指定一些策略,
指定硬件的buffer大小,缓冲线的相关设置,这样可以根据不同的计算硬件的特性来实现高效率的计算单元的调度,而图像算法的计算实现却不需要修改。

元编程

Halide的思想与元编程有着密切的关系,不仅是其设计思路或者是其执行思路,都遵循了元编程的思想,也就是代码在编译之前并没有明确的执行逻辑,只有编译过后,才会形成执行逻辑。

使用简介

参考


Halide 基本语法


Halide 关键字

Algorithm部分:

Halide::func 对应图像流水线处理中的一个步骤。这个func定义了一个图像中每一个像素应该是什么值。这里只是一个定义。
Halide::Var 用于定义func的变量。
Halide::Expr 用于定义一个表达式。
Halide::cast 强制类型转换。
Halide::min 
Halide::Buffer<T>用于定义缓存

Schedule部分:Vectorize,parallelize,unroll
Halide在定义func的时候没有真正计算每个像素点的值,只有在调用func的realize方法时才会真正执行。
Halide Debug手段

_.compile_to_lowered_stmt("_.html", {}, HTML)可以将中间结果生成html预览。
trace_stores用于跟踪执行过程。
在func中直接print部分内容。
cout可以打印Expr的具体表达式内容。
_.print_loop_nest()可以将调度的伪代码打印出来。



// 定义函数
Func gradient("gradient"); // 利用Func 定义一个待执行的function,并起名为gradient

// 定义变量
Var x("x"), y("y"); // 利用Halide定义两个变量,这两个变量单独使用时没有任何意义,同时我们用字符串x和y为两个变量起了名字

// 定义表达式  Expr

// 这时我们定义function中每个点的执行逻辑,对于(x,y)这个点执行的逻辑为x + y。
// 其中x和y都是Var,而x + y这个操作在赋予给gradient的时候会自动转化为Expr类型,这里可以理解为将x + y这个代数表达式的逻辑赋予了gradient,
// 最后,通过realize函数来执行整个逻辑:

gradient(x, y) = x + y;
// realize 即为实现这个操作 到了这一步才会对上述的操作进行编译并执行
Buffer<int> output = gradient.realize(4, 4);

// 这个逻辑我们用C++来表示即为:
for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 4; x++) {
        printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
    }
}

// 而上述实现的Halide伪代码为:
produce gradient:
  for y:
    for x:
      gradient(...) = ...
      
// Halide默认的计算顺序是行优先的,也就是x代表每一行的元素位置,y代表每一列的元素位置

// 如果我们将其中y和x的计算顺序换一下:
// 将y的顺序提到x之前
gradient.reorder(y, x);
//最终的计算过程就为列优先:

//相应的伪代码为:
produce gradient_col_major:
  for x:
    for y:
      gradient_col_major(...) = ...
      
      
// 定义图像  Image


demo示例


// Halide.h包含了整个Halide, 只需要include这个头文件即可
#include "Halide.h"
//c头文件是为了使用c函数
#include <stdio.h>

int main(int argc, char **argv) {

    //定义函数
    Halide::Func gradient;
    //定义变量
    Halide::Var x, y;
    //定义计算
    Halide::Expr e = x + y;

    gradient(x, y) = e;
    //获取计算结果
    Halide::Buffer<int32_t> output = gradient.realize(800, 600);

    //验证计算结果
    for (int j = 0; j < output.height(); j++) {
        for (int i = 0; i < output.width(); i++) {
            if (output(i, j) != i + j) {
                printf("Something went wrong!\n"
                       "Pixel %d, %d was supposed to be %d, but instead it's %d\n",
                       i, j, i+j, output(i, j));
                return -1;
            }
        }
    }
    printf("Success!\n");
    return 0;
}

拆分 Split

//我们可以对每个维度进行拆分,假如我们依然是行优先计算,但是我们对x轴进行拆分,将其拆成一个外循环一个里循环,y轴不进行变动:

Var x_outer, x_inner;

gradient.split(x, x_outer, x_inner, 2);

这时对应的C++代码实现为:

for (int y = 0; y < 4; y++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        for (int x_inner = 0; x_inner < 2; x_inner++) {
            int x = x_outer * 2 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
    }
}

融合 fuse

或者我们不进行拆分,对x和y两个轴进行融合:

Var fused;

gradient.fuse(x, y, fused);

此时对应的C++实现代码为:

for (int fused = 0; fused < 4*4; fused++) {
    int y = fused / 4;
    int x = fused % 4;
    printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
}

平铺 tile 分块计算

这一步中就要进入Halide中比较重要的部分了,这一步中我们将x和y轴以4为因子间隔进行划分,并且重新对计算的路径进行重排序:

Var x_outer, x_inner, y_outer, y_inner;
gradient.split(x, x_outer, x_inner, 4);
gradient.split(y, y_outer, y_inner, 4);
gradient.reorder(x_inner, y_inner, x_outer, y_outer);

// 上面的步骤其实可以简化成
gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);

// 对应的C++计算代码为:
for (int y_outer = 0; y_outer < 2; y_outer++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        for (int y_inner = 0; y_inner < 4; y_inner++) {
            for (int x_inner = 0; x_inner < 4; x_inner++) {
                int x = x_outer * 4 + x_inner;
                int y = y_outer * 4 + y_inner;
                printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
            }
        }
    }
}


这种平铺的好处是可以充分利用相邻的像素,例如在模糊中,我们会使用重叠的输入数据(也就是存在一个元素使用两次的情况),如果采用这种计算方式,可以大大加快计算性能。

向量化 vector

向量化即我们使用cpu中的SIMD技术,一次性计算多个数据,充分利用硬件的特点,例如在x86中我们可以利用SSE技术来实现这个功能。

在Halide中,我们首先将x轴的循环嵌套按照,内侧循环因子4的方式,拆分为两个(也就是内侧循环x执行四次,外侧根据总数进行计算,下例是2*4=8),然后将内侧的x循环转化为向量的形式:

Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 4);
gradient.vectorize(x_inner);
// 用C++来表示即为:
for (int y = 0; y < 4; y++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        // The loop over x_inner has gone away, and has been
        // replaced by a vectorized version of the
        // expression. On x86 processors, Halide generates SSE
        // for all of this.
        int x_vec[] = {x_outer * 4 + 0,
                        x_outer * 4 + 1,
                        x_outer * 4 + 2,
                        x_outer * 4 + 3};
        int val[] = {x_vec[0] + y,
                        x_vec[1] + y,
                        x_vec[2] + y,
                        x_vec[3] + y};
        printf("Evaluating at <%d, %d, %d, %d>, <%d, %d, %d, %d>:"
                " <%d, %d, %d, %d>\n",
                x_vec[0], x_vec[1], x_vec[2], x_vec[3],
                y, y, y, y,
                val[0], val[1], val[2], val[3]);
    }
}



可视化后就比较明显了,外部x每一行执行两次,内侧x变为向量的形式,一个指令集就可以执行完成:

展开 unrolling

如果在图像中多个像素同时共享有重叠的数据,这个时候我们就可以将循环展开,从而使那些可以共享使用的数据只计算一次亦或是只加载一次。

在下面中我们将x轴拆分为内侧和外侧,因为每次内侧的数值增长都是从0到1,如果我们将内测循环的x轴展开,就不需要每次循环到这里再读取内测循环的x的值了:


Var x_outer, x_inner;
gradient.split(x, x_outer, x_inner, 2);
gradient.unroll(x_inner);
相应的C++代码为:
printf("Equivalent C:\n");
for (int y = 0; y < 4; y++) {
    for (int x_outer = 0; x_outer < 2; x_outer++) {
        // Instead of a for loop over x_inner, we get two
        // copies of the innermost statement.
        {
            int x_inner = 0;
            int x = x_outer * 2 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
        {
            int x_inner = 1;
            int x = x_outer * 2 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
    }
}

融合、平铺、并行 Fusing, tiling, and parallelizing

这一步中,我们将融合、平铺和并行操作都融合到一起,来对一个8×8的图像进行操作。首先,我们将x轴和y轴都按照4因子进行平铺操作。随后我们将外侧的y和外侧的x轴循环进行融合(2+2=4),再将这个融合后的操作进行并行操作,也就是同时执行这四个(2+2=4)操作:

Var x_outer, y_outer, x_inner, y_inner, tile_index;
gradient.tile(x, y, x_outer, y_outer, x_inner, y_inner, 4, 4);
gradient.fuse(x_outer, y_outer, tile_index);
gradient.parallel(tile_index);

相应的C++代码为:

// This outermost loop should be a parallel for loop, but that's hard in C.
for (int tile_index = 0; tile_index < 4; tile_index++) {
    int y_outer = tile_index / 2;
    int x_outer = tile_index % 2;
    for (int y_inner = 0; y_inner < 4; y_inner++) {
        for (int x_inner = 0; x_inner < 4; x_inner++) {
            int y = y_outer * 4 + y_inner;
            int x = x_outer * 4 + x_inner;
            printf("Evaluating at x = %d, y = %d: %d\n", x, y, x + y);
        }
    }



可视化后的结果,可以看到8×8中左上、左下、右上、右下四个区域是几乎同时进行的(tile_index),而每个区域和之前tile那一节的计算方式是一样的,只不过这次换成了并行计算:

整合

这次来点大点的图像,我们输入的图像大小为350 x 250,对其进行最优化的操作:
首先我们将其按照64 x 64的因子进行平铺,其次融合y轴和x轴外侧的循环操作数,最后对其进行并行操作
(这里注意下,我们可以看到350或者250并不能被64整除,这个不用担心,Halide会自动处理多余或者不够的部分)。



Var x_outer, y_outer, x_inner, y_inner, tile_index;
gradient_fast
    .tile(x, y, x_outer, y_outer, x_inner, y_inner, 64, 64)
    .fuse(x_outer, y_outer, tile_index)
    .parallel(tile_index);
// 可以这样连续使用.写,因为对象函数返回的是对象本身的引用

这样还不够,上面我们已经将整个图像平铺为6*4个部分,而这一步中对每个平铺后的部分再进行一次平铺操作,这次将每个小块按照4×2的形式平铺为,其中y_inner_outer分成两个(每个为y_pairs),x_inner_outer分成四个(每个为x_vectors),然后将每个x_vectors并行化,将y_pairs展开。

Var x_inner_outer, y_inner_outer, x_vectors, y_pairs;
gradient_fast
    .tile(x_inner, y_inner, x_inner_outer, y_inner_outer, x_vectors, y_pairs, 4, 2)
    .vectorize(x_vectors)
    .unroll(y_pairs);


对应的c++展示代码为:
for (int tile_index = 0; tile_index < 6 * 4; tile_index++) {
    int y_outer = tile_index / 4;
    int x_outer = tile_index % 4;
    for (int y_inner_outer = 0; y_inner_outer < 64/2; y_inner_outer++) {
        for (int x_inner_outer = 0; x_inner_outer < 64/4; x_inner_outer++) {
            // We're vectorized across x
            int x = std::min(x_outer * 64, 350-64) + x_inner_outer*4;
            int x_vec[4] = {x + 0,
                            x + 1,
                            x + 2,
                            x + 3};

            // And we unrolled across y
            int y_base = std::min(y_outer * 64, 250-64) + y_inner_outer*2;
            {
                // y_pairs = 0
                int y = y_base + 0;
                int y_vec[4] = {y, y, y, y};
                int val[4] = {x_vec[0] + y_vec[0],
                                x_vec[1] + y_vec[1],
                                x_vec[2] + y_vec[2],
                                x_vec[3] + y_vec[3]};

                // Check the result.
                for (int i = 0; i < 4; i++) {
                    if (result(x_vec[i], y_vec[i]) != val[i]) {
                        printf("There was an error at %d %d!\n",
                                x_vec[i], y_vec[i]);
                        return -1;
                    }
                }
            }
            {
                // y_pairs = 1
                int y = y_base + 1;
                int y_vec[4] = {y, y, y, y};
                int val[4] = {x_vec[0] + y_vec[0],
                                x_vec[1] + y_vec[1],
                                x_vec[2] + y_vec[2],
                                x_vec[3] + y_vec[3]};

                // Check the result.
                for (int i = 0; i < 4; i++) {
                    if (result(x_vec[i], y_vec[i]) != val[i]) {
                        printf("There was an error at %d %d!\n",
                                x_vec[i], y_vec[i]);
                        return -1;
                    }
                }
            }
        }
    }
}

模糊(blur)文章来源地址https://www.toymoban.com/news/detail-858974.html

如果用Halide来写文章一开头描述的模糊(blur)算法的话,会是这个样子:
Func blur_3x3(Func input) {
  Func blur_x, blur_y;
  Var x, y, xi, yi;

  // The algorithm - no storage or order
  blur_x(x, y) = (input(x-1, y) + input(x, y) + input(x+1, y))/3;
  blur_y(x, y) = (blur_x(x, y-1) + blur_x(x, y) + blur_x(x, y+1))/3;

  // The schedule - defines order, locality; implies storage
  blur_y.tile(x, y, xi, yi, 256, 32)
        .vectorize(xi, 8).parallel(y);
  blur_x.compute_at(blur_y, x).vectorize(x, 8);

  return blur_y;
}


到了这里,关于Halide 高效的图像处理语言 简化图像编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 构建基于AWSLambda的人工智能应用:语音识别、图像识别和自然语言处理

    作者:禅与计算机程序设计艺术 在人工智能领域,用大数据、机器学习等方法来解决复杂的问题,已经成为越来越多企业和开发者关注的问题。但是,如何把这些方法落地到生产环境中,仍然是一个难题。 随着云计算平台的广泛普及,AWS Lambda作为一项服务正在成为各个公司

    2024年02月09日
    浏览(77)
  • 基于python的神经网络在图像识别和自然语言处理上的应用

    随着计算机算力的不断增长,基于机器学习(神经网络)的方法成为了自然语言处理以及计算机图像识别的新范式。在自然语言处理方面,RNN、LSTM等方法能够有效提取语言之间的前后顺序和相互关系,在机器翻译、语言生成等任务取得了非常好的效果。在图像识别、图像检测

    2023年04月27日
    浏览(46)
  • 深度探析卷积神经网络(CNN)在图像视觉与自然语言处理领域的应用与优势

    卷积神经网络(CNN)作为一种强大的深度学习模型,在图像视觉和自然语言处理领域展现出了广泛的应用。其独特的网络结构以及层次化的特征学习使其成为目标检测、语音识别、视频分类以及文本分类等多个领域的重要工具。本文将深入探讨CNN在这些领域中的具体应用,并

    2024年01月16日
    浏览(48)
  • 基于OpenCV的传统视觉应用 -- OpenCV图像处理 图像模糊处理 图像锐化处理

    图像处理是用计算机对图像进行分析,以获取所需结果的过程,又称为影像处理。图像处理一般是指数字图像的处理。数字图像是用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组,该数组的元素称为像素,其值称为灰度值。 均值滤波是指任意一点的像素

    2024年02月07日
    浏览(74)
  • 彩色图像处理之彩色图像直方图处理的python实现——数字图像处理

    彩色图像的直方图处理是一种重要的图像处理技术,用于改善图像的视觉效果,增强图像的对比度,或为后续的图像处理任务(如图像分割、特征提取)做准备。彩色图像通常由红色(R)、绿色(G)、蓝色(B)三个颜色通道组成,因此彩色图像的直方图处理相比单色图像更

    2024年01月23日
    浏览(68)
  • 数字图像处理实验——数字图像处理初步

    一、实验目的与要求 1.熟悉及掌握在MATLAB中能够处理哪些格式的图像; 2.熟练掌握在MATLAB中如何读取图像及图像的属性信息(大小、颜色、亮度(灰度)、宽度、高度等); 3.掌握如何在MATLAB中按照指定要求存储一副图像的方法; 4.了解图像的算术运算在数字图像中的初步应

    2024年02月04日
    浏览(53)
  • 数字信号与图像处理实验三:图像处理基础与图像变换

    ​ 通过本实验加深对数字图像的理解,熟悉MATLAB中的有关函数;应用DCT对图像进行变换;熟悉图像常见的统计指标,实现图像几何变换的基本方法。 ​ 选择两幅图像,读入图像并显示,同时使用Matlab计算图像的大小,灰度平均值、协方差矩阵、灰度标准差和相关系数。 DC

    2024年02月04日
    浏览(61)
  • 数字图像处理第六章——彩色图像处理

    目录 引言 一、彩色基础 二、彩色模型 2.1 RGB彩色模型 2.2 CMY和CMYK彩色模型  2.3 HSI彩色模型 三、伪彩色图像处理 3.1 灰度分层 3.2 灰度到彩色的变换 四、彩色变换 ​编辑色调与色彩校正 五、平滑与锐化 5.1 平滑 5.2 锐化         在图像处理中,彩色的运用受两个主要因素

    2024年02月09日
    浏览(64)
  • Matlab图像处理基础(1):图像表示,点处理

    目录 0. 概要 1. 图像表示 Image Representation 1.1 图像格式 Image format 1.2 图像分辨率 resolution of image 1.3 图像的编码 1.4 Matlab图像加载、显示和保存  1.5 Image Information 1.6 图像格式转换 1.7 其它类型的像素 1.8 像素数值格式 1.9 图像数据的访问和引用 3. 点处理 Point Processing 3.1 关于像

    2023年04月08日
    浏览(52)
  • Python图像处理【23】分布式图像处理

    Python 已逐渐成为数据分析/处理领域中的主要语言,这得益于 Python 丰富的第三方库,但是,这些库的设计并未在分布式上进行扩展。 Dask 是为了原生地扩展这些 Python 库及其生态系统而开发的,它能够与现有的 Python 生态系统兼容,将其扩展到多核计算机和分布式集群中。

    2024年03月23日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包