OpenCV
提供的視覺處理算法很是豐富,對圖像、視頻處理提供比較方便的處理方法,本文介紹使用 OpenCV
對圖像進行處理,本文例子基於 Android Studio 3.4.1
,OpenCV 3.4.6
,gradle-5.1.1
,build:gradle:3.4.1
。若下載 Demo 編譯不成功請升級 AS 或 將相關配置修改,項目源碼在文末連接下載。git
主要使用 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;
}
複製代碼
主要是拿到像素值用 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;
}
複製代碼
使用 addWeighted
方法,必須兩張圖的大小同樣api
addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);數組
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;
}
複製代碼
alpha:飽和度,對比度;beta:亮度bash
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;
}
複製代碼
注意: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;
}
複製代碼
本文介紹三種濾波:均值濾波,中值濾波,高斯濾波,對於其原理,鑑於篇幅,本文不作詳細介紹,讀者可自行去了解字體
blur( InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT );gradle
medianBlur( InputArray src, OutputArray dst, int ksize );
GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT );
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。