Android opencv笔记

Mac环境下opencv for android笔记

想不到时隔一年,又要接触NDK了。。。
首先按照在Android Studio中安装OpenCV mac环境/Linux环境小试了一把。

JNI Tip

jni的文件夹名必须是作者截图中的jniLibs(因为这个是Gradle默认的JNI文件夹),不然System.loadLibrary方法会报错。也可以用另一个属性 jniLibs.srcDirs = ['libs']设置,这样的话就把JNI文件放到与src同级的libs文件夹。
另外,只需要复制要支持的cpu架构的文件夹。如果只需要调用opencv中封装好的JNI接口,文件夹中只保留opencv_java.so这个文件。

opencv 初始化

调用OpenCVLoader.initAsync()的话会检测 OpenCVManager 这个程序有没有安装,没有就会引导用户安装。OpenCVManager里包含的是你要调用的各种so文件,一应俱全。
但这样显然会影响用户体验,所以推荐另一个初始化方法OpenCVLoader.initDebug()
这个方法基本等同于System.loadLibrary("opencv_java")
opencv_java是我们上一步放到jniLibs下的libopencv_java.so文件, 包含了所有opencv封装的JNI接口。
如果你还需要使用其他so文件,可以使用 System.loadLibrary继续加载。这样,初始化的逻辑就搞清楚了。初始化可以在onResume(),或者static 代码块里执行。

基本数据结构和概念解释
Size

opencV的Imgproc有很多模糊函数, 它们都需要传入Size参数。参数名为ksize, 是kernel size的缩写,即滤波器模板(核)的尺寸。构造函数 Size(w,h) w 为像素宽度, h为像素高度。Size(3,3)就是33的核。像Size和blockSize这种,边长还是设置成2,5,7等奇数比较好理解。虽然有时候22也可以设置,但不知道和3*3有啥区别。

  • Scalar
     public Scalar</font>(double v0) {
        val = new double[] { v0, 0, 0, 0 };
         }
      以上面单个参数的构造方法为例,可以看出是一个size为4的一维数组。应用举例:
      //与一维数组相乘,所以结果是第一个通道(ARGB的话就是alpha通道)的值被放大一百倍,其他通道的值变为0
     Core.multiply(mat1, new Scalar(100), mat1);
  • Sobel
     //这个构造函数的dx指的是x方向求导的阶数,dy指的是y方向求导的阶数。ddepth指的是输出图像的深度。
     public static void Sobel(Mat src, Mat dst, 
     int ddepth, int dx, int dy, int ksize,
      double scale, double delta);
  • Mat
    Mat即矩阵(Matrix)的缩写, 是保存图像像素信息的矩阵。它主要包含两部分:矩阵头和一个指向像素数据的矩阵指针。代码示例:
//构造一个3*3卷积核,8位无符号整型单通道。
Mat kernel= new Mat( 3, 3, CvType.CV_8UC1);
//前两个参数表示操作起始坐标,为(0,0),之后的参数为填充数据[0,-1, 0,-1, 5,-1, 0,-1, 0] 
//因为是单通道,所以9个数刚好能填满。如果是4通道,就需要9*4才能填满。
kernel. put( 0, 0, 0,-1, 0,-1, 5,-1, 0,-1, 0); 

得到的卷积核如下

0 -1 0
-1 5 -1
0 -1 0

霍夫变换
参考文章霍夫变换 确定图像上直线位置
以检测直线为例,
通过定义理解:
笛卡尔坐标系的点(X, Y)对应着经过它的无数条直线,这无数条直线在p-θ平面上(p轴代表直线截距,θ代表直线夹角)上可以用一条直线表示。把笛卡尔坐标系的大量的点都映射到p-θ平面上,就有了大量直线。如果p-θ平面上存在大量直线在某个点相交,就说明笛卡尔坐标系包含一条直线,直线的斜率和截距对应着此点的p和θ。

通过公式理解:
其实,笛卡尔坐标系的直线公式转化一下,也能得出结论,就是个相对的思维。
用y = kx+b表示笛卡尔坐标系的任意一条直线,这样x, y, k, b 都是未知数了。
而b 和k 通过三角函数可以转化成p和θ,
暂且用b = f(p,θ)和 k = g(p,θ)
这样,直线y = kx+b上的点,虽然每一个点都能在p-θ平面上映射无数条直线,但必定每个点映射的直线必定有一条是
f(p,θ) = y- g(p,θ)x
笛卡尔坐标系里,确定y = kx+b的参数值,只需要两个在这条直线上的不同的点的坐标(x0,y0), (x1,y1)
把同样的(x0,y0), (x1,y1)带入到方程f(p,θ) = y- g(p,θ)x,就可以求出p, θ的值了。所以,笛卡尔坐标系的直线就对应着p-θ平面上的一个点。

在检测圆的过程中,发现Imgproc.HoughCircles方法居然会改变输入的Mat, 也就是第一个参数。而且如果采用new Mat()的方法生成Mat, 并且不是第一个Mat, 就可能会影响之前的Mat。而调用Mat.zeros方法就不会影响。暂时当作opencv4Android的一个bug吧,C++版本应该没这么明显的bug。

在JNI中调用openCV

在JNI中使用openCV时,如果报错imread imwrite等undefined reference, 可能是因为编译时STL配置的问题,需要使用gnustl_shared: Recently, NDK switched to libc++ as default STL, but OpenCV is built with gnustl

YUV21转RGB

先了解常见的视频格式:
视频存储格式YUV420 NV12 NV21 i420 YV12

YUV21转RGB的方法有很多种, 效率对比如下:
【视频处理】YUV与RGB格式转换
Android libyuv应用系列(二)libyuv在Android中的使用
使用libyuv对YUV数据进行缩放,旋转,镜像,裁剪等操作
(libYUV的话需要先将相机的NV21(YUV420sp)数据转成I420(YUV420P) )
最后觉得OpenCV的方式既高保真, 速度也快, 也提供了镜像/旋转之类的接口,接入也方便:
android + java opencv + Mat与byte[]互换

JNI打印Mat信息:

void printMAtMessage(Mat &mat) {
    LOGD("printMAtMessage","***************************Mat信息开始************************");
    LOGD("printMAtMessage","mat.rows %d",mat.rows);
    LOGD("printMAtMessage","mat.cols %d",mat.cols);
    LOGD("printMAtMessage","mat.total %d",mat.total());
    LOGD("printMAtMessage","mat.channels %d",mat.channels());
    LOGD("printMAtMessage","mat.depth %d",mat.depth());
    LOGD("printMAtMessage","mat.type %d",mat.type());
    LOGD("printMAtMessage","mat.flags %d",mat.flags);
    LOGD("printMAtMessage","mat.elemSize %d",mat.elemSize());
    LOGD("printMAtMessage","mat.elemSize1 %d",mat.elemSize1());
    LOGD("printMAtMessage","mat.data[0] %d",mat.data[0]);
    LOGD("printMAtMessage","mat.data[1] %d",mat.data[1]);
    LOGD("printMAtMessage","mat.data[mat.total()*mat.elemSize()-1]) %d",mat.data[mat.total()*mat.elemSize()-1]);
    LOGD("printMAtMessage","mat.data[mat.cols*mat.elemSize()-1] %d",mat.data[mat.cols*mat.elemSize()-1]);
    LOGD("printMAtMessage","mat.data[mat.total()*mat.elemSize()-mat.cols*mat.elemSize()] %d",mat.data[mat.total()*mat.elemSize()-mat.cols*mat.elemSize()]);
    LOGD("printMAtMessage","***************************Mat信息结束************************");
}
  • OpenCV 二进制文件和RGB图像互转:
/** 
RGB图像转二进制文件
**/
// imread()导入图片时是BGR通道顺序
cv::Mat sourceImage = cv::imread("xxx.png");// 当前文件的相对路径或者绝对路径
if (sourceImage.empty())
{
    return -1;
}

cv::Mat  bgr;
//OpenCV操作图片时经常需要拷贝一份操作,避免修改原数据;
sourceImage.copyTo(bgr);

FILE* fpw = fopen("C:\\Users\\xxx\\Desktop\\RGB.txt", "wb"); // wb write as binary file
// 字符模式(w模式)打开的文件,在windows下,遇到0x0A进行写入(也就是\n)会替换为0x0D和0x0A(分别是\r和\n)
if (fpw == NULL)
{
    return -1;
}
// write image to binary format file
int rows = bgr.rows; // 高
int cols = bgr.cols; // 宽
uint8_t* dp = (uint8_t*)bgr.data;//
for (int i = 0; i < rows * cols; i++)
    {
    fwrite(&dp[i * 3], sizeof(uint8_t), 1, fpw);
    fwrite(&dp[i * 3 + 1], sizeof(uint8_t), 1, fpw);
    fwrite(&dp[i * 3 + 2], sizeof(uint8_t), 1, fpw);
    }
// 或者一行搞定write
// fwrite(dp, rows * cols * 3, 1, fpw);// 1 byte 即sizeof(uint8_t)
// 或者 fwrite(dp, bgr.step * rows, 1, fpw); 
// cv::Mat::step 即mat矩阵的每一行的字节数,BGR3字节,即cv::Mat::step = bgr.cols * 3
fclose(fpw)


/** 
二进制文件转RGB
**/
//CV_8UC3 is an 8-bit unsigned integer matrix/image with 3 channels
Mat zeroImg = Mat::zeros(width, height, CV_8UC3);//创建像素值全为0的图像
// BGR格式的二进制图像数据,  rb read as binary file
FILE* fpr = fopen("C:\\Users\\xxx\\Desktop\\BRG_Binary.txt", "rb");
if (fpr == NULL)
{
    return -1;
}
fread_s(zeroImg.data, width*height*3,1, width*height*3, fpr);// 建议用空的Mat数据,避免数据覆盖引发错误结论
// 或者 fread(zeroImg.data, width*height*3, 1,  fpr);
fclose(fpr);
cv::imwrite("image.png", zeroImg);// 相对路径或者绝对路径
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容