NDK 開發之 OpenCV 使用實踐

前言

OpenCV 提供的視覺處理算法很是豐富,對圖像、視頻處理提供比較方便的處理方法,本文介紹使用 OpenCV 對圖像進行處理,本文例子基於 Android Studio 3.4.1OpenCV 3.4.6gradle-5.1.1build:gradle:3.4.1。若下載 Demo 編譯不成功請升級 AS 或 將相關配置修改,項目源碼在文末連接下載。git

1. 轉灰度圖

主要使用 cvtColor 方法進行轉換,亦可拿到圖片的像素進行自行轉換github

JNIEXPORT jint JNICALL
Java_com_vegen_opencvproject_ResultUtil_toGray(JNIEnv *env, jobject instance, jobject bitmap) {
   // --------- 第一種方法:使用 api 轉灰度圖 ---------
   /*
   Mat mat;
   bitmap2Mat(env, mat, bitmap);
   Mat gray_mat;
   cvtColor(mat, gray_mat, COLOR_BGRA2GRAY);
   mat2Bitmap(env, gray_mat, bitmap);
   */

   // --------- 第二種方法:原理層面轉灰度圖 ---------
   AndroidBitmapInfo bitmapInfo;
   int info_res = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
   if (info_res != 0) {
       return info_res;
   }

   void *pixels;
   AndroidBitmap_lockPixels(env, bitmap, &pixels);

   // 判斷顏色通道
   if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
       for (int i = 0; i < bitmapInfo.width * bitmapInfo.height; ++i) {
           uint32_t *pixel_p = reinterpret_cast<uint32_t *>(pixels) + i;
           uint32_t pixel = *pixel_p;
           int a = (pixel >> 24) & 0xff;
           int r = (pixel >> 16) & 0xff;
           int g = (pixel >> 8) & 0xff;
           int b = pixel & 0xff;
           // f = 0.213f * r + 0.715f * g + 0.072f * b
           int gery = (int) (0.213f * r + 0.715f * g + 0.072f * b);
           *pixel_p = (a << 24) | (gery << 16) | (gery << 8) | gery;
       }
   } else if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565) {
       for (int i = 0; i < bitmapInfo.width * bitmapInfo.height; ++i) {
           uint16_t *pixel_p = reinterpret_cast<uint16_t *>(pixels) + i;
           uint16_t pixel = *pixel_p;
           // 8888 -> 565
           int r = ((pixel >> 11) & 0x1f) << 3; // 5
           int g = ((pixel >> 5) & 0x3f) << 2; // 6
           int b = (pixel & 0x1f) << 3; // 5
           // f = 0.213f * r + 0.715f * g + 0.072f * b
           int gery = (int) (0.213f * r + 0.715f * g + 0.072f * b); // 8位

           *pixel_p = ((gery >> 3) << 11) | ((gery >> 2) << 5) | (gery >> 3);
       }
   }
   // 其餘通道暫不介紹

   AndroidBitmap_unlockPixels(env, bitmap);
   return 0;
}
複製代碼

轉灰度圖

2. 底片效果

主要是拿到像素值用 255 減之算法

JNIEXPORT jint JNICALL
Java_com_vegen_opencvproject_ResultUtil_negative(JNIEnv *env, jobject instance, jobject bitmap) {
   Mat src;
   bitmap2Mat(env, src, bitmap);

   Mat gary;
   cvtColor(src, gary, COLOR_BGR2GRAY);

   // 讀者能夠註釋下面實現查看效果
   Mat testMat = src.clone();    // 4 通道
   //Mat testMat = gary.clone();     // 1 通道
   // 獲取信息
   int cols = testMat.cols;// 寬
   int rows = testMat.rows;// 高
   int channels = testMat.channels();// 1
   LOGE("cols:%d rows:%d channels:%d", cols, rows, channels);

   // Bitmap 裏面轉的是 4 通道 , 一個通道就能夠表明灰度
   for (int i = 0; i < rows; i++) {
       for (int j = 0; j < cols; j++) {
           if (channels == 3) {
               // 獲取像素 at  Vec3b 個參數
               int b = testMat.at<Vec3b>(i, j)[0];
               int g = testMat.at<Vec3b>(i, j)[1];
               int r = testMat.at<Vec3b>(i, j)[2];

               // 修改像素 (底片效果)
               testMat.at<Vec3b>(i, j)[0] = 255 - b;
               testMat.at<Vec3b>(i, j)[1] = 255 - g;
               testMat.at<Vec3b>(i, j)[2] = 255 - r;
           } else if (channels == 4) {
               // 獲取像素 at  Vec4b 個參數
               int b = testMat.at<Vec4b>(i, j)[0];
               int g = testMat.at<Vec4b>(i, j)[1];
               int r = testMat.at<Vec4b>(i, j)[2];
               int a = testMat.at<Vec4b>(i, j)[3];
               // 修改像素 (底片效果)
               testMat.at<Vec4b>(i, j)[0] = 255 - b;
               testMat.at<Vec4b>(i, j)[1] = 255 - g;
               testMat.at<Vec4b>(i, j)[2] = 255 - r;
           } else if (channels == 1) {
               uchar pixels = testMat.at<uchar>(i, j);
               testMat.at<uchar>(i, j) = 255 - pixels;
           }
       }
   }

   mat2Bitmap(env, testMat, bitmap);
   return 0;
}
複製代碼

底片效果

3. 圖層疊加

使用 addWeighted 方法,必須兩張圖的大小同樣api

addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);數組

  • 第一個參數,InputArray類型的src1,表示須要加權的第一個數組,經常填一個Mat。
  • 第二個參數,alpha,表示第一個數組的權重
  • 第三個參數,src2,表示第二個數組,它須要和第一個數組擁有相同的尺寸和通道數。
  • 第四個參數,beta,表示第二個數組的權重值。
  • 第五個參數,dst,輸出的數組,它和輸入的兩個數組擁有相同的尺寸和通道數。
  • 第六個參數,gamma,一個加到權重總和上的標量值。
  • 第七個參數,dtype,輸出陣列的可選深度,有默認值-1。;當兩個輸入數組具備相同的深度時,這個參數設置爲-1(默認值),即等同於src1.depth()
JNIEXPORT jint JNICALL
Java_com_vegen_opencvproject_ResultUtil_layerOverlay(JNIEnv *env, jobject instance, jobject bitmap,
                                                    jobject layerDrawable) {
   Mat img;
   bitmap2Mat(env, img, bitmap);

   Mat logo;
   bitmap2Mat(env, logo, layerDrawable);

   Mat imgROI1 = img(Rect(0, 0, logo.cols, logo.rows));
   Mat imgROI2 = img(Rect(img.cols - logo.cols, img.rows - logo.rows, logo.cols, logo.rows));
   /**
    * addWeighted 方法必須兩張圖的大小同樣
    * addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);
    * 第一個參數,InputArray類型的src1,表示須要加權的第一個數組,經常填一個Mat。
    * 第二個參數,alpha,表示第一個數組的權重
    * 第三個參數,src2,表示第二個數組,它須要和第一個數組擁有相同的尺寸和通道數。
    * 第四個參數,beta,表示第二個數組的權重值。
    * 第五個參數,dst,輸出的數組,它和輸入的兩個數組擁有相同的尺寸和通道數。
    * 第六個參數,gamma,一個加到權重總和上的標量值。
    * 第七個參數,dtype,輸出陣列的可選深度,有默認值-1。;當兩個輸入數組具備相同的深度時,這個參數設置爲-1(默認值),即等同於src1.depth()
    */
   addWeighted(imgROI1, 1, logo, 1, 0.0, imgROI1);
   addWeighted(imgROI2, 1, logo, 1, 0.0, imgROI2);

   mat2Bitmap(env, img, bitmap);
   return 0;
}
複製代碼

圖層疊加

4. 飽和度、對比度、亮度調節

alpha:飽和度,對比度;beta:亮度bash

  • F(R) = alpha*R + beta;
  • F(G) = alpha*G + beta;
  • F(B) = alpha*B + beta;
JNIEXPORT jint JNICALL
Java_com_vegen_opencvproject_ResultUtil_chromaChange(JNIEnv *env, jobject instance, jobject bitmap) {
   Mat src;
   bitmap2Mat(env, src, bitmap);

   int cols = src.cols;// 寬
   int rows = src.rows;// 高
   int channels = src.channels();// 通道

   LOGE("chromaChange-->channels=%d", channels);   // 4

   // alpha 飽和度 , 對比度
   // beta 亮度
   // F(R) = alpha*R + beta;
   // F(G) = alpha*G + beta;
   // F(B) = alpha*B + beta;

   float alpha = 1.2f;
   float beta = 20;

   for (int i = 0; i < rows; i++) {
       for (int j = 0; j < cols; j++) {

           if (channels == 3) {
               // 獲取像素 at  Vec3b 個參數
               int b = src.at<Vec3b>(i, j)[0];
               int g = src.at<Vec3b>(i, j)[1];
               int r = src.at<Vec3b>(i, j)[2];

               src.at<Vec3b>(i, j)[0] = saturate_cast<uchar>(b * alpha + beta);
               src.at<Vec3b>(i, j)[1] = saturate_cast<uchar>(g * alpha + beta);
               src.at<Vec3b>(i, j)[2] = saturate_cast<uchar>(r * alpha + beta);
           } else if (channels == 4) {
               // 獲取像素 at  Vec4b 個參數
               int b = src.at<Vec4b>(i, j)[0];
               int g = src.at<Vec4b>(i, j)[1];
               int r = src.at<Vec4b>(i, j)[2];
               int a = src.at<Vec4b>(i, j)[3];

               src.at<Vec4b>(i, j)[0] = saturate_cast<uchar>(b * alpha + beta);
               src.at<Vec4b>(i, j)[1] = saturate_cast<uchar>(g * alpha + beta);
               src.at<Vec4b>(i, j)[2] = saturate_cast<uchar>(r * alpha + beta);
               src.at<Vec4b>(i, j)[3] = 255;
           } else if (channels == 1) {
               uchar pixels = src.at<uchar>(i, j);
               src.at<uchar>(i, j) = saturate_cast<uchar>(pixels * alpha + beta);
           }
       }
   }

   mat2Bitmap(env, src, bitmap);
   return 0;
}

複製代碼

色值(飽和度、亮度)調節

5. 繪製形狀和文字

注意:Scalar 四個參數分別對應 B G R Adom

涉及方法函數

  • 畫線:line
  • 矩形:rectangle
  • 橢圓:ellipse
  • 多邊形:fillPoly
  • 圓:circle
  • 文字:putText
JNIEXPORT jint JNICALL
Java_com_vegen_opencvproject_ResultUtil_sketchpad(JNIEnv *env, jobject instance, jobject bitmap) {
   Mat src;
   bitmap2Mat(env, src, bitmap);

   // 注意:Scalar 四個參數分別對應 B G R A

   // 線 line
   line(src, Point(0, 0), Point(500, 500), Scalar(0, 0, 255, 255), 20, LINE_8);

   // 矩形 rectangle
   rectangle(src, Point(500, 500), Point(1000, 1000), Scalar(255, 0, 0, 255), 20, LINE_8);

   // 橢圓 ellipse
   // 第二個參數是: 橢圓的中心點
   // 第三個參數是: Size 第一個值是橢圓 x width 的半徑 ,第二個 ...
   ellipse(src, Point(src.cols / 2, src.rows / 2), Size(src.cols / 8, src.rows / 4), 360, 0, 360,
           Scalar(0, 255, 255, 255), 20);

   // 三角形
   Point pts[1][4];
   pts[0][0] = Point(500, 500);
   pts[0][1] = Point(500, 1000);
   pts[0][2] = Point(1000, 1000);
   pts[0][3] = Point(500, 500);

   const Point *ptss[] = {pts[0]};
   const int npts[] = {4};
   /*
    * 填充 fillPoly 多邊形
    Mat& img, const Point** pts,
                        const int* npts, int ncontours,
                        const Scalar& color, int lineType = LINE_8, int shift = 0,
                        Point offset = Point()
    */

   fillPoly(src, ptss, npts, 1, Scalar(255, 0, 0), 20);

   // 圓 circle
   circle(src, Point(src.cols / 2, src.rows / 2), src.rows / 4, Scalar(255, 255, 0, 255), 20, LINE_AA);

   // 文字
   const String text = "Hello World";
   int fontFace = CV_FONT_BLACK;   // 字體
   double fontScale = 6;           // 字體縮放比
   int thickness = 2;              // 畫筆厚度
   int baseline = 0;               // 基線
   // 獲取文字寬度
   /*
    const String& text, int fontFace,
                           double fontScale, int thickness,
                           CV_OUT int* baseLine
    */
   Size textSize = getTextSize(text, fontFace, fontScale, thickness, &baseline);
   // 文字 putText
   /*
    InputOutputArray img, const String& text, Point org,
                        int fontFace, double fontScale, Scalar color,
                        int thickness = 1, int lineType = LINE_8,
                        bool bottomLeftOrigin = false
    */
   putText(src, text, Point(src.cols / 2 - textSize.width / 2, 200), fontFace, fontScale, Scalar(255, 255, 255, 255),
           thickness, LINE_AA);

   // 隨機畫 srand 畫線
   // opencv 作隨機 srand random 效果同樣
   RNG rng(time(NULL));

   // 隨機生成十條線
   for (int i = 0; i < 10; i++) {
       Point sp;
       sp.x = rng.uniform(0, src.cols);
       sp.y = rng.uniform(0, src.rows);
       Point ep;
       ep.x = rng.uniform(0, src.cols);
       ep.y = rng.uniform(0, src.rows);
       line(src, sp, ep, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255), 255), 4);
   }

   mat2Bitmap(env, src, bitmap);
   return 0;
}
複製代碼

繪製形狀和文字

6. 三種濾波模糊

本文介紹三種濾波:均值濾波,中值濾波,高斯濾波,對於其原理,鑑於篇幅,本文不作詳細介紹,讀者可自行去了解字體

均值濾波

blur( InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT );gradle

  • 第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象便可。該函數對通道是獨立處理的,且能夠處理任意通道數的圖片,但須要注意,待處理的圖片深度應該爲CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
  • 第二個參數,OutputArray類型的dst,即目標圖像,須要和源圖片有同樣的尺寸和類型。好比能夠用Mat::Clone,以源圖片爲模板,來初始化獲得如假包換的目標圖。
  • 第三個參數,Size類型的 ksize,內核的大小。通常這樣寫Size( w,h )來表示內核的大小( 其中,w 爲像素寬度, h爲像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
  • 第四個參數,Point類型的anchor,表示錨點(即被平滑的那個點),注意他有默認值Point(-1,-1)。若是這個點座標是負值的話,就表示取核的中心爲錨點,因此默認值Point(-1,-1)表示這個錨點在覈的中心。
  • 第五個參數,int類型的borderType,用於推斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT,咱們通常不去管它。
中值濾波

medianBlur( InputArray src, OutputArray dst, int ksize );

  • InputArray src: 輸入圖像,圖像爲一、三、4通道的圖像,當模板尺寸爲3或5時,圖像深度只能爲CV_8U、CV_16U、CV_32F中的一個,如而對於較大孔徑尺寸的圖片,圖像深度只能是CV_8U。
  • OutputArray dst: 輸出圖像,尺寸和類型與輸入圖像一致,可使用Mat::Clone以原圖像爲模板來初始化輸出圖像dst
  • int ksize: 濾波模板的尺寸大小,必須是大於1的奇數,如三、五、7……
高斯濾波

GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT );

  • InputArray src: 輸入圖像,能夠是Mat類型,圖像深度爲CV_8U、CV_16U、CV_16S、CV_32F、CV_64F。
  • OutputArray dst: 輸出圖像,與輸入圖像有相同的類型和尺寸。
  • Size ksize: 高斯內核大小,這個尺寸與前面兩個濾波kernel尺寸不一樣,ksize.width和ksize.height能夠不相同可是這兩個值必須爲正奇數,若是這兩個值爲0,他們的值將由sigma計算。
  • double sigmaX: 高斯核函數在X方向上的標準誤差
  • double sigmaY: 高斯核函數在Y方向上的標準誤差,若是sigmaY是0,則函數會自動將sigmaY的值設置爲與sigmaX相同的值,若是sigmaX和sigmaY都是0,這兩個值將由ksize.width和ksize.height計算而來。具體能夠參考getGaussianKernel()函數查看具體細節。建議將size、sigmaX和sigmaY都指定出來。
  • int borderType = BORDER_DEFAULT: 推斷圖像外部像素的某種便捷模式,有默認值BORDER_DEFAULT,若是沒有特殊須要不用更改,具體能夠參考borderInterpolate()函數。
JNIEXPORT jint JNICALL
Java_com_vegen_opencvproject_ResultUtil_blur(JNIEnv *env, jobject instance, jobject bitmap) {

   Mat src;
   bitmap2Mat(env, src, bitmap);

   // 每橫向 1/3 演示一種處理效果,請仔細觀察

   /** 均值濾波 **/
   Size size = Size(29, 29);
   Mat mat1 = src(Rect(0, 0, src.cols / 3, src.rows));
   /*
    blur( InputArray src, OutputArray dst,
                       Size ksize, Point anchor = Point(-1,-1),
                       int borderType = BORDER_DEFAULT );
    第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象便可。該函數對通道是獨立處理的,且能夠處理任意通道數的圖片,但須要注意,待處理的圖片深度應該爲CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
    第二個參數,OutputArray類型的dst,即目標圖像,須要和源圖片有同樣的尺寸和類型。好比能夠用Mat::Clone,以源圖片爲模板,來初始化獲得如假包換的目標圖。
    第三個參數,Size類型的 ksize,內核的大小。通常這樣寫Size( w,h )來表示內核的大小( 其中,w 爲像素寬度, h爲像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
    第四個參數,Point類型的anchor,表示錨點(即被平滑的那個點),注意他有默認值Point(-1,-1)。若是這個點座標是負值的話,就表示取核的中心爲錨點,因此默認值Point(-1,-1)表示這個錨點在覈的中心。
    第五個參數,int類型的borderType,用於推斷圖像外部像素的某種邊界模式。有默認值BORDER_DEFAULT,咱們通常不去管它。
    */
   blur(mat1, mat1, size);

   line(src, Point(src.cols / 3, 0), Point(src.cols / 3, src.rows), Scalar(255, 255, 255, 255), 3, LINE_8);

   /** 中值模糊 **/
   Mat mat2 = src(Rect(src.cols / 3, 0, src.cols / 3, src.rows));
   medianBlur(mat2, mat2, 31);

   line(src, Point(2 * src.cols / 3, 0), Point(2 * src.cols / 3, src.rows), Scalar(255, 255, 255, 255), 3, LINE_8);

   /** 高斯模糊 **/
   Mat mat3 = src(Rect(2 * src.cols / 3, 0, src.cols / 3, src.rows));
   /*
    GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                               double sigmaX, double sigmaY = 0,
                               int borderType = BORDER_DEFAULT );
    InputArray src: 輸入圖像,能夠是Mat類型,圖像深度爲CV_8U、CV_16U、CV_16S、CV_32F、CV_64F。
    OutputArray dst: 輸出圖像,與輸入圖像有相同的類型和尺寸。
    Size ksize: 高斯內核大小,這個尺寸與前面兩個濾波kernel尺寸不一樣,ksize.width和ksize.height能夠不相同可是這兩個值必須爲正奇數,若是這兩個值爲0,他們的值將由sigma計算。
    double sigmaX: 高斯核函數在X方向上的標準誤差
    double sigmaY: 高斯核函數在Y方向上的標準誤差,若是sigmaY是0,則函數會自動將sigmaY的值設置爲與sigmaX相同的值,若是sigmaX和sigmaY都是0,這兩個值將由ksize.width和ksize.height計算而來。具體能夠參考getGaussianKernel()函數查看具體細節。建議將size、sigmaX和sigmaY都指定出來。
    int borderType=BORDER_DEFAULT: 推斷圖像外部像素的某種便捷模式,有默認值BORDER_DEFAULT,若是沒有特殊須要不用更改,具體能夠參考borderInterpolate()函數。
    */
   GaussianBlur(mat3, mat3, Size(41, 41), 0, 0);

   mat2Bitmap(env, src, bitmap);
   return 0;
}

複製代碼

濾波模糊

後話

OpenCV 的圖片處理功能還有不少,本文介紹僅經常使用的一些處理效果實現,文中 Demo 源碼下載地址:github.com/Vegen/OpenC…,歡迎 star。

相關文章
相關標籤/搜索