feat(3d): 创建异常截图改相机重构图(方案A,frame-to-fit selection)
异常截图原为整窗口截图。改为业界 frame/zoom-to-fit selection 范式: captureFramedRegionPng 把相机临时重新取景到圈定 worldPts 外扩区域(padFactor=1.4≈异常占画面~70% 带周边语境),视角方向不变仅推近/缩放(ResetCamera),后台缓冲+关交换截图屏幕不闪,截后还原相机。 点(零体积)/线面共面(某轴零厚度)用切片尺寸 0.25×min(e1,e2) 作框景半径兜底。 main 调用处从 worldPts 算世界包围盒 + 从切片 o/p1/p2 算兜底尺寸。 构建:app 链接通过
This commit is contained in:
parent
56e4b3a7ff
commit
75c1327aa4
|
|
@ -2,12 +2,15 @@
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <vtkCamera.h>
|
||||||
#include <vtkDataArray.h>
|
#include <vtkDataArray.h>
|
||||||
#include <vtkImageData.h>
|
#include <vtkImageData.h>
|
||||||
#include <vtkNew.h>
|
#include <vtkNew.h>
|
||||||
#include <vtkPNGWriter.h>
|
#include <vtkPNGWriter.h>
|
||||||
#include <vtkPointData.h>
|
#include <vtkPointData.h>
|
||||||
#include <vtkRenderWindow.h>
|
#include <vtkRenderWindow.h>
|
||||||
|
#include <vtkRenderer.h>
|
||||||
|
#include <vtkRendererCollection.h>
|
||||||
#include <vtkWindowToImageFilter.h>
|
#include <vtkWindowToImageFilter.h>
|
||||||
|
|
||||||
namespace geopro::app {
|
namespace geopro::app {
|
||||||
|
|
@ -41,6 +44,68 @@ bool captureRenderWindowPng(vtkRenderWindow* win, const std::string& path, int&
|
||||||
return writer->GetErrorCode() == 0;
|
return writer->GetErrorCode() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool captureFramedRegionPng(vtkRenderWindow* win, const double regionBounds[6], double padFactor,
|
||||||
|
double minExtent, const std::string& path, int& outW, int& outH) {
|
||||||
|
outW = outH = 0;
|
||||||
|
if (win == nullptr || path.empty()) return false;
|
||||||
|
vtkRenderer* ren =
|
||||||
|
win->GetRenderers() ? win->GetRenderers()->GetFirstRenderer() : nullptr;
|
||||||
|
vtkCamera* cam = ren ? ren->GetActiveCamera() : nullptr;
|
||||||
|
if (ren == nullptr || cam == nullptr)
|
||||||
|
return captureRenderWindowPng(win, path, outW, outH); // 无渲染器 → 退回整窗
|
||||||
|
|
||||||
|
// 1) 区域包围盒:minExtent 兜底(点零体积/共面零厚度) → padFactor 以中心外扩留边距。
|
||||||
|
double b[6];
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
const double lo = regionBounds[2 * i], hi = regionBounds[2 * i + 1];
|
||||||
|
const double c = 0.5 * (lo + hi);
|
||||||
|
double half = 0.5 * (hi - lo);
|
||||||
|
if (2.0 * half < minExtent) half = 0.5 * minExtent; // 退化轴兜底
|
||||||
|
half *= padFactor; // 外扩边距
|
||||||
|
b[2 * i] = c - half;
|
||||||
|
b[2 * i + 1] = c + half;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 存相机现场(ResetCamera 改 position/focalPoint/clipping/parallelScale)。
|
||||||
|
double pos[3], fp[3], up[3], clip[2];
|
||||||
|
cam->GetPosition(pos);
|
||||||
|
cam->GetFocalPoint(fp);
|
||||||
|
cam->GetViewUp(up);
|
||||||
|
cam->GetClippingRange(clip);
|
||||||
|
const double va = cam->GetViewAngle();
|
||||||
|
const double ps = cam->GetParallelScale();
|
||||||
|
|
||||||
|
// 3) 重构图:保持视角方向,仅推近/缩放框住外扩区域。
|
||||||
|
ren->ResetCamera(b);
|
||||||
|
|
||||||
|
// 4) 截图(后台缓冲 + 关交换 → 屏幕不闪)。
|
||||||
|
vtkNew<vtkWindowToImageFilter> w2i;
|
||||||
|
w2i->SetInput(win);
|
||||||
|
w2i->ReadFrontBufferOff();
|
||||||
|
w2i->Update();
|
||||||
|
if (auto* img = w2i->GetOutput()) {
|
||||||
|
int dims[3];
|
||||||
|
img->GetDimensions(dims);
|
||||||
|
outW = dims[0];
|
||||||
|
outH = dims[1];
|
||||||
|
}
|
||||||
|
vtkNew<vtkPNGWriter> writer;
|
||||||
|
writer->SetFileName(path.c_str());
|
||||||
|
writer->SetInputConnection(w2i->GetOutputPort());
|
||||||
|
writer->Write();
|
||||||
|
const bool ok = writer->GetErrorCode() == 0;
|
||||||
|
|
||||||
|
// 5) 还原相机 + 重绘回原视图。
|
||||||
|
cam->SetPosition(pos);
|
||||||
|
cam->SetFocalPoint(fp);
|
||||||
|
cam->SetViewUp(up);
|
||||||
|
cam->SetViewAngle(va);
|
||||||
|
cam->SetParallelScale(ps);
|
||||||
|
cam->SetClippingRange(clip);
|
||||||
|
win->Render();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
bool exportSliceDat(vtkImageData* slice, const std::string& path) {
|
bool exportSliceDat(vtkImageData* slice, const std::string& path) {
|
||||||
if (slice == nullptr || path.empty()) return false;
|
if (slice == nullptr || path.empty()) return false;
|
||||||
vtkDataArray* arr = slice->GetPointData() ? slice->GetPointData()->GetScalars() : nullptr;
|
vtkDataArray* arr = slice->GetPointData() ? slice->GetPointData()->GetScalars() : nullptr;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,15 @@ bool exportSliceImagePng(vtkImageData* colorImage, const std::string& path);
|
||||||
// 截整个渲染窗口为 PNG(异常标识截图,需求 R88);成功返回 true,并填回截图像素宽高。
|
// 截整个渲染窗口为 PNG(异常标识截图,需求 R88);成功返回 true,并填回截图像素宽高。
|
||||||
bool captureRenderWindowPng(vtkRenderWindow* win, const std::string& path, int& outW, int& outH);
|
bool captureRenderWindowPng(vtkRenderWindow* win, const std::string& path, int& outW, int& outH);
|
||||||
|
|
||||||
|
// 「相机重构图」截图(方案A):把相机临时重新取景到 regionBounds(圈定范围)外扩后的区域,
|
||||||
|
// 使异常框在画面中央带周边语境,再截图、还原相机。业界 frame/zoom-to-fit selection 范式。
|
||||||
|
// regionBounds: {xmin,xmax,ymin,ymax,zmin,zmax} 世界系圈定包围盒;
|
||||||
|
// padFactor: 以盒中心外扩的倍数(1.4≈异常占画面~70%);
|
||||||
|
// minExtent: 退化兜底(点=零体积、线/面共面=某轴零厚度)时各轴的最小世界尺寸。
|
||||||
|
// 视角方向不变(仅推近/缩放);屏幕无闪(后台缓冲+关交换)。失败回退整窗截图。
|
||||||
|
bool captureFramedRegionPng(vtkRenderWindow* win, const double regionBounds[6], double padFactor,
|
||||||
|
double minExtent, const std::string& path, int& outW, int& outH);
|
||||||
|
|
||||||
// 把切片重采样 2D 标量影像写为 .dat 文本网格(行=j、列=i,空格分隔,每格取标量首分量);成功返回 true。
|
// 把切片重采样 2D 标量影像写为 .dat 文本网格(行=j、列=i,空格分隔,每格取标量首分量);成功返回 true。
|
||||||
bool exportSliceDat(vtkImageData* slice, const std::string& path);
|
bool exportSliceDat(vtkImageData* slice, const std::string& path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -538,7 +538,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
anomalyDrawTool->start(
|
anomalyDrawTool->start(
|
||||||
o, normal,
|
o, normal,
|
||||||
[&window, sceneView, scene3dRepo, renderWindowPtr, refreshAnomalies, refreshAnalysis,
|
[&window, sceneView, scene3dRepo, renderWindowPtr, refreshAnomalies, refreshAnalysis,
|
||||||
volId, savedSliceId, normal, o](const std::vector<ri::Vec3>& worldPts) {
|
volId, savedSliceId, normal, o, p1, p2](const std::vector<ri::Vec3>& worldPts) {
|
||||||
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
||||||
geopro::core::Anomaly a;
|
geopro::core::Anomaly a;
|
||||||
a.markType = geopro::core::AnomalyMarkType::Polygon;
|
a.markType = geopro::core::AnomalyMarkType::Polygon;
|
||||||
|
|
@ -558,7 +558,23 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
const QString shot =
|
const QString shot =
|
||||||
QDir(QDir::tempPath()).filePath(QStringLiteral("geopro_anomaly_shot.png"));
|
QDir(QDir::tempPath()).filePath(QStringLiteral("geopro_anomaly_shot.png"));
|
||||||
int sw = 0, sh = 0;
|
int sw = 0, sh = 0;
|
||||||
geopro::app::captureRenderWindowPng(renderWindowPtr, shot.toStdString(), sw, sh);
|
// 相机重构图(方案A):框住圈定 worldPts 外扩区域(异常居中带语境);
|
||||||
|
// 点/共面退化用切片尺寸兜底框景半径。
|
||||||
|
double rb[6] = {worldPts[0][0], worldPts[0][0], worldPts[0][1],
|
||||||
|
worldPts[0][1], worldPts[0][2], worldPts[0][2]};
|
||||||
|
for (const auto& p : worldPts) {
|
||||||
|
rb[0] = std::min(rb[0], p[0]); rb[1] = std::max(rb[1], p[0]);
|
||||||
|
rb[2] = std::min(rb[2], p[1]); rb[3] = std::max(rb[3], p[1]);
|
||||||
|
rb[4] = std::min(rb[4], p[2]); rb[5] = std::max(rb[5], p[2]);
|
||||||
|
}
|
||||||
|
auto vlen = [](double x, double y, double z) {
|
||||||
|
return std::sqrt(x * x + y * y + z * z);
|
||||||
|
};
|
||||||
|
const double e1 = vlen(p1[0] - o[0], p1[1] - o[1], p1[2] - o[2]);
|
||||||
|
const double e2 = vlen(p2[0] - o[0], p2[1] - o[1], p2[2] - o[2]);
|
||||||
|
const double minExt = 0.25 * std::min(e1, e2); // 点/线退化框景半径
|
||||||
|
geopro::app::captureFramedRegionPng(renderWindowPtr, rb, 1.4, minExt,
|
||||||
|
shot.toStdString(), sw, sh);
|
||||||
geopro::app::AnomalySaveDialog dlg(shot, sw, sh, &window);
|
geopro::app::AnomalySaveDialog dlg(shot, sw, sh, &window);
|
||||||
if (dlg.exec() != QDialog::Accepted) {
|
if (dlg.exec() != QDialog::Accepted) {
|
||||||
sceneView->removeAnomaly(draftId);
|
sceneView->removeAnomaly(draftId);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue