海康 MV 相机几种Bayer RG像素格式的处理
海康 MV 相机输出的像素格式为以下几种:

Bayer RG8 格式数据的解析见:
https://blog.csdn.net/xulibo5828/article/details/148781170
一、Bayer RG10 数据格式解析
1. 核心概念
- Bayer 格式:工业相机常用的原始图像格式,像素按特定规律排列(RG10 表示排列顺序为 R-G/B-G,即第一行是 R、G、R、G...,第二行是 G、B、G、B...)。
- RG10:
RG:Bayer 排列方式(也叫 BGGR 的镜像,核心是 R/G/B 的位置);10:每个像素占10 位(bit),而常规的 8 位图像(RGB8)每个像素占 8 位,因此 RG10 需要先做位深转换。
- 数据存储:RG10 数据通常以16 位无符号整数(uint16) 存储(因为 10 位无法用 8 位存储),有效数据是低 10 位,高 6 位为 0,取值范围是 0~1023(2^10-1)。
2. 数据结构
假设相机输出分辨率为 width × height:
- RG10 原始数据总字节数 = width × height × 2(每个 uint16 占 2 字节);
- 像素排列示例(RG10):
R(10bit) G(10bit) R(10bit) G(10bit) ... G(10bit) B(10bit) G(10bit) B(10bit) ... R(10bit) G(10bit) R(10bit) G(10bit) ...
3. OpenCV 转换为 RGB8 :
import cv2
import numpy as np
def rg10_to_rgb8(rg10_data: bytes, width: int, height: int) -> np.ndarray:
"""
将海康MV相机的Bayer RG10数据转换为RGB8格式图像
:param rg10_data: 相机输出的RG10原始字节数据
:param width: 图像宽度(像素)
:param height: 图像高度(像素)
:return: RGB8格式的numpy数组(uint8)
"""
# 步骤1:将字节数据转换为uint16数组(RG10原始数据)
# 注意:海康相机RG10数据通常为小端存储(little-endian)
rg10_array = np.frombuffer(rg10_data, dtype=np.uint16).reshape(height, width)
# 步骤2:10位数据归一化到8位(0~1023 → 0~255)
# 方法:除以 4(1023/4≈255.75,取整后为0~255),或使用cv2.normalize
rg8_array = (rg10_array // 4).astype(np.uint8)
# 步骤3:Bayer RG10(对应OpenCV的BAYER_RGGB格式)转RGB8
# OpenCV的cvtColor支持直接转换Bayer格式,注意匹配排列方式:
# RG10的排列 = BAYER_RGGB → 对应cv2.COLOR_BAYER_RG2RGB
rgb8_image = cv2.cvtColor(rg8_array, cv2.COLOR_BAYER_RG2RGB)
return rgb8_image
# ------------------- 测试示例 -------------------
if __name__ == "__main__":
# 模拟获取海康相机RG10数据(实际使用时替换为相机SDK读取的数据)
# 假设图像分辨率为1920×1080,生成随机RG10测试数据
width, height = 1920, 1080
test_rg10_data = np.random.randint(0, 1024, size=height*width, dtype=np.uint16).tobytes()
# 转换为RGB8
rgb_image = rg10_to_rgb8(test_rg10_data, width, height)
# 验证结果
print(f"RGB图像形状: {rgb_image.shape}") # 输出 (1080, 1920, 3)
print(f"数据类型: {rgb_image.dtype}") # 输出 uint8
# 显示图像(可选)
cv2.imshow("RGB8 Image", rgb_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存图像(可选)
cv2.imwrite("rgb8_output.jpg", rgb_image)
4. 关键细节说明
- Bayer 格式匹配:海康 RG10 对应的是 OpenCV 的
COLOR_BAYER_RG2RGB,如果转换后颜色异常,可尝试:cv2.COLOR_BAYER_BG2RGB(BGGR 排列);cv2.COLOR_BAYER_GR2RGB(GRBG 排列);cv2.COLOR_BAYER_GB2RGB(GBRG 排列)。
- 位深转换优化:如果需要更精准的归一化,可替换步骤 2 为:
rg8_array = cv2.normalize(rg10_array, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
5. RG10 处理的常见问题与解决方案
1). 位深转换的各种方式效率比较
除了之前的//4,还有更精准的归一化方式,适合对画质要求高的场景:
# 方式1:整数运算(速度快,无精度损失)
rg8_array = (rg10_array >> 2).astype(np.uint8) # 右移2位 ≈ 除以4,等价于//4
# 方式2:浮点归一化(更精准,适合动态范围要求高的场景)
rg8_array = np.clip((rg10_array / 1023) * 255, 0, 255).astype(np.uint8)
速度实测:
timer = QElapsedTimer()
timer.start()
img_8bit1 = np.clip((rg10_array / 1023) * 255, 0, 255).astype(np.uint8) # 使用比例换算方法
print(timer.elapsed()) # 71ms
timer.restart()
img_8bit2 = (rg10_array >> 2).astype(np.uint8) # 使用移位方法
print(timer.elapsed()) # 10ms
timer.restart()
img_8bit3 = (rg10_array // 4).astype(np.uint8) # 除以4的方法
print(timer.elapsed()) # 8ms
timer.restart()
img_8bit4 = cv2.normalize(rg10_array, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) # opencv归一化
print(timer.elapsed()) # 8ms
2). 海康相机 RG10 的端序问题
海康 MV 相机的 RG10 数据默认是小端(little-endian) 存储,若直接用np.frombuffer解析异常,需显式指定端序:
# 显式指定小端解析(兼容不同相机配置)
rg10_array = np.frombuffer(
rg10_data,
dtype=np.dtype('<u2') # < 表示小端,u2表示uint16
).reshape(height, width)
3). 实战:从海康 MVS SDK 读取 RG10 并验证
用海康官方 MVS SDK,读取 RG10 的真实代码片段(基于 C++/Python 封装):
import cv2
import numpy as np
from MvImport.MvCameraControl_class import *
def decoding_char(c_ubyte_value):
c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p)
try:
decode_str = c_char_p_value.value.decode('gbk') # Chinese characters
except UnicodeDecodeError:
decode_str = str(c_char_p_value.value)
return decode_str
# 转为16进制字符串
def To_hex_str(num):
chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'}
hexStr = ""
if num < 0:
num = num + 2 ** 32
while num >= 16:
digit = num % 16
hexStr = chaDic.get(digit, str(digit)) + hexStr
num //= 16
hexStr = chaDic.get(num, str(num)) + hexStr
return hexStr
def rg10_to_rgb8(rg10_data: bytes, width: int, height: int) -> np.ndarray:
"""
将海康MV相机的Bayer RG10数据转换为RGB8格式图像
:param rg10_data: 相机输出的RG10原始字节数据
:param width: 图像宽度(像素)
:param height: 图像高度(像素)
:return: RGB8格式的numpy数组(uint8)
"""
# 步骤1:将字节数据转换为uint16数组(RG10原始数据)
# 注意:海康相机RG10数据通常为小端存储(little-endian)
rg10_array = np.frombuffer(rg10_data, dtype=np.uint16).reshape(height, width)
# 步骤2:10位数据归一化到8位(0~1023 → 0~255)
# 方法:除以 4(1023/4≈255.75,取整后为0~255),或使用cv2.normalize
rg8_array = (rg10_array // 4).astype(np.uint8)
# 步骤3:Bayer RG10(对应OpenCV的BAYER_RGGB格式)转RGB8
# OpenCV的cvtColor支持直接转换Bayer格式,注意匹配排列方式:
# RG10的排列 = BAYER_RGGB → 对应cv2.COLOR_BAYER_RG2RGB
rgb8_image = cv2.cvtColor(rg8_array, cv2.COLOR_BAYER_RG2RGB)
return rgb8_image
def read_hik_rg10():
# 1. 枚举设备并打开相机
deviceList = MV_CC_DEVICE_INFO_LIST()
ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE, deviceList)
if ret != 0:
print("查找设备失败。 错误码[0x%x]" % ret)
return -1
if deviceList.nDeviceNum == 0: # 在线设备数量
print("未发现设备!")
return -1
mvcc_dev_info = cast(deviceList.pDeviceInfo[0], POINTER(MV_CC_DEVICE_INFO)).contents
cam = MvCamera()
ret = cam.MV_CC_CreateHandle(mvcc_dev_info) # 创建设备句柄
if ret != 0:
cam.MV_CC_DestroyHandle() # 销毁设备句柄
print("创建设备句柄失败。 错误码[0x%x]" % ret)
else:
cam.user_defined_name = decoding_char(
mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName) # 设备的用户定义名称(支持中文名称)
cam.model_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName) # 相机的型号
print("user_defined_name:", cam.user_defined_name)
print("model_name:", cam.model_name)
cam.MV_CC_OpenDevice()
ret = cam.MV_CC_StartGrabbing() # 开始取流
if ret != 0:
print(f"start grabbing fail! ret = {To_hex_str(ret)}")
cam.MV_CC_DestroyHandle() # 销毁设备句柄
return ret
pFrameBuffer = MV_FRAME_OUT() # 相机的输出图像帧对象
frame_info = MV_FRAME_OUT_INFO_EX() # 相机的输出图像帧信息对象
memset(byref(pFrameBuffer), 0, sizeof(pFrameBuffer)) # 初始化输出图像帧对象的内存区
ret = cam.MV_CC_GetImageBuffer(pFrameBuffer, 1000) # 超时1000ms
if ret != 0:
print(f"get image buffer fail! ret = {To_hex_str(ret)}")
cam.MV_CC_DestroyHandle() # 销毁设备句柄
return ret
memmove(byref(frame_info), byref(pFrameBuffer.stFrameInfo), sizeof(MV_FRAME_OUT_INFO_EX)) # 拷贝图像帧信息
l = frame_info.nFrameLen # 图像的帧长度
pixelType = frame_info.enPixelType # 图像的像素格式,enum的类型在PixelType_header.py内定义
nFrameNum = frame_info.nFrameNum # 图像的帧号
buf_bytes = (c_ubyte * l)() # 开辟存储字节流的缓存空间
# cdll.msvcrt.memcpy(byref(buf_bytes), stOutFrame.pBufAddr, l) # 拷贝图像帧的字节流数据
memmove(byref(buf_bytes), pFrameBuffer.pBufAddr, l) # 拷贝图像帧的字节流数据
# 4. 解析RG10数据(pFrameBuffer.pBuf是字节数据)
width = frame_info.nWidth
height = frame_info.nHeight
print(f"获取到图像帧,帧号: {nFrameNum}, 帧长度: {l}, pixelType: {pixelType}, width: {width}, height: {height}")
# 5. 释放资源
cam.MV_CC_FreeImageBuffer(pFrameBuffer)
cam.MV_CC_StopGrabbing()
cam.MV_CC_CloseDevice()
cam.MV_CC_DestroyHandle()
return buf_bytes, width, height
rg10_data, w, h = read_hik_rg10()
rgb8_img = rg10_to_rgb8(rg10_data, w, h)
cv2.imshow("rgb8_img", rgb8_img)
cv2.waitKey(0)
总结
- RG10 核心特征:10bit 有效数据(0~1023),以 uint16 存储,Bayer 排列为 RGGB,小端存储;
- 转换关键步骤:uint16 数组转换 → 10bit 归一化到 8bit → OpenCV 的 BAYER_RG2RGB 转换;
- 避坑点:确认 Bayer 排列(颜色异常时换 OpenCV 的 Bayer 转换枚举)、端序(小端)、分辨率匹配。
如果 RG10 数据转换后出现颜色偏色、画面乱码等问题,优先核对 Bayer 排列方式(换COLOR_BAYER_BG2RGB/COLOR_BAYER_GR2RGB试试),其次检查数据长度是否和宽高匹配(width×height×2 字节)。
二、RG12 格式
RG12 中的 12 就是指 12 位有效位深,和 RG10 的命名规则完全一致,是工业相机中另一类常用的 Bayer 原始格式,海康 MV 相机也广泛支持该格式。
1. 和 RG10 对标
| 特征 | RG12 | RG10 |
|---|---|---|
| 有效位深 | 12bit | 10bit |
| 取值范围 | 0 ~ 4095(2¹²-1) | 0 ~ 1023(2¹⁰-1) |
| 存储方式 | 封装为uint16(2 字节) | 封装为uint16(2 字节) |
| 无效位分布 | 高 4 位为 0,低 12 位为有效数据 | 高 6 位为 0,低 10 位为有效数据 |
| Bayer 排列 | 同 RG10,为RGGB(R-G/R-G,G-B/G-B) | RGGB |
| 存储端序 | 海康相机默认小端(little-endian) | 小端 |
RG12 的 16bit 存储结构:
| bit15 | bit14 | bit13 | bit12 | bit11 ~ bit0 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 有效 12bit 数据 |
2. 核心差异
核心差异仅在12bit→8bit 的位深归一化步骤,其余解析、Bayer 转换逻辑完全一致,直接复用 RG10 的流程即可,优化后通用代码如下:
import cv2
import numpy as np
def bayer_rgxx_to_rgb8(raw_data: bytes, width: int, height: int, bit_depth: int = 10) -> np.ndarray:
"""
通用海康RG10/RG12转RGB8函数
:param raw_data: 相机原始字节数据
:param width/height: 图像分辨率
:param bit_depth: 位深(10/RG10,12/RG12)
:return: RGB8格式numpy数组
"""
# 1. 小端解析为uint16数组(海康默认)
rgxx_array = np.frombuffer(raw_data, dtype=np.dtype('<u2')).reshape(height, width)
# 2. 不同位深归一化到8bit(0~255)
if bit_depth == 10:
rg8_array = (rgxx_array >> 2).astype(np.uint8) # 右移2位=除以4,1023→255
elif bit_depth == 12:
rg8_array = (rgxx_array >> 4).astype(np.uint8) # 右移4位=除以16,4095→255
else:
raise ValueError("仅支持10/12位深,对应RG10/RG12")
# 3. RGGB排列转RGB8(和RG10完全一致)
rgb8_image = cv2.cvtColor(rg8_array, cv2.COLOR_BAYER_RG2RGB)
return rgb8_image
# ------------------- 调用示例 -------------------
# RG12调用:指定bit_depth=12
# width, height = 相机实际分辨率
# rg12_data = 海康相机读取的RG12原始字节数据
# rgb8_img = bayer_rgxx_to_rgb8(rg12_data, width, height, bit_depth=12)
3. RG12 更精准的归一化方式(画质优先场景)
如果对图像动态范围要求高,不建议直接右移整数运算(会丢失低阶细节),可使用浮点归一化 + 限幅,适配 RG12 的代码:
# RG12精准归一化(替代右移4位)
rg8_array = np.clip((rg12_array / 4095) * 255, 0, 255).astype(np.uint8)
✅ 优点:保留 12bit 的完整动态范围,暗部 / 亮部细节更丰富;
❌ 缺点:浮点运算比整数右移稍慢,对实时性要求极高的场景(如帧率 > 30fps)建议用整数运算。
总结
- RG12 的12即 12 位有效位深,命名、排列、存储规则和 RG10 完全统一,仅有效数据位宽不同;
- 转 RGB8 的唯一差异是归一化步骤:RG12 右移 4 位,RG10 右移 2 位;
- 海康相机 RG10/RG12 的 Bayer 排列均为RGGB,均用
cv2.COLOR_BAYER_RG2RGB转换,无需修改。
三、RG10 packed
RG10 Packed(紧凑式 RG10) 是海康等工业相机对 10bit Bayer RGGB 的高密度存储格式,和之前讲的RG10 Unpacked(非紧凑,也叫 RG10 Planar) 核心区别是存储方式:Unpacked 是 10bit 数据用 2 字节(uint16)存储(高 6 位补 0,存在空间浪费),而Packed 是 3 个字节存储 2 个 10bit 像素。
先明确核心结论:
- RG10 Packed 仍为RGGB 的 Bayer 排列,仅像素数据的字节封装规则不同;
- 处理核心是解包:将 3 字节 2 像素的紧凑格式,还原为每个像素 10bit 的原始数据,再按常规 RG10 流程转 RGB8;
- 海康 MV 相机中,RG10 Packed 的像素格式枚举值为
PixelType_Gvsp_RG10_Packed(需和普通 RG10 区分)。
1. RG10 Packed 存储规则
10bit 的像素,2 个像素总数据量为 2×10bit = 20bit = 3字节,这是 RG10 Packed 的封装基础。设 2 个连续的 RGGB Bayer 像素为P0、P1(每个像素 10 位,高位为 bit9,低位为 bit0),这 4 个像素被封装到3 个连续字节中,封装规则如下:
| 字节位 | B0[7:0] | B1[5:4] | B2[7:0] | B1[1:0] |
|---|---|---|---|---|
| 封装的像素位 | P0[9:2] | P0[1:0] | P1[9:2] | P1[1:0] |
拆解理解(从字节还原单像素 10bit 数据)
- P0(第 1 个像素):高 8 位 = B0 全 8 位,低 2 位 = B1 的 bit5~bit4
- P1(第 2 个像素):高 8 位 = B2 全 8 位,,低 2 位 = B1 的 bit1~bit0
两个相邻的RG或GB像素存储在3个字节中的情形为:

每4 个连续像素为连续的 RGGB 排列,和普通 RG10 的 Bayer 顺序完全一致,解包后无需调整像素顺序。
2. RG10 Packed 图像总字节数计算
普通 RG10(Unpacked)总字节数 = 宽 × 高 ×2;RG10 Packed 总字节数 = (宽 × 高 × 3) / 2 = 宽 × 高 × 1.5(必须为整数,相机输出分辨率会保证此条件)。
3. RG10 Packed 解包
处理流程:读取 Packed 字节数据 → 按 3 字节 2 像素规则解包为 10bit uint16 数组
完整代码:
- 使用for循环验证原理:
def unpack_bayer_rg10_packed(raw_bytes, width, height):
"""
解包(2像素→3字节)
"""
total_pixels = width * height
bayer_10bit = np.zeros(total_pixels, dtype=np.uint16)
for i in range(0, total_pixels, 2):
idx = i * 3 // 2
b0 = raw_bytes[idx + 0]
b1 = raw_bytes[idx + 1]
b2 = raw_bytes[idx + 2]
# 标准10位解包
p0 = (b0 << 2) | (b1 >> 4)
p1 = (b2 << 2) | (b1 & 15)
bayer_10bit[i] = p0
bayer_10bit[i+1] = p1
return bayer_10bit.reshape(height, width)
for循环的运算效率很低,实测3072*2048的图像,转码时间长达2秒钟。
- 使用numpy:
def unpack_10bit_numpy(raw_bytes: bytes, height: int, width: int) -> np.ndarray:
# 1. 转换为uint8 numpy数组(一次性内存操作,速度极快)
raw_arr = np.frombuffer(raw_bytes, dtype=np.uint8)
total_pixels = height * width
# 2. 计算索引:一次性生成所有需要的字节索引(向量化)
idx = np.arange(0, total_pixels, 2) * 3 // 2
# 3. 批量读取3个字节数组(无循环)
b0 = raw_arr[idx]
b1 = raw_arr[idx + 1]
b2 = raw_arr[idx + 2]
# 4. 向量化位运算(CPU底层并行计算)
p0 = (b0.astype(np.uint16) << 2) | (b1 >> 4)
p1 = ((b2.astype(np.uint16) << 2) | (b1 & 0x0F)).astype(np.uint16)
# 5. 拼接像素并reshape
bayer_10bit = np.empty(total_pixels, dtype=np.uint16)
bayer_10bit[0::2] = p0 # 偶数位赋值
bayer_10bit[1::2] = p1 # 奇数位赋值
return bayer_10bit.reshape(height, width)
使用numpy,转码时间缩短到了79ms。
4. 关键优化点与避坑指南
1). 解包速度优化:用 NumPy 向量化替代 Python 循环
如果用for循环逐 3 字节解包,1920×1080 的图像会耗时几百毫秒,无法满足实时采集的要求;而上述代码的NumPy 向量化操作,效率提高很多。
2). 颜色偏色 / 画面乱码的排查步骤
如果转换后颜色异常或画面乱码,按以下顺序排查:
- 校验数据长度:确保读取的字节数 =
宽*高*3/2,相机返回的nFrameLen会直接给出正确值,不要手动计算; - 确认 Bayer 排列:海康 RG10 Packed 仍为RGGB,必须用
cv2.COLOR_BAYER_RG2RGB,切勿用 BG/GR/GB 系列; - 像素校验:把相机所有的增益和曝光调到最大,输出一个过度曝光的白色图(像素字节的所有位都是1),然后读取其字节,与计算结果进行比对校验;
- 相机参数复位:若以上都正确,复位相机参数后重新设置 RG10 Packed,避免格式混叠。
5. 10bit 转 8bit 的两种方式对比
| 方式 | 代码 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 整数右移 2 位 | rg8 = (rg10 >> 2).astype(np.uint8) |
速度极快,无浮点运算 | 丢失最低 2 位细节 | 实时采集、高帧率场景 |
| 浮点归一化 + 限幅 | np.clip((rg10/1023)*255,0,255) |
保留 10bit 完整动态范围 | 浮点运算稍慢 | 画质优先、低帧率场景 |
6. RG10 Packed vs 普通 RG10 核心对比
| 特性 | RG10 Packed(紧凑) | RG10 Unpacked(非紧凑) |
|---|---|---|
| 存储方式 | 3 字节 2 像素,无冗余位 | 2 字节 1 像素,高 6 位补 0 |
| 总字节数 | 宽 × 高 ×1.5 | 宽 × 高 ×2 |
| 空间利用率 | 83.3% | 62.5%( |
| 处理步骤 | 需先解包,再转 RGB8 | 直接解析,无需解包 |
| 带宽 / 存储占用 | 低(适合高帧率 / 大分辨率) | 高(适合简单处理) |
| 海康枚举值 | PixelType_Gvsp_RG10_Packed | PixelType_Gvsp_RG10 |
| Bayer 排列 | 均为 RGGB | 均为 RGGB |
| 转 RGB8 核心差异 | 多一步解包 | 无解压步骤 |
四、扩展:RG12 Packed 格式(举一反三)
海康相机也支持RG12 Packed,其封装规则为3 字节存储 2 个 12bit 像素(2×12bit=24bit=3 字节),和 RG10 Packed 的设计思路完全一致,仅封装 / 解包的位运算规则不同。
RG12 Packed 格式的2个像素在3个字节中的存储:

最终总结
RG Packed 的处理核心是 **「解包」,只要掌握 2 字节 3 像素的封装规则,用 NumPy 实现向量化解包,就能高效转换为普通 RG 格式,后续的 转 8bit、Bayer 转 RGB8 流程和普通 RG10 完全复用。该格式是海康 MV 相机高帧率、大分辨率采集的最优选择,能大幅降低网络 / 存储带宽,唯一的代价是增加了一步解包操作。
兼容支持RG 10 Packed和RG 12 Packed的numpy代码:
def unpack_bayer_rg_packed(raw_bytes: bytes, width: int, height: int, bit_depth:int) -> np.ndarray:
# 1. 转换为uint8 numpy数组(一次性内存操作,速度极快)
raw_arr = np.frombuffer(raw_bytes, dtype=np.uint8)
total_pixels = height * width
# 2. 计算索引:一次性生成所有需要的字节索引(向量化)
idx = np.arange(0, total_pixels // 2) * 3
# 3. 批量读取3个字节数组(无循环)
b0 = raw_arr[idx]
b1 = raw_arr[idx + 1]
b2 = raw_arr[idx + 2]
# 4. 向量化位运算(CPU底层并行计算)
if bit_depth == 10:
n = 2
elif bit_depth == 12:
n = 4
else:
raise ValueError("不支持的位深")
p0 = (b0.astype(np.uint16) << n) | (b1 >> 4)
p1 = ((b2.astype(np.uint16) << n) | (b1 & 15))
# 5. 拼接像素并reshape
bayer_arr = np.empty(total_pixels, dtype=np.uint16)
bayer_arr[0::2] = p0 # 偶数位赋值
bayer_arr[1::2] = p1 # 奇数位赋值
return bayer_arr.reshape(height, width)
#return bayer_arr # 也可以直接输出原始数列
更多推荐
所有评论(0)