Android BlurFilter處理流程

前言

從Android4.0開始,Android源碼開始合入BlurFilter(模糊濾波)相關(guān)算法扼鞋,提供模糊(毛玻璃)效果支持申鱼,幫助上層應(yīng)用在使用窗口、視圖或圖片時(shí)更有層次感云头。圖像處理的模糊濾波算法有很多捐友,包括常見的均值模糊和高斯模糊,Android 12采用的模糊濾波為Kawase濾波算法盘寡。

卷積

提及圖像濾波一定需要講到卷積楚殿。在圖像處理的基礎(chǔ)原理中,卷積是圖像處理的基本操作竿痰。通過 圖像源矩陣卷積核 得到 結(jié)果矩陣 脆粥,這里的結(jié)果矩陣即單次圖像處理的結(jié)果。

卷積.png

均值/高斯/Kawase濾波器

不同的濾波器采用的卷積核各不相同影涉,以下列三種為例:

  • 均值濾波器

    均值濾波器的卷積核通常是一個(gè)m*m的矩陣变隔,且每個(gè)元素為均等的1/(m*m);

  • 高斯濾波器

    高斯濾波器的卷積核中的元素遵循高斯分布蟹倾,卷積核中心的值相對(duì)邊緣較大匣缘,因此在做卷積處理時(shí)中心點(diǎn)的權(quán)重最大猖闪,權(quán)重逐步向邊緣遞減。

  • Kawase濾波器

    Kawase濾波器采用的是取中心點(diǎn)向外的四個(gè)點(diǎn)進(jìn)行采樣肌厨,且隨著迭代次數(shù)增加向外擴(kuò)張更遠(yuǎn)的采樣距離

Kawase_1.png
Kawase_2.png

通過對(duì)模糊品質(zhì)培慌、穩(wěn)定性和性能三個(gè)標(biāo)準(zhǔn)的比較(引用十種模糊算法比較

  • 模糊品質(zhì):模糊品質(zhì)的好壞是模糊算法是否優(yōu)秀的主要指標(biāo)
  • 模糊穩(wěn)定性:模糊的穩(wěn)定性決定了在畫面變化過程中,模糊是否穩(wěn)定柑爸,不會(huì)出現(xiàn)跳變或者閃爍吵护。
  • 性能:性能的好壞是模糊算法是否能被廣泛使用的關(guān)鍵所在。
十種模糊算法比較.jpg

RenderEngine

在圖像顯示系統(tǒng)中表鳍,需要使用特定的渲染引擎通過GPU繪制到暫存緩沖區(qū)(采用Client合成方式的圖層)馅而,因此在設(shè)備啟動(dòng)時(shí)會(huì)進(jìn)行渲染引擎的創(chuàng)建。

渲染引擎的選擇是根據(jù)renderEngineType來確定的譬圣。在Android 12 中默認(rèn)選擇SKIA_GL_THREADED瓮恭,Android 11 默認(rèn)選擇GLES。前者是采用異步的SKIA_GL厘熟,底層實(shí)現(xiàn)是在OpenGL基礎(chǔ)上做的封裝屯蹦, 后者是直接調(diào)用OpenGL接口并使用GLES語言進(jìn)行著色器編輯。

private:
    // 1 means RGBA_8888
    int pixelFormat = 1;
    uint32_t imageCacheSize = 0;
    bool useColorManagement = true;
    bool enableProtectedContext = false;
    bool precacheToneMapperShaderOnly = false;
    bool supportsBackgroundBlur = false;
    RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM;
    RenderEngine::RenderEngineType renderEngineType =
            RenderEngine::RenderEngineType::SKIA_GL_THREADED;
};

開發(fā)人員可根據(jù)設(shè)置DEBUG_RENDERENGINE_BACKEND參數(shù)為“gles/threaded/skiagl/skiaglthreaded"選擇渲染引擎盯漂。

[\framework\native\libs\renderengine\RenderEngine.cpp]
RenderEngineType renderEngineType = args.renderEngineType;

    // Keep the ability to override by PROPERTIES:
    char prop[PROPERTY_VALUE_MAX];
    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
    if (strcmp(prop, "gles") == 0) {
        renderEngineType = RenderEngineType::GLES;
    }
    if (strcmp(prop, "threaded") == 0) {
        renderEngineType = RenderEngineType::THREADED;
    }
    if (strcmp(prop, "skiagl") == 0) {
        renderEngineType = RenderEngineType::SKIA_GL;
    }
    if (strcmp(prop, "skiaglthreaded") == 0) {
        renderEngineType = RenderEngineType::SKIA_GL_THREADED;
    }
switch (renderEngineType) {
        case RenderEngineType::THREADED:
            ALOGD("Threaded RenderEngine with GLES Backend");
            return renderengine::threaded::RenderEngineThreaded::create(
                    [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); },
                    renderEngineType);
        case RenderEngineType::SKIA_GL:
            ALOGD("RenderEngine with SkiaGL Backend");
            return renderengine::skia::SkiaGLRenderEngine::create(args);
        case RenderEngineType::SKIA_GL_THREADED: {
            // These need to be recreated, since they are a constant reference, and we need to
            // let SkiaRE know that it's running as threaded, and all GL operation will happen on
            // the same thread.
            RenderEngineCreationArgs skiaArgs =
                    RenderEngineCreationArgs::Builder()
                            .setPixelFormat(args.pixelFormat)
                            .setImageCacheSize(args.imageCacheSize)
                            .setUseColorManagerment(args.useColorManagement)
                            .setEnableProtectedContext(args.enableProtectedContext)
                            .setPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly)
                            .setSupportsBackgroundBlur(args.supportsBackgroundBlur)
                            .setContextPriority(args.contextPriority)
                            .setRenderEngineType(renderEngineType)
                            .build();
            ALOGD("Threaded RenderEngine with SkiaGL Backend");
            return renderengine::threaded::RenderEngineThreaded::create(
                    [skiaArgs]() {
                        return android::renderengine::skia::SkiaGLRenderEngine::create(skiaArgs);
                    },
                    renderEngineType);
        }
        case RenderEngineType::GLES:
        default:
            ALOGD("RenderEngine with GLES Backend");
            return renderengine::gl::GLESRenderEngine::create(args);
    }

GLESRenderEngine

以Android 11 為例颇玷,因?yàn)閞enderEngineType默認(rèn)為GLES,因此在渲染引擎初始化時(shí)會(huì)選擇GLESRenderEngine進(jìn)行創(chuàng)建就缆。在創(chuàng)建過程中如果設(shè)備支持Blur則創(chuàng)建BlurFilter對(duì)象帖渠。

[\framework\native\libs\renderengine\gl\GLESRenderEngine.cpp]
GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                   EGLConfig config, EGLContext ctxt, EGLSurface stub,
                                   EGLContext protectedContext, EGLSurface protectedStub)
      : RenderEngine(args.renderEngineType),
        mEGLDisplay(display),
        mEGLConfig(config),
        mEGLContext(ctxt),
        mStubSurface(stub),
        mProtectedEGLContext(protectedContext),
        mProtectedStubSurface(protectedStub),
        mVpWidth(0),
        mVpHeight(0),
        mFramebufferImageCacheSize(args.imageCacheSize),
        mUseColorManagement(args.useColorManagement),
        mPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly) {
    ...
    if (args.supportsBackgroundBlur) {
        mBlurFilter = new BlurFilter(*this);
        checkErrors("BlurFilter creation");
    }
    ...
}

BlurFilter初始化時(shí),標(biāo)識(shí)統(tǒng)一變量鏈接著色器中的屬性值部分會(huì)有一定耗時(shí)竭宰,在初始化中完成能夠減少因代碼邏輯問題的反復(fù)鏈接導(dǎo)致的渲染性能降低空郊。

[\framework\native\libs\renderengine\gl\filters\BlurFilter.cpp]
BlurFilter::BlurFilter(GLESRenderEngine& engine)
      : mEngine(engine),
        mCompositionFbo(engine),
        mPingFbo(engine),
        mPongFbo(engine),
        mMixProgram(engine),
        mBlurProgram(engine) {
    mMixProgram.compile(getVertexShader(), getMixFragShader());
    mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
    mMUvLoc = mMixProgram.getAttributeLocation("aUV");
    mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
    mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
    mMMixLoc = mMixProgram.getUniformLocation("uMix");

    mBlurProgram.compile(getVertexShader(), getFragmentShader());
    mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
    mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
    mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
    mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");

    static constexpr auto size = 2.0f;
    static constexpr auto translation = 1.0f;
    const GLfloat vboData[] = {
        // Vertex data
        translation - size, -translation - size,
        translation - size, -translation + size,
        translation + size, -translation + size,
        // UV data
        0.0f, 0.0f - translation,
        0.0f, size - translation,
        size, size - translation
    };
    mMeshBuffer.allocateBuffers(vboData, 12 /* size */);
}

在GLESRenderEngine初始化創(chuàng)建完成后,如果SurfaceFlinger采用Client合成方式切揭,那么各個(gè)Layer的內(nèi)容會(huì)用GPU渲染到暫存緩沖區(qū)狞甚,最后將暫存緩沖區(qū)傳送到顯示組件。具體使用GLESRenderEngine::drawLayers將所有圖層繪制到該緩沖區(qū)廓旬。

[\framework\native\libs\renderengine\gl\GLESRenderEngine.cpp]
status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,
                                      const std::vector<const LayerSettings*>& layers,
                                      const std::shared_ptr<ExternalTexture>& buffer,
                                      const bool useFramebufferCache, base::unique_fd&& bufferFence,
                                      base::unique_fd* drawFence) {
    ATRACE_CALL();
    if (layers.empty()) {
        ALOGV("Drawing empty layer stack");
        return NO_ERROR;
    }

    if (bufferFence.get() >= 0) {
        // Duplicate the fence for passing to waitFence.
        base::unique_fd bufferFenceDup(dup(bufferFence.get()));
        if (bufferFenceDup < 0 || !waitFence(std::move(bufferFenceDup))) {
            ATRACE_NAME("Waiting before draw");
            sync_wait(bufferFence.get(), -1);
        }
    }
    ...

在針對(duì)每個(gè)Layer的渲染過程中哼审,如果該圖層被設(shè)置進(jìn)行模糊濾波處理,則通過BlurFilter::prepare進(jìn)行Kawase濾波算法的圖像渲染孕豹。這里的做法是先縮小整個(gè)圖像進(jìn)行高效率的模糊處理涩盾,再通過BlurFilter::render放大模糊效果,將使用較大的合成紋理對(duì)其進(jìn)行插值得到一幀畫面励背,以隱藏縮小的瑕疵春霍。

[\framework\native\libs\renderengine\gl\GLESRenderEngine.cpp]
status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,
                                      const std::vector<const LayerSettings*>& layers,
                                      const std::shared_ptr<ExternalTexture>& buffer,
                                      const bool useFramebufferCache, base::unique_fd&& bufferFence,
                                      base::unique_fd* drawFence) {
    ...
    for (auto const layer : layers) {
        if (blurLayers.size() > 0 && blurLayers.front() == layer) {
            blurLayers.pop_front();

            auto status = mBlurFilter->prepare();
            ...
            if (blurLayers.size() == 0) {
                // Done blurring, time to bind the native FBO and render our blur onto it.
                fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
                                                                      buffer.get()
                                                                              ->getBuffer()
                                                                              ->getNativeBuffer(),
                                                                      useFramebufferCache);
                status = fbo->getStatus();
                setViewportAndProjection(display.physicalDisplay, display.clip);
            } else {
                // There's still something else to blur, so let's keep rendering to our FBO
                // instead of to the display.
                status = mBlurFilter->setAsDrawTarget(display,
                                                      blurLayers.front()->backgroundBlurRadius);
            }
            ...
            status = mBlurFilter->render(blurLayersSize > 1);
            ...
        }
    ...
}

BlurFilter——GL

具體到模糊濾波的代碼,在BlurFilter::prepare中叶眉,通過傳入的模糊半徑參數(shù)mRadius計(jì)算模糊限制步長stepX/stepY和kawase模糊迭代次數(shù)passes址儒。Android 12中限定的最多迭代次數(shù)為6次芹枷。

[\frameworks\native\libs\renderengine\gl\filters\BlurFilter.cpp]
status_t BlurFilter::prepare() {
    ATRACE_NAME("BlurFilter::prepare");

    const auto radius = mRadius / 6.0f;
    // Calculate how many passes we'll do, based on the radius.
    // Too many passes will make the operation expensive.
    const auto passes = min(kMaxPasses, (uint32_t)ceil(radius));

    const float radiusByPasses = radius / (float)passes;
    const float stepX = radiusByPasses / (float)mCompositionFbo.getBufferWidth();
    const float stepY = radiusByPasses / (float)mCompositionFbo.getBufferHeight();
   ...
}

傳入紋理信息mCompositionFbo.getTextureName()、步長stepX莲趣,stepY進(jìn)行片段渲染鸳慈。

status_t BlurFilter::prepare() {
    ...
    // Let's start by downsampling and blurring the composited frame simultaneously.
    mBlurProgram.useProgram();
    glActiveTexture(GL_TEXTURE0);
    glUniform1i(mBTextureLoc, 0);
    glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
    glUniform2f(mBOffsetLoc, stepX, stepY);
    glViewport(0, 0, mPingFbo.getBufferWidth(), mPingFbo.getBufferHeight());
    mPingFbo.bind();
    drawMesh(mBUvLoc, mBPosLoc);
    ...
}

調(diào)用OpenGL API 執(zhí)行單次模糊渲染。

void BlurFilter::drawMesh(GLuint uv, GLuint position) {

    glEnableVertexAttribArray(uv);
    glEnableVertexAttribArray(position);
    mMeshBuffer.bind();
    glVertexAttribPointer(position, 2 /* size */, GL_FLOAT, GL_FALSE,
                          2 * sizeof(GLfloat) /* stride */, 0 /* offset */);
    glVertexAttribPointer(uv, 2 /* size */, GL_FLOAT, GL_FALSE, 0 /* stride */,
                          (GLvoid*)(6 * sizeof(GLfloat)) /* offset */);
    mMeshBuffer.unbind();

    // draw mesh
    glDrawArrays(GL_TRIANGLES, 0 /* first */, 3 /* count */);
}

原生限制最多6次的迭代模糊渲染妖爷,通過兩個(gè)幀緩沖區(qū)read和draw做離屏讀寫緩沖蝶涩,使用幀緩沖區(qū)渲染到紋理的方式將結(jié)果紋理作為下次渲染所需要的紋理圖片理朋,迭代反復(fù)渲染絮识。

status_t BlurFilter::prepare() {

    ...
    // And now we'll ping pong between our textures, to accumulate the result of various offsets.
    GLFramebuffer* read = &mPingFbo;
    GLFramebuffer* draw = &mPongFbo;
    glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
    for (auto i = 1; i < passes; i++) {
        ATRACE_NAME("BlurFilter::renderPass");
        draw->bind();
j
        glBindTexture(GL_TEXTURE_2D, read->getTextureName());
        glUniform2f(mBOffsetLoc, stepX * i, stepY * i);

        drawMesh(mBUvLoc, mBPosLoc);

        // Swap buffers for next iteration
        auto tmp = draw;
        draw = read;
        read = tmp;
    }
    mLastDrawTarget = read;

    return NO_ERROR;
}

原生GL渲染引擎的著色器采用Kawase算法,著色器的修改和自定義可以在該部分完成嗽上。需要注意的是修改著色器中的濾波算法可能由于算法限制導(dǎo)致渲染效率低次舌,畫面顯示幀率降低。

string BlurFilter::getFragmentShader() const {
    return R"SHADER(#version 300 es
        precision mediump float;

        uniform sampler2D uTexture;
        uniform vec2 uOffset;

        in highp vec2 vUV;
        out vec4 fragColor;

        void main() {
            fragColor  = texture(uTexture, vUV, 0.0);
            fragColor += texture(uTexture, vUV + vec2( uOffset.x,  uOffset.y), 0.0);
            fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
            fragColor += texture(uTexture, vUV + vec2(-uOffset.x,  uOffset.y), 0.0);
            fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);

            fragColor = vec4(fragColor.rgb * 0.2, 1.0);
        }
    )SHADER";
}

BlurFilter——Skia

Android 12 上針對(duì)模糊濾波的相關(guān)內(nèi)容同Android 11兽愤,不過是由原來的GL換成了Skia彼念。內(nèi)部的模糊濾波算法和執(zhí)行流程近似相同。

以下簡(jiǎn)單列出Skia中BlurFilter源碼浅萧,其中BlurFilter::generate類比GL中的BlurFilter::prepare

sk_sp<SkImage> BlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
                                    const sk_sp<SkImage> input, const SkRect& blurRect) const {
    ...
    // And now we'll build our chain of scaled blur stages
    for (auto i = 1; i < numberOfPasses; i++) {
        const float stepScale = (float)i * kInputScale;
        blurBuilder.child("input") =
                tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
        blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale, stepY * stepScale};
        blurBuilder.uniform("in_maxSizeXY") =
                SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
        tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
    }

    return tmpBlur;
}

Skia中的BlurFilter::drawBlurRegion類比GL中的BlurFilter::render逐沙。

void BlurFilter::drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
                                const uint32_t blurRadius, const float blurAlpha,
                                const SkRect& blurRect, sk_sp<SkImage> blurredImage,
                                sk_sp<SkImage> input) {
    ATRACE_CALL();

    SkPaint paint;
    paint.setAlphaf(blurAlpha);

    const auto blurMatrix = getShaderTransform(canvas, blurRect, kInverseInputScale);
    SkSamplingOptions linearSampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
    const auto blurShader = blurredImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                                     linearSampling, &blurMatrix);

    if (blurRadius < kMaxCrossFadeRadius) {
        // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
        // case you might expect the matrix to simply be the canvas matrix.
        SkMatrix inputMatrix;
        if (!canvas->getTotalMatrix().invert(&inputMatrix)) {
            ALOGE("matrix was unable to be inverted");
        }

        SkRuntimeShaderBuilder blurBuilder(mMixEffect);
        blurBuilder.child("blurredInput") = blurShader;
        blurBuilder.child("originalInput") =
                input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
                                  inputMatrix);
        blurBuilder.uniform("mixFactor") = blurRadius / kMaxCrossFadeRadius;

        paint.setShader(blurBuilder.makeShader(nullptr, true));
    } else {
        paint.setShader(blurShader);
    }
    ...
}

注意事項(xiàng)

  • 不同于gl傳入片段著色器的是紋理映射坐標(biāo),范圍是(0洼畅,1)吩案;skia中傳入片段著色器的是屏幕頂點(diǎn)坐標(biāo),范圍是(screenW帝簇,screenH)徘郭。

  • 本文介紹的BlurFilter處理流程都是SurfaceFlinger采用Client合成方式的圖層,因此會(huì)通過renderEngine渲染后傳給HWC的Layer list丧肴,如果采用Device合成方式的圖層不會(huì)經(jīng)過renderEngine渲染残揉,而是直接將該圖層對(duì)應(yīng)的GraphicBuffer的buffer handle放入HWC的Layer list。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芋浮,一起剝皮案震驚了整個(gè)濱河市抱环,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纸巷,老刑警劉巖镇草,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異何暇,居然都是意外死亡陶夜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門裆站,熙熙樓的掌柜王于貴愁眉苦臉地迎上來条辟,“玉大人黔夭,你說我怎么就攤上這事∮鸬眨” “怎么了本姥?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杭棵。 經(jīng)常有香客問我婚惫,道長,這世上最難降的妖魔是什么魂爪? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任先舷,我火速辦了婚禮,結(jié)果婚禮上滓侍,老公的妹妹穿的比我還像新娘蒋川。我一直安慰自己,他們只是感情好撩笆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布捺球。 她就那樣靜靜地躺著,像睡著了一般夕冲。 火紅的嫁衣襯著肌膚如雪氮兵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天歹鱼,我揣著相機(jī)與錄音泣栈,去河邊找鬼。 笑死醉冤,一個(gè)胖子當(dāng)著我的面吹牛秩霍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚁阳,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼铃绒,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了螺捐?” 一聲冷哼從身側(cè)響起颠悬,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎定血,沒想到半個(gè)月后赔癌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澜沟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年灾票,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茫虽。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刊苍,死狀恐怖既们,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情正什,我是刑警寧澤啥纸,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站婴氮,受9級(jí)特大地震影響斯棒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜主经,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一荣暮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旨怠,春花似錦渠驼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽百揭。三九已至爽哎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間器一,已是汗流浹背课锌。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祈秕,地道東北人渺贤。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像请毛,于是被迫代替她去往敵國和親志鞍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 圖像預(yù)處理主要包括去噪方仿、對(duì)比度增強(qiáng)固棚,去噪和對(duì)比度增強(qiáng)方法順序不唯一,根據(jù)實(shí)際情況作出最好的安排仙蚜。 1此洲、灰度化 ht...
    景寶寶1號(hào)閱讀 18,651評(píng)論 0 4
  • 基本概念圖像的平滑也就是圖像的模糊處理,簡(jiǎn)單但是使用頻率很高委粉,在執(zhí)行許多高級(jí)處理之前都需要先進(jìn)性圖像的平滑處理呜师,以...
    昵稱真難選閱讀 740評(píng)論 2 3
  • 目錄如下: 音視頻基礎(chǔ)技術(shù)棧 ? 抖音/快手等短視頻APP的風(fēng)靡,讓音視頻成為當(dāng)下最火熱的技術(shù)贾节,越來越多的人想...
    LukiYLS閱讀 816評(píng)論 1 0
  • 本文研究場(chǎng)景為大圖標(biāo)顯示為較小圖標(biāo)時(shí)汁汗,消除鋸齒的處理方法趟紊,不適用于放大圖片的場(chǎng)景。建議縮小倍數(shù)大于兩倍時(shí)再使用本文...
    無人接聽閱讀 6,768評(píng)論 0 4
  • Android實(shí)驗(yàn)室CV培訓(xùn) 傅里葉變換(空間域 -> 頻率域) 什么是傅里葉變換碰酝? 維基百科: 傅里葉變換(法語...
    亞歐沙龍閱讀 883評(píng)論 0 6