通过分析图片和JSON数据,我可以明确说明坐标系的起始位置和具体规则:
2023年度报告母公司_page_001.json
{
"blocks": [
{
"block_label": "header",
"block_content": "广东荣德会计师事务所有限公司",
"block_bbox": [175, 157, 594, 219]
},
{
"block_label": "doc_title",
"block_content": "审计报告",
"block_bbox": [627, 263, 976, 325]
},
{
"block_label": "footer",
"block_content": "此码用于证明该审计报告是否由具有执业许可的会计师事务所出具...",
"block_bbox": [527, 2235, 1411, 2297]
}
]
}
坐标系以图片左上角为原点 (0,0):
{
"block_label": "doc_title",
"block_content": "审计报告",
"block_bbox": [627, 263, 976, 325]
}
[627, 263, 976, 325] 表示:
{
"block_label": "header",
"block_content": "广东荣德会计师事务所有限公司",
"block_bbox": [175, 157, 594, 219]
}
[175, 157, 594, 219] 表示:
{
"block_label": "footer",
"block_content": "此码用于证明该审计报告是否由具有执业许可的会计师事务所出具...",
"block_bbox": [527, 2235, 1411, 2297]
}
[527, 2235, 1411, 2297] 表示:
所有的 bbox 都采用 [x1, y1, x2, y2] 格式:
x1, y1:矩形框的左上角坐标x2, y2:矩形框的右下角坐标从JSON中可以看出:
这完全符合左上角为原点,Y轴向下递增的坐标系统。
"block_bbox": [627, 263, 976, 325]
fig.add_layout_image(
dict(
source=image,
xref="x", yref="y",
x=0, y=image.height, # ← 这里是关键!
sizex=image.width,
sizey=image.height,
sizing="stretch",
opacity=1.0,
layer="below"
)
)
y=image.height 的含义y=0 是图像底部y=image.height 是图像顶部假设图像高度为2000像素:
# JSON坐标(图像坐标系)
json_bbox = [627, 263, 976, 325] # [x1, y1, x2, y2]
# 转换为Plotly坐标系
def convert_to_plotly_coords(bbox, image_height):
x1, y1, x2, y2 = bbox
# X坐标不变
plot_x1 = x1
plot_x2 = x2
# Y坐标需要翻转
plot_y1 = image_height - y2 # 原来的底部 -> Plotly的底部
plot_y2 = image_height - y1 # 原来的顶部 -> Plotly的顶部
return [plot_x1, plot_y1, plot_x2, plot_y2]
# 示例转换
image_height = 2000
plotly_coords = convert_to_plotly_coords([627, 263, 976, 325], image_height)
# 结果:[627, 1675, 976, 1737]
您的代码中有坐标转换不一致的问题:
def create_resized_interactive_plot(self, image: Image.Image, selected_bbox: Optional[List[int]], zoom_level: float, all_boxes: list[tuple]) -> go.Figure:
"""创建可调整大小的交互式图片"""
fig = go.Figure()
# 添加图片 - Plotly坐标系,原点在左下角
fig.add_layout_image(
dict(
source=image,
xref="x", yref="y",
x=0, y=image.height, # 图片左下角在Plotly坐标系中的位置
sizex=image.width,
sizey=image.height,
sizing="stretch",
opacity=1.0,
layer="below"
)
)
# 显示所有bbox - 需要坐标转换
if len(all_boxes) > 0:
for bbox in all_boxes:
if len(bbox) >= 4:
x1, y1, x2, y2 = bbox[:4]
# 转换为Plotly坐标系(翻转Y轴)
plot_x1 = x1
plot_x2 = x2
plot_y1 = image.height - y2 # JSON的y2 -> Plotly的底部
plot_y2 = image.height - y1 # JSON的y1 -> Plotly的顶部
color = "rgba(0, 100, 200, 0.2)"
fig.add_shape(
type="rect",
x0=plot_x1, y0=plot_y1,
x1=plot_x2, y1=plot_y2,
line=dict(color="blue", width=1),
fillcolor=color,
)
# 高亮显示选中的bbox
if selected_bbox and len(selected_bbox) >= 4:
x1, y1, x2, y2 = selected_bbox[:4]
# 转换为Plotly坐标系
plot_x1 = x1
plot_x2 = x2
plot_y1 = image.height - y2 # 翻转Y坐标
plot_y2 = image.height - y1 # 翻转Y坐标
fig.add_shape(
type="rect",
x0=plot_x1, y0=plot_y1,
x1=plot_x2, y1=plot_y2,
line=dict(color="red", width=3),
fillcolor="rgba(255, 0, 0, 0.3)",
)
# ...existing code...
关键点:
y=image.height 表示图像在Plotly坐标系中的顶部位置plotly_y = image_height - json_y在坐标系中,原点位于左上角,x轴向右增加,y轴向下增加(类似于屏幕坐标系)。绕原点旋转任意角度θ(θ为用户视角的逆时针角度)的旋转矩阵为:
\begin{bmatrix}
x' \\
y'
\end{bmatrix}
=
\begin{bmatrix}
\cos \theta & \sin \theta \\
-\sin \theta & \cos \theta
\end{bmatrix}
\begin{bmatrix}
x \\
y
\end{bmatrix}
其中: