news 2026/8/22 14:21:58

Windows 11下编译支持CUDA的Ceres Solver 2.2.0完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Windows 11下编译支持CUDA的Ceres Solver 2.2.0完整指南

1. 项目概述与背景

最近在做一个三维重建相关的项目,后端优化部分绕不开非线性最小二乘求解。Ceres Solver作为谷歌开源的利器,自然是首选。但项目里有些模块用了CUDA加速,想着要是Ceres也能用上GPU,那整体流程的瓶颈说不定就能打通了。官方预编译的二进制包通常不包含CUDA支持,在Windows上从头编译一个带CUDA的Ceres,尤其是还要搞定其强依赖的SuiteSparse,这个过程确实有点“劝退”。网上教程不少,但要么年代久远,要么步骤跳跃,缺了关键细节就容易掉坑里。这次我成功在Windows 11上,用Visual Studio 2022编译了Ceres Solver 2.2.0,并集成了CUDA 12.x和SuiteSparse 5.13.0,最后还把它做成了一个干净的CMake项目,方便集成到自己的工程里。如果你也在Windows上被Ceres+CUDA的编译搞得头疼,希望这篇从环境准备、依赖编译、问题排查到项目集成的完整记录能帮到你。

2. 环境准备与工具链选型

编译像Ceres这样依赖复杂的库,一个清晰、一致的环境是成功的一半。在Windows上,我们主要需要协调好编译器、CUDA工具链和数学库之间的关系。

2.1 核心工具安装与版本确认

我的基础环境是Windows 11 64位。以下是经过验证可以协同工作的工具版本:

  1. Visual Studio 2022:这是编译的“发动机”。必须安装“使用C++的桌面开发”工作负载,确保MSVC编译器(如MSVC v143)可用。社区版即可。
  2. CMake 3.28+:用于生成VS解决方案。建议从官网下载安装程序,并勾选“将CMake添加到系统PATH”,这样在命令行和VS中都能方便使用。
  3. CUDA Toolkit 12.x:我使用的是12.4。安装时,务必在自定义安装中勾选“Visual Studio Integration”。安装完成后,在命令行输入nvcc --versionnvidia-smi来验证CUDA编译器驱动版本。一个常见坑点是nvcc版本(编译器)和nvidia-smi显示的驱动版本可能不一致,但只要大版本号(如12.x)匹配,通常问题不大。CUDA 12.x对NVIDIA显卡架构(如Sm_89 for RTX 40系列)支持更好。
  4. Git:用于克隆源代码。

注意:路径中请尽量避免中文和空格。我将所有工具和源码都放在类似D:\Dev这样的纯英文路径下,能减少很多不必要的麻烦。

2.2 依赖库规划:为何选择手动编译SuiteSparse?

Ceres的依赖项很多,例如Eigen、glog、gflags等。对于大多数依赖,我们可以让Ceres在编译时自动下载(通过FetchContent),这非常方便。但SuiteSparse是一个例外

SuiteSparse是一套稀疏矩阵计算库,Ceres用它来求解大型稀疏线性系统,性能远超默认的稠密求解器。在Linux上,通过包管理器安装libsuitesparse-dev很简单。但在Windows上,没有官方预编译包。虽然有一些第三方编译好的二进制文件,但版本、编译器运行时库(MSVC Redistributable)和我们的环境很难完全匹配,极易引发运行时崩溃(如“找不到VCRUNTIME140.dll”或“0xC0000005内存访问冲突”)。

因此,最稳妥的方案是用自己的工具链从头编译SuiteSparse。这样生成的静态库(.lib)与我们的VS版本、运行时库完全一致,确保最终链接和运行的稳定性。我们选择编译其核心组件:AMDCAMDCCOLAMDCOLAMDCHOLMODSPQR。其中CHOLMOD又依赖LAPACKBLAS

对于LAPACK/BLAS,我们选择Intel MKL。它性能强劲,且提供了Windows预编译库,兼容性好。可以从Intel官网下载并安装Intel oneAPI Base Toolkit(其中包含MKL),或者直接使用其在线安装器。安装后,我们需要记住其库文件(如mkl_intel_lp64.lib)和头文件的路径。

3. 编译SuiteSparse 5.13.0

这是整个过程中最具挑战性的一环。SuiteSparse的构建系统原本是为Unix设计的,在Windows上需要一些适配。

3.1 源码获取与结构整理

首先,从Tim Davis教授的官网或GitHub仓库下载SuiteSparse 5.13.0的源码包。解压后,你会看到一堆以组件命名的文件夹(AMDCHOLMOD等)和一个SuiteSparse_config文件夹。

为了让CMake更容易管理,我创建了一个清晰的目录结构:

D:\Dev\SuiteSparse_Source\ (源码解压至此) D:\Dev\SuiteSparse_Build\ (编译输出目录) D:\Dev\SuiteSparse_Install\ (最终库和头文件安装目录)

将下载的SuiteSparse-5.13.0.tar.gz解压到SuiteSparse_Source,内容应直接是各个组件文件夹。

3.2 编写顶级CMakeLists.txt

SuiteSparse_Source目录下,创建一个CMakeLists.txt文件。这个文件的作用是组织所有子组件,并统一设置编译选项和查找MKL。

cmake_minimum_required(VERSION 3.20) project(SuiteSparse LANGUAGES C) # 设置编译类型(Release/Debug)和运行时库(MT/MTd) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") # 寻找Intel MKL。如果你安装在了默认位置,可以这样设置。 # 请根据你的实际安装路径修改 set(INTEL_MKL_ROOT "C:/Program Files (x86)/Intel/oneAPI/mkl/latest") set(ENV{MKLROOT} ${INTEL_MKL_ROOT}) find_package(MKL REQUIRED) if(MKL_FOUND) message(STATUS "Found MKL: ${MKL_LIBRARIES}") include_directories(${MKL_INCLUDE_DIRS}) else() message(FATAL_ERROR "MKL not found. Please install Intel oneAPI MKL.") endif() # 添加子目录,注意依赖顺序。 # SuiteSparse_config 必须第一个,它包含全局配置。 # CHOLMOD 依赖较多,放在后面。 add_subdirectory(SuiteSparse_config) add_subdirectory(AMD) add_subdirectory(CAMD) add_subdirectory(CCOLAMD) add_subdirectory(COLAMD) # CHOLMOD 依赖之前的所有组件以及MKL add_subdirectory(CHOLMOD) add_subdirectory(SPQR) # 创建一个安装目标,将所有库和头文件收集到指定目录 install(DIRECTORY include/ DESTINATION include) install(TARGETS suitesparseconfig amd camd ccolamd colamd cholmod spqr ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin)

3.3 修改组件CMakeLists.txt与源码适配

每个组件文件夹(如AMD)里可能没有CMakeLists.txt,或者其Makefile不适用于Windows。我们需要为每个组件创建简单的CMakeLists.txt

AMD为例,在D:\Dev\SuiteSparse_Source\AMD中创建CMakeLists.txt

project(AMD LANGUAGES C) # 收集当前目录下的所有.c文件 file(GLOB_RECURSE SOURCES "*.c") # 添加包含路径,指向SuiteSparse_config的头文件 include_directories(../SuiteSparse_config) # 创建静态库 add_library(amd STATIC ${SOURCES}) # 设置库的属性,避免警告 set_target_properties(amd PROPERTIES POSITION_INDEPENDENT_CODE ON ) # 安装头文件和库 install(FILES Include/amd.h DESTINATION include) install(TARGETS amd ARCHIVE DESTINATION lib)

对于CHOLMOD,情况更复杂,因为它依赖MKL。在其CMakeLists.txt中需要链接MKL:

project(CHOLMOD LANGUAGES C) file(GLOB_RECURSE SOURCES "*.c") include_directories(../SuiteSparse_config ../AMD/Include ../CAMD/Include ../CCOLAMD/Include ../COLAMD/Include .) add_library(cholmod STATIC ${SOURCES}) set_target_properties(cholmod PROPERTIES POSITION_INDEPENDENT_CODE ON) # 关键:链接依赖库和MKL target_link_libraries(cholmod PUBLIC amd camd ccolamd colamd suitesparseconfig ${MKL_LIBRARIES} # 链接MKL库 ) target_include_directories(cholmod PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Include> ) install(FILES Include/cholmod.h Include/cholmod_*.h DESTINATION include) install(TARGETS cholmod ARCHIVE DESTINATION lib)

一个关键的源码修改:在SuiteSparse_configSuiteSparse_config.h文件中,需要确保它正确地定义了Windows下的符号。通常需要检查或添加:

#ifdef _WIN32 #define SuiteSparse_long __int64 #define SuiteSparse_long_max _I64_MAX // 确保使用符合C99标准的函数,例如将`round`定义为`_round` #include <math.h> #ifndef round #define round(x) ((x) >= 0 ? (int)((x)+0.5) : (int)((x)-0.5)) #endif #endif

如果遇到round函数重定义警告,可以注释掉自定义定义,因为math.h通常已提供。

3.4 生成、编译与安装

  1. 生成VS解决方案: 打开“x64 Native Tools Command Prompt for VS 2022”(确保是64位环境)。

    cd D:\Dev\SuiteSparse_Build cmake ../SuiteSparse_Source -G "Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX=../SuiteSparse_Install

    参数解释:-G指定生成器,-A指定平台,-DCMAKE_INSTALL_PREFIX指定安装路径。

  2. 编译与安装

    cmake --build . --config Release --target INSTALL

    这个命令会编译Release配置,并将库和头文件安装到D:\Dev\SuiteSparse_Install目录下。完成后,该目录会有includelib文件夹,里面就是我们需要的所有东西。

实操心得:第一次编译可能会遇到各种未定义符号或链接错误。请务必按照依赖顺序添加子目录(如先编译AMD等,再编译依赖它们的CHOLMOD)。错误信息通常会告诉你缺少哪个库,根据提示调整target_link_libraries的顺序。编译SPQR如果报错找不到cholmod_l_*函数,可能需要检查CHOLMOD的编译选项是否生成了这些带_l后缀的长整型版本函数。

4. 编译Ceres Solver 2.2.0 (With CUDA)

搞定SuiteSparse后,编译Ceres本身反而相对直接。

4.1 源码获取与CMake配置

从Ceres Solver的GitHub仓库下载2.2.0版本源码,解压到D:\Dev\Ceres_Source。创建构建目录D:\Dev\Ceres_Build

在CMake GUI中操作会更直观:

  1. 设置源码路径(D:/Dev/Ceres_Source)和构建路径(D:/Dev/Ceres_Build)。

  2. 点击“Configure”,选择“Visual Studio 17 2022”和“x64”。

  3. 会出现一堆红色配置项。我们需要关键修改以下几项:

    • CMAKE_PREFIX_PATH: 添加我们的SuiteSparse安装路径,如D:/Dev/SuiteSparse_Install。这能帮助CMake自动找到SuiteSparse。
    • SUITESPARSE_INCLUDE_DIR_HINTS: 手动指向D:/Dev/SuiteSparse_Install/include
    • SUITESPARSE_LIBRARY_DIR_HINTS: 手动指向D:/Dev/SuiteSparse_Install/lib
    • Eigen3_DIR: 如果自动下载Eigen失败,可以手动指定本地Eigen路径。
    • BUILD_SHARED_LIBS:OFF(我们编译静态库,便于部署)。
    • BUILD_EXAMPLES:OFF(非必须)。
    • CUDA:ON(启用CUDA支持)。
    • CUDA_TOOLKIT_ROOT_DIR: 通常CMake能自动找到,如果没找到,手动指向CUDA安装目录(如C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v12.4)。
    • CUDA_ARCHITECTURES: 根据你的GPU计算能力填写。例如,RTX 4060 Ti是Ada Lovelace架构,计算能力8.9,可以填写native89-real;89-virtual。填写native让CMake自动检测是最省事的。
    • CXX11_ABI: 保持默认(通常为ON)。确保与后续使用Ceres的项目设置一致。
    • MSVC_USE_STATIC_RUNTIME:ON(使用静态运行时库/MT,这样生成的库不依赖VC++运行时DLL,但会增大体积。如果选择OFF/MD,则必须确保部署环境有对应运行时库)。
  4. 再次点击“Configure”,直到没有红色错误。此时CMake应该能正确找到SuiteSparse的各组件库(如SUITESPARSE_CHOLMOD_LIB等)。

  5. 点击“Generate”生成VS解决方案。

4.2 编译与验证

打开生成的Ceres.sln,在解决方案配置中选择“Release”和“x64”,然后生成“ALL_BUILD”目标。

编译成功后,在D:\Dev\Ceres_Build\lib\Release目录下会生成ceres.lib(静态库)和ceres.dll(如果编译了动态库)。头文件在源码目录的include里。

验证CUDA是否启用:编译一个使用了ceres::CUDADenseQRceres::CUDASparseCholesky等CUDA求解器的示例程序。或者在代码中检查:

#ifdef CERES_USE_CUDA std::cout << "CUDA support is enabled in Ceres!" << std::endl; #endif

5. 创建集成的CMakeLists项目

现在,我们将编译好的Ceres和SuiteSparse集成到一个干净的CMake项目中,方便在其他工程里引用。

假设你的项目结构如下:

MyProject/ ├── CMakeLists.txt ├── src/ │ └── main.cpp ├── deps/ │ ├── ceres/ │ │ ├── include/ (从Ceres源码和SuiteSparse安装目录合并而来) │ │ └── lib/ (包含ceres.lib, suitesparse的各个.lib, 以及CUDA相关的.lib如cudart_static.lib) │ └── mkl/ │ └── (MKL的lib和include)

5.1 依赖库的整理

将以下文件拷贝到deps目录下:

  1. Ceres:
    • D:\Dev\Ceres_Source\include\ceres\*->MyProject\deps\ceres\include\ceres\
    • D:\Dev\Ceres_Build\include\ceres\internal\config.h->MyProject\deps\ceres\include\ceres\internal\(这个生成的配置头文件很重要!)
    • D:\Dev\Ceres_Build\lib\Release\ceres.lib->MyProject\deps\ceres\lib\
  2. SuiteSparse:
    • D:\Dev\SuiteSparse_Install\include\*.h->MyProject\deps\ceres\include\(与Ceres头文件放同级,因为Ceres会直接包含suitesparse.h等)
    • D:\Dev\SuiteSparse_Install\lib\*.lib->MyProject\deps\ceres\lib\
  3. CUDA:
    • 需要链接CUDA运行时库。通常使用cudart_static.lib(静态链接)。它位于CUDA_PATH\lib\x64(如C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\lib\x64)。将其也拷贝到MyProject\deps\ceres\lib\,或者不拷贝,在CMake中直接引用绝对路径。
  4. MKL:
    • 将MKL的库文件(如来自%ONEAPI_ROOT%\mkl\latest\lib\intel64)和头文件也组织到deps/mkl下,或者直接在CMake中查找。

5.2 编写项目CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(MyCeresProject LANGUAGES CXX CUDA) # 注意这里添加了CUDA语言 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 设置依赖库路径 set(DEP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/deps) set(CERES_DIR ${DEP_ROOT}/ceres) set(MKL_DIR ${DEP_ROOT}/mkl) # 假设MKL已整理至此 # 添加可执行文件 add_executable(my_ceres_app src/main.cpp) # 包含目录 target_include_directories(my_ceres_app PRIVATE ${CERES_DIR}/include # 如果MKL头文件不在默认路径,也需要添加 ${MKL_DIR}/include ) # 链接库 - 这是一个需要仔细排序的列表 target_link_libraries(my_ceres_app PRIVATE # 1. Ceres Solver 主库 ${CERES_DIR}/lib/ceres.lib # 2. SuiteSparse 组件 (注意顺序,被依赖的放后面) ${CERES_DIR}/lib/spqr.lib ${CERES_DIR}/lib/cholmod.lib ${CERES_DIR}/lib/amd.lib ${CERES_DIR}/lib/camd.lib ${CERES_DIR}/lib/ccolamd.lib ${CERES_DIR}/lib/colamd.lib ${CERES_DIR}/lib/suitesparseconfig.lib # 3. MKL 库 (需要根据MKL链接线指南调整,这是一个简化示例) ${MKL_DIR}/lib/mkl_intel_lp64.lib ${MKL_DIR}/lib/mkl_sequential.lib ${MKL_DIR}/lib/mkl_core.lib # 4. CUDA 运行时库 cudart_static.lib # 如果路径不在系统库目录,需要用完整路径或find_package(CUDA) # 5. Windows 系统库 ws2_32.lib # SuiteSparse可能需要的网络库 shlwapi.lib ) # 重要:如果使用静态运行时库(/MT),需要定义宏 target_compile_definitions(my_ceres_app PRIVATE $<$<CXX_COMPILER_ID:MSVC>:_STATIC_CPPLIB> # 避免链接冲突 ) # 如果项目中有.cu文件,需要启用CUDA编译并链接CUDA库 enable_language(CUDA) set_target_properties(my_ceres_app PROPERTIES CUDA_SEPARABLE_COMPILATION ON )

5.3 一个简单的测试代码

src/main.cpp中编写一个简单的BA问题测试:

#include <iostream> #include <ceres/ceres.h> #include <ceres/cuda_sparse_cholesky.h> int main(int argc, char** argv) { google::InitGoogleLogging(argv[0]); // 检查CUDA是否可用 #ifdef CERES_USE_CUDA std::cout << "[INFO] Ceres CUDA support is enabled." << std::endl; // 可以尝试设置线性求解器为CUDA版本 ceres::Solver::Options options; options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; options.sparse_linear_algebra_library_type = ceres::CUDA_SPARSE; options.dense_linear_algebra_library_type = ceres::CUDA; options.num_threads = 1; // CUDA求解器通常单线程 options.minimizer_progress_to_stdout = true; // 注意:实际使用需要确保问题规模适合GPU,且有可用的CUDA求解器 std::cout << "CUDA linear solver options configured." << std::endl; #else std::cout << "[WARN] Ceres compiled without CUDA." << std::endl; #endif // 这里可以构造一个简单的优化问题 // ... std::cout << "Ceres Solver with CUDA test environment setup successfully." << std::endl; return 0; }

6. 常见编译与链接问题排查

即使按照步骤操作,也可能会遇到各种错误。这里记录几个我踩过的坑和解决方法。

6.1 SuiteSparse编译阶段

  1. “LINK : fatal error LNK1104: 无法打开文件 ‘mkl_intel_lp64.lib’”

    • 原因:CMake没有正确找到MKL库路径。
    • 解决:在顶级CMakeLists.txt中,使用find_package(MKL REQUIRED)前,通过set(MKLROOT “your_mkl_path”)或设置环境变量MKLROOT明确指定路径。确保路径中的斜杠使用/或双反斜杠\\
  2. “未定义的外部符号 cholmod_l_*”

    • 原因:SuiteSparse默认可能同时编译32位和64位整数版本(intlong)。Ceres通常使用long版本(函数带_l后缀)。
    • 解决:在编译SuiteSparse时,确保SuiteSparse_config.hDLONG被定义(通常默认就是),这样就会编译cholmod_l_*函数。检查CHOLMOD的Makefile或CMake是否开启了-DDLONG选项。
  3. “error C2065: ‘round’: 未声明的标识符”

    • 原因:Windows旧版本MSVC的math.h可能没有C99的round函数。
    • 解决:在SuiteSparse_config.h的Windows部分,使用前面提到的round宏定义,或者包含<cmath>并确认使用标准兼容模式(/std:c++17或更高)。

6.2 Ceres编译阶段

  1. “Could not find CHOLMOD or CHOLMOD not compiled with SuiteSparse_config.h”

    • 原因:CMake找不到SuiteSparse或找到的版本不匹配。
    • 解决:确保CMAKE_PREFIX_PATHSUITESPARSE*_DIR_HINTS指向的是你安装的目录(SuiteSparse_Install),而不是源码或构建目录。该目录下必须有include/suitesparse.hlib/cholmod.lib等文件。
  2. “CUDA_nvcc_executable-NOTFOUND”

    • 原因:CMake找不到nvcc编译器。
    • 解决:检查CUDA是否安装正确,并确保在“x64 Native Tools Command Prompt for VS 2022”中运行CMake,这个命令行环境设置了VS和CUDA所需的路径。也可以手动设置CUDA_TOOLKIT_ROOT_DIR变量。
  3. “error : Unsupported GPU architecture ‘compute_89’”

    • 原因:CUDA编译器版本太旧,不支持你指定的新GPU架构(如RTX 40系列的sm_89)。
    • 解决:升级CUDA Toolkit到支持该架构的版本(如CUDA 11.8+支持sm_89)。或者在CUDA_ARCHITECTURES中指定一个旧一点的、支持的架构(如75for Turing),但这意味着无法充分利用新显卡性能。

6.3 项目链接与运行时阶段

  1. “无法解析的外部符号 google::base::CheckOpMessageBuilder”

    • 原因:缺少glog或gflags库。虽然Ceres静态链接了它们的一部分,但有时仍需要显式链接。
    • 解决:在你的项目CMakeLists.txt中,链接glog.libgflags.lib。你可以让Ceres自动下载并编译它们,然后将生成的库文件也拷贝到你的deps目录并链接。
  2. “0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突”

    • 原因:这是典型的运行时库不匹配或内存损坏。最常见的原因是:SuiteSparse或Ceres是用动态运行时库(/MD)编译的,而你的项目是用静态运行时库(/MT)编译的,或者反之
    • 解决:统一所有库和你的项目的运行时库设置。在CMake中,通过set(CMAKE_MSVC_RUNTIME_LIBRARY “MultiThreaded$<$<CONFIG:Debug>:Debug>”)来强制使用静态运行时库(/MT或/MTd)。确保你使用的所有第三方.lib文件都是用相同设置编译的。
  3. 程序运行正常,但CUDA求解器没有加速效果

    • 原因:问题规模太小,GPU启动和内存传输的开销超过了计算收益;或者没有正确调用CUDA求解器。
    • 解决:确保在ceres::Solver::Options中正确设置了linear_solver_typesparse_linear_algebra_library_typedense_linear_algebra_library_type为CUDA选项。对于稀疏Cholesky求解器,使用options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; options.sparse_linear_algebra_library_type = ceres::CUDA_SPARSE;。构造一个足够大的问题(例如,BA中成千上万个点和观测)才能体现GPU优势。
  4. “CUSPARSE_STATUS_ALLOC_FAILED” 或 GPU内存不足

    • 原因:问题数据太大,超出了GPU显存。
    • 解决:减小问题规模,或者使用迭代求解器(如CGNR)配合CUDA的预处理子。监控GPU显存使用情况(可以用nvidia-smi)。

整个流程走下来,虽然步骤繁琐,但一旦打通,你就拥有了一个完全可控、深度定制的Ceres Solver开发环境。最大的体会是,在Windows上处理这类源自Linux生态的库,一致性是关键:编译器版本、运行时库、依赖库的编译选项必须完全匹配。手动编译SuiteSparse虽然前期耗时,但避免了后续无数诡异的运行时错误,从长远看是值得的。最后,将一切封装在清晰的CMake项目里,无论是团队协作还是项目迁移,都会轻松很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 14:19:55

免费开源的图片转PDF:手机上三步完成的完整指南

免费开源的图片转PDF&#xff1a;手机上三步完成的完整指南 【免费下载链接】Images-to-PDF An app to convert images to PDF file! 项目地址: https://gitcode.com/gh_mirrors/im/Images-to-PDF Images-to-PDF 是一款免费开源的 Android 应用&#xff0c;把多张照片秒…

作者头像 李华