【android opencv学习笔记】Day 1: Switch类

霍夫变换直线/线段检测
霍夫变换(Hough Transform)是机器视觉领域经典的几何特征检测算法,尤其在直线、线段检测中表现突出,广泛应用于车道线识别、文档边缘校正、工业零件定位等场景。
本文将从霍夫变换原理 、OpenCV 核心 API 解析 、Android 完整工程实现 三部分,手把手讲解霍夫直线检测。项目支持开发者传入自定义 2048×2048 本地图片,自动完成边缘检测、霍夫直线/线段提取与绘制,源码完整可编译、可直接运行,适合学习、项目集成与技术博文发布。
霍夫变换核心原理
霍夫变换的核心思想是**"空间映射与投票"**,将图像空间中的点映射到参数空间,通过投票统计找到满足同一参数方程的点集,从而检测出直线、圆等几何形状。
1. 直线的参数化表示
在图像空间中,一条直线可以用极坐标形式表示:
ρ=xcosθ+ysinθ \rho = x \cos\theta + y \sin\theta ρ=xcosθ+ysinθ
- ρ\rhoρ:直线到图像原点(左上角)的距离;
- θ\thetaθ:直线与垂直方向的夹角,范围 0,π0, \\pi0,π。
这种参数化表示的优势是:所有直线都能被唯一表示,且无需处理直线斜率无穷大的特殊情况。
2. 从图像空间到参数空间的映射
图像空间中的一个点 (x0,y0)(x_0, y_0)(x0,y0),在参数空间中对应一条正弦曲线:
ρ=x0cosθ+y0sinθ \rho = x_0 \cos\theta + y_0 \sin\theta ρ=x0cosθ+y0sinθ
图像空间中同一条直线上的多个点,在参数空间中对应的正弦曲线会相交于同一点 (ρ,θ)(\rho, \theta)(ρ,θ)。
3. 累加器投票机制
霍夫变换使用一个二维累加器(投票矩阵)统计参数空间中各点的投票数:
- 遍历二值边缘图像中的所有非零像素点;
- 对每个点,遍历所有可能的 θ\thetaθ 值,计算对应的 ρ\rhoρ;
- 累加器中 (ρ,θ)(\rho, \theta)(ρ,θ) 位置的投票数加1;
- 投票数超过预设阈值的 (ρ,θ)(\rho, \theta)(ρ,θ),即为检测到的直线。
4. 概率霍夫变换(HoughLinesP)
标准霍夫变换只能检测无限长直线,而实际应用中我们通常需要线段(带端点)。OpenCV 提供的 HoughLinesP(概率霍夫变换)是其优化版本,核心改进:
- 随机选取边缘点进行投票,减少计算量;
- 引入线段最小长度、最大间隙两个参数,仅保留连续线段;
- 直接返回线段的两个端点坐标,无需额外计算直线与图像边界的交点。
OpenCV 核心 API 解析
1. 标准霍夫直线检测:cv::HoughLines
cpp
void HoughLines(
InputArray image, // 输入二值边缘图像(如 Canny 输出)
OutputArray lines, // 输出直线参数向量,每个元素为 Vec2f(ρ, θ)
double rho, // ρ 方向步长(像素级精度)
double theta, // θ 方向步长(弧度级精度)
int threshold, // 最低投票数
double srn = 0, // 多尺度霍夫变换的 rho 除数
double stn = 0, // 多尺度霍夫变换的 theta 除数
double min_theta = 0, // θ 范围最小值
double max_theta = CV_PI // θ 范围最大值
);
- 常用参数:
rho=1,theta=CV_PI/180,threshold根据图像调整(一般 50~150); - 输出结果:仅包含直线参数,需额外计算直线与图像边界的交点才能绘制。
2. 概率霍夫线段检测:cv::HoughLinesP
cpp
void HoughLinesP(
InputArray image, // 输入二值边缘图像
OutputArray lines, // 输出线段向量,每个元素为 Vec4i(x1,y1,x2,y2)
double rho, // ρ 方向步长
double theta, // θ 方向步长
int threshold, // 最低投票数
double minLineLength = 0, // 线段最小长度(像素)
double maxLineGap = 0 // 线段间允许的最大间隙(像素)
);
- 关键参数:
minLineLength过滤过短线段,maxLineGap合并断裂的同一直线段; - 输出结果:直接返回线段端点坐标,可直接用
cv::line绘制。
Android 完整工程实现
环境说明
- 开发环境:Android Studio + NDK 27 + OpenCV Android
- 图片要求:支持开发者自行传入 2048×2048 本地图片
- 技术栈:Kotlin + JNI + C++ + OpenCV
布局文件 activity_main.xml
页面分为原图展示区、Canny 边缘图、霍夫直线检测结果区,使用滚动布局适配大图预览:
xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f5f5f5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp"
android:gap="10dp">
<!-- 原始图片展示 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="原始图片"
android:textSize="16sp"
android:textStyle="bold"/>
<ImageView
android:id="@+id/iv_origin"
android:layout_width="match_parent"
android:layout_height="220dp"
android:scaleType="fitCenter"
android:background="#ffffff"/>
</LinearLayout>
<!-- Canny 边缘图 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Canny 边缘图"
android:textSize="16sp"
android:textStyle="bold"/>
<ImageView
android:id="@+id/iv_canny"
android:layout_width="match_parent"
android:layout_height="220dp"
android:scaleType="fitCenter"
android:background="#ffffff"/>
</LinearLayout>
<!-- 霍夫线段检测结果 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="霍夫线段检测结果"
android:textSize="16sp"
android:textStyle="bold"/>
<ImageView
android:id="@+id/iv_hough"
android:layout_width="match_parent"
android:layout_height="220dp"
android:scaleType="fitCenter"
android:background="#ffffff"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
上层 Kotlin 代码 MainActivity.kt
负责加载本地图片、创建位图、调用 JNI 原生方法、展示结果。开发者只需将自己的 2048×2048 图片 放入 res/drawable 目录,修改资源名即可使用:
kotlin
package com.example.houghline
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib")
}
}
/**
* JNI 原生方法:执行 Canny 边缘检测 + 霍夫线段检测
* @param srcBitmap 输入原图 Bitmap
* @param outCanny 输出 Canny 边缘图 Bitmap
* @param outHough 输出霍夫线段检测结果 Bitmap
*/
private external fun processHoughLine(
srcBitmap: Bitmap,
outCanny: Bitmap,
outHough: Bitmap
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ========== 1. 加载你自己的 2048*2048 图片 ==========
val srcBitmap = BitmapFactory.decodeResource(resources, R.drawable.test_image)
// 创建输出位图,尺寸与原图保持一致
val cannyBitmap = Bitmap.createBitmap(srcBitmap.width, srcBitmap.height, Bitmap.Config.ARGB_8888)
val houghBitmap = Bitmap.createBitmap(srcBitmap.width, srcBitmap.height, Bitmap.Config.ARGB_8888)
// ========== 2. 调用原生算法 ==========
processHoughLine(srcBitmap, cannyBitmap, houghBitmap)
// ========== 3. 展示图片 ==========
findViewById<ImageView>(R.id.iv_origin).setImageBitmap(srcBitmap)
findViewById<ImageView>(R.id.iv_canny).setImageBitmap(cannyBitmap)
findViewById<ImageView>(R.id.iv_hough).setImageBitmap(houghBitmap)
}
}
底层 C++ JNI 代码 native-lib.cpp
核心逻辑:Bitmap 与 OpenCV Mat 互转、Canny 边缘检测、概率霍夫线段检测与绘制,附带完整注释:
cpp
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
using namespace cv;
using namespace std;
/**
* Bitmap 转 OpenCV Mat
* @param bitmap Android 上层传入的 Bitmap
* @return 转换后的 BGR 格式 Mat
*/
Mat bitmapToMat(JNIEnv *env, jobject bitmap) {
AndroidBitmapInfo info;
void* pixels = nullptr;
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
Mat rgba(info.height, info.width, CV_8UC4, pixels);
Mat bgr;
cvtColor(rgba, bgr, COLOR_RGBA2BGR);
AndroidBitmap_unlockPixels(env, bitmap);
return bgr;
}
/**
* OpenCV Mat 转 Bitmap,用于回传给 Android 上层展示
* @param srcMat OpenCV 图像矩阵
* @param dstBitmap 目标 Bitmap
*/
void matToBitmap(JNIEnv *env, const Mat& srcMat, jobject dstBitmap) {
AndroidBitmapInfo info;
void* pixels = nullptr;
AndroidBitmap_getInfo(env, dstBitmap, &info);
AndroidBitmap_lockPixels(env, dstBitmap, &pixels);
Mat rgba;
if (srcMat.channels() == 1) {
cvtColor(srcMat, rgba, COLOR_GRAY2RGBA);
} else {
cvtColor(srcMat, rgba, COLOR_BGR2RGBA);
}
memcpy(pixels, rgba.data, info.width * info.height * 4);
AndroidBitmap_unlockPixels(env, dstBitmap);
}
/**
* 霍夫线段检测核心逻辑
* @param srcBgr 输入彩色图像
* @param outCanny 输出 Canny 边缘图像
* @param outHough 输出霍夫线段检测结果图像
*/
void houghLineDetection(const Mat& srcBgr, Mat& outCanny, Mat& outHough) {
// 1. 转灰度图
Mat srcGray;
cvtColor(srcBgr, srcGray, COLOR_BGR2GRAY);
// 2. Canny 边缘检测(霍夫变换输入为二值边缘图)
Canny(srcGray, outCanny, 50, 150, 3);
// 3. 概率霍夫线段检测
vector<Vec4i> lines;
HoughLinesP(
outCanny, lines,
1, // ρ 步长
CV_PI/180, // θ 步长
60, // 最低投票数
100, // 线段最小长度
20 // 线段间最大间隙
);
// 4. 在原图上绘制检测到的线段(绿色线条,线宽 2)
outHough = srcBgr.clone();
for (size_t i = 0; i < lines.size(); i++) {
Vec4i l = lines[i];
line(outHough, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 255, 0), 2);
}
}
/**
* JNI 入口函数:供 Kotlin 调用
*/
extern "C" JNIEXPORT void JNICALL
Java_com_example_houghline_MainActivity_processHoughLine(
JNIEnv *env, jobject thiz,
jobject srcBitmap,
jobject outCanny,
jobject outHough)
{
// 1. Bitmap 转 Mat
Mat srcBgr = bitmapToMat(env, srcBitmap);
Mat matCanny, matHough;
// 2. 执行 Canny + 霍夫线段检测
houghLineDetection(srcBgr, matCanny, matHough);
// 3. 结果回转为 Bitmap,返回上层
matToBitmap(env, matCanny, outCanny);
matToBitmap(env, matHough, outHough);
}
3.4 CMake 配置(CMakeLists.txt)
NDK 编译核心配置,关联 OpenCV 库,按需修改 OpenCV 路径即可:
cmake
cmake_minimum_required(VERSION 3.22.1)
project("houghline")
# 引入 OpenCV 头文件目录
include_directories(E:/xxx/opencv-native/include)
# 配置原生库
add_library(
native-lib
SHARED
native-lib.cpp)
# 链接系统库与 OpenCV 库
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})

运行说明与效果解读
使用步骤
- 将 2048×2048 图片 放入项目
res/drawable文件夹; - 在 Kotlin 代码中修改图片资源名
R.drawable.test_image; - 同步 NDK 配置,编译运行项目;
- 页面自动展示原图 、Canny 边缘图 与霍夫线段检测结果图。
运行效果
- 原图:你自定义的 2048×2048 彩色图片;
- Canny 边缘图:黑色背景 + 白色单像素细边缘,为霍夫变换提供输入;
- 霍夫结果图:在原图上用绿色线条标注出检测到的所有线段,仅保留长度达标、连续的线段。
参数调优指南
根据图片场景调整霍夫变换参数,适配不同场景:
- 道路线检测 :提高
minLineLength(如 150),过滤过短的干扰线段; - 文档边缘检测 :降低
maxLineGap(如 10),保证断裂的文档边缘被合并; - 高噪声图片 :提高
threshold(如 80),减少噪声产生的伪直线; - 低对比度图片 :降低
threshold(如 40),同时降低 Canny 低阈值(如 30),保留更多弱边缘。
总结与拓展
- 算法核心:霍夫变换通过"参数空间投票"实现直线检测,概率霍夫变换是工程中更常用的线段检测方案;
- API 要点 :
HoughLinesP需传入二值边缘图,minLineLength和maxLineGap是控制检测效果的关键参数; - 工程优势:本项目完全基于 Android NDK + OpenCV 实现,支持自定义大图输入,源码注释完整,可直接用于车道线检测、文档校正等项目集成;
- 拓展方向 :可在此基础上增加霍夫圆检测(
HoughCircles)、滑动条动态调整参数、相机实时霍夫检测等功能。
