Bokeh与Numba结合实现交互式图像处理教程
概述
本教程将展示如何利用Bokeh可视化库与Numba加速计算库,在Jupyter Notebook中实现高效的交互式图像处理。通过这个教程,你将学习到:
- 如何使用Bokeh在Notebook中展示图像
- 如何利用Numba加速图像处理算法
- 如何实现无服务器(serverless)的交互式图像处理
环境准备
在开始之前,请确保已安装以下Python库:
- Bokeh:用于创建交互式可视化
- Numba:用于加速数值计算
- NumPy:用于数组操作
- SciPy:提供示例图像数据
- ipywidgets:用于创建交互控件
基础图像处理:高斯模糊
1. 加载和显示图像
首先,我们加载一张示例图像并显示在Bokeh画布上:
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import GlyphRenderer
import numpy as np
import scipy.misc
# 加载并预处理图像
img_blur = (scipy.misc.ascent()[::-1,:]/255.0)[:250, :250].copy(order='C')
# 创建灰度调色板
palette = [f'#{i:02x}{i:02x}{i:02x}' for i in range(256)]
# 创建Bokeh图像展示
width, height = img_blur.shape
p_blur = figure(x_range=(0, width), y_range=(0, height))
r_blur = p_blur.image(image=[img_blur], x=[0], y=[0], dw=[width], dh=[height], palette=palette, name='blur')
# 显示图像
t_blur = show(p_blur, notebook_handle=True)
2. 使用Numba加速高斯模糊算法
我们使用Numba的@njit
装饰器来加速高斯模糊算法:
from numba import njit
@njit
def blur(outimg, img, amt):
iw, ih = img.shape
for i in range(amt, iw-amt):
for j in range(amt, ih-amt):
px = 0.
for w in range(-amt//2, amt//2):
for h in range(-amt//2, amt//2):
px += img[i+w, j+h]
outimg[i, j]= px/(amt*amt)
3. 创建交互控件
通过ipywidgets创建滑块控件,实现交互式模糊效果:
from ipywidgets import interact
from bokeh.io import push_notebook
def update(i=0):
level = 2*i + 1
out = img_blur.copy()
# 应用模糊效果
blur(out, img_blur, level)
# 更新Bokeh显示
r_blur.data_source.data['image'] = [out]
push_notebook(handle=t_blur)
# 创建滑块控件
interact(update, i=(0, 10))
进阶应用:3x3图像卷积核
1. 卷积核工厂函数
我们创建一个通用的卷积核应用函数,支持边缘处理:
from numba import jit
@jit
def getitem(img, x, y):
# 处理边缘像素
w, h = img.shape
if x >= w:
x = w - 1 - (x - w)
if y >= h:
y = h - 1 - (y - h)
return img[x, y]
def filter_factory(kernel):
# 归一化卷积核
ksum = np.sum(kernel)
if ksum == 0:
ksum = 1
k9 = kernel / ksum
@jit
def kernel_apply(img, out, x, y):
# 内部像素处理
tmp = 0
for i in range(3):
for j in range(3):
tmp += img[x+i-1, y+j-1] * k9[i, j]
out[x, y] = tmp
@jit
def kernel_apply_edge(img, out, x, y):
# 边缘像素处理
tmp = 0
for i in range(3):
for j in range(3):
tmp += getitem(img, x+i-1, y+j-1) * k9[i, j]
out[x, y] = tmp
@jit
def kernel_k9(img, out):
# 应用卷积核到整个图像
# 处理内部区域
for x in range(1, img.shape[0] -1):
for y in range(1, img.shape[1] -1):
kernel_apply(img, out, x, y)
# 处理边缘区域
for x in range(img.shape[0]):
kernel_apply_edge(img, out, x, 0)
kernel_apply_edge(img, out, x, img.shape[1] - 1)
for y in range(img.shape[1]):
kernel_apply_edge(img, out, 0, y)
kernel_apply_edge(img, out, img.shape[0] - 1, y)
return kernel_k9
2. 常用卷积核示例
我们定义了几种常见的图像处理卷积核:
# 平均模糊
average = np.array([
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
], dtype=np.float32)
# 锐化
sharpen = np.array([
[-1, -1, -1],
[-1, 12, -1],
[-1, -1, -1],
], dtype=np.float32)
# 边缘检测
edge = np.array([
[ 0, -1, 0],
[-1, 4, -1],
[ 0, -1, 0],
], dtype=np.float32)
# 其他卷积核...
3. 交互式卷积应用
创建交互界面,允许用户选择不同的卷积核和调整参数:
def update(image="ascent", kernel_name="none", scale=100, bias=0):
img_kernel = images.get(image)
# 应用选择的卷积核
kernel = kernels.get(kernel_name, None)
if kernel is None:
out = np.copy(img_kernel)
else:
out = np.zeros_like(img_kernel)
kernel(img_kernel, out)
# 调整亮度和对比度
out *= scale / 100.0
out += bias
# 更新显示
r_kernel.data_source.data['image'] = [out]
push_notebook(handle=t_kernel)
# 创建交互控件
knames = ["none", *sorted(kernels.keys())]
interact(update, image=["ascent" ,"face"], kernel_name=knames, scale=(10, 100, 10), bias=(0, 255))
高级应用:小波分解
1. Haar小波变换实现
我们使用Numba加速Haar小波变换:
@njit
def wavelet_decomposition(img, tmp):
"""
使用tmp作为临时缓冲区对img进行小波分解
"""
w, h = img.shape
halfwidth, halfheight = w//2, h//2
lefthalf, righthalf = tmp[:halfwidth, :], tmp[halfwidth:, :]
# 沿第一维度分解
for x in range(halfwidth):
for y in range(h):
lefthalf[x, y] = (img[2 * x, y] + img[2 * x + 1, y]) / 2
righthalf[x, y] = img[2 * x, y] - img[2 * x + 1, y]
# 交换缓冲区
img, tmp = tmp, img
tophalf, bottomhalf = tmp[:, :halfheight], tmp[:, halfheight:]
# 沿第二维度分解
for y in range(halfheight):
for x in range(w):
tophalf[x, y] = (img[x, 2 * y] + img[x, 2 * y + 1]) / 2
bottomhalf[x, y] = img[x, 2 * y] - img[x, 2 * y + 1]
return halfwidth, halfheight
2. 交互式小波分解
创建滑块控件,控制小波分解的层级:
def update(level=0):
out = np.copy(img_wavelet)
tmp = np.zeros_like(img_wavelet)
hw, hh = img_wavelet.shape
while level > 0 and hw > 1 and hh > 1:
hw, hh = wavelet_decomposition(out[:hw, :hh], tmp[:hw, :hh])
level -= 1
r_wavelet.data_source.data['image'] = [out]
push_notebook(handle=t_wavelet)
interact(update, level=(0, 7))
性能优化技巧
-
Numba使用建议:
- 使用
@njit
替代@jit
以获得更好的性能 - 确保数组是连续的(使用
order='C'
) - 避免在Numba函数中分配大量内存
- 使用
-
Bokeh交互优化:
- 使用
push_notebook
进行高效更新 - 避免频繁更新,可以添加去抖动(debounce)机制
- 对于复杂操作,考虑使用Bokeh服务器应用
- 使用
-
图像处理优化:
- 预处理图像到合适的大小
- 对多次操作的结果进行缓存
- 分离耗时操作和快速操作
总结
本教程展示了如何结合Bokeh和Numba在Jupyter Notebook中创建高效的交互式图像处理应用。通过这种组合,我们能够:
- 利用Numba加速计算密集型图像处理算法
- 使用Bokeh创建丰富的可视化界面
- 通过ipywidgets添加交互控件
- 实现无服务器的交互式体验
这种技术组合特别适合原型开发、教学演示和数据分析场景,能够快速实现想法并验证算法效果。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考