孔庆威的博客

精于心,简于形


  • 首页

  • 分类

  • 归档

  • 标签

Android左右声道的控制

发表于 2016-08-26 |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

源码

AndroidSoundManagerDemo


我这里主要是用到了AudioTrack实现的左右声道的控制,手机一般都只有两个声道,即左声道和右声道,我们在输出的时候可以选择单声道,也可以选择双声道(立体声)。

查看了AudioTrack的API,提供了play()、pause()、stop()、write()等一系列的方法。
通过write()方法,可以实现将音频数据发送出去(播放出来)。

AudioTrack对象的构造

有三个构造方法

1
2
3
AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode, int sessionId)
AudioTrack (AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)

主要参数有如下几个

  • streamType:以什么形式播放

    • STREAM_VOICE_CALL
    • STREAM_SYSTEM
    • STREAM_RING
    • STREAM_MUSIC
    • STREAM_ALARM
    • STREAM_NOTIFICATION
  • sampleRateInHz:采样率

  • channelConfig:声道

    • AudioFormat.CHANNEL_OUT_MONO:输出单声道音频数据
    • AudioFormat.CHANNEL_OUT_STEREO:输出双声道音频数据(立体声)
  • audioFormat:音频数据格式

  • mode:缓冲模式

    • MODE_STATIC:一次性将音频载入以后再播放
    • MODE_STREAM:以流的形式,加载一点就播放一点

把channelConfig的相关参数都看了一遍,没发现有可以指定向某声道发送数据的,只能通过AudioFormat.CHANNEL_OUT_MONO和AudioFormat.CHANNEL_OUT_STEREO选择是输出单声道的音频数据还是双声道的音频数据。

左右声道控制

构造的时候不能选择指定声道输出音频,但是有这样一个方法

1
setStereoVolume(float leftGain, float rightGain)

可以通过把某一个声道的音量设置到最小,达到只向某个声道输出音频的效果。
我自己也有点”呵呵“,但是也没有发现还有别的方法可以实现这样的效果。

这个方法还有一点小问题,在个别手机上,即使将某个声道的声音设置到了最小,也还是会有一点声音,这个我也还没有搞清楚为什么,个人猜测可能和手机硬件有关系。

封装

我这里的缓冲模式使用的MODE_STREAM的形式,以流的形式播放,因为这个逻辑要稍微复杂一点,尤其是暂停以后再继续播放的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package kong.qingwei.androidsoundmanagerdemo;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by kqw on 2016/8/26.
* 播放音乐的线程
*/
public class PlayThread extends Thread {
// 采样率
private int mSampleRateInHz = 16000;
// 单声道
private int mChannelConfig = AudioFormat.CHANNEL_OUT_MONO;
// 双声道(立体声)
// private int mChannelConfig = AudioFormat.CHANNEL_OUT_STEREO;
private static final String TAG = "PlayThread";
private Activity mActivity;
private AudioTrack mAudioTrack;
private byte[] data;
private String mFileName;
public PlayThread(Activity activity, String fileName) {
mActivity = activity;
mFileName = fileName;
int bufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, AudioFormat.ENCODING_PCM_16BIT);
mAudioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
mSampleRateInHz,
mChannelConfig,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize,
AudioTrack.MODE_STREAM);
}
@Override
public void run() {
super.run();
try {
if (null != mAudioTrack)
mAudioTrack.play();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
InputStream inputStream = mActivity.getResources().getAssets().open(mFileName);
// 缓冲区
byte[] buffer = new byte[1024];
// 播放进度
int playIndex = 0;
// 是否缓冲完成
boolean isLoaded = false;
// 缓冲 + 播放
while (null != mAudioTrack && AudioTrack.PLAYSTATE_STOPPED != mAudioTrack.getPlayState()) {
// 字符长度
int len;
if (-1 != (len = inputStream.read(buffer))) {
byteArrayOutputStream.write(buffer, 0, len);
data = byteArrayOutputStream.toByteArray();
Log.i(TAG, "run: 已缓冲 : " + data.length);
} else {
// 缓冲完成
isLoaded = true;
}
if (AudioTrack.PLAYSTATE_PAUSED == mAudioTrack.getPlayState()) {
// TODO 已经暂停
}
if (AudioTrack.PLAYSTATE_PLAYING == mAudioTrack.getPlayState()) {
Log.i(TAG, "run: 开始从 " + playIndex + " 播放");
playIndex += mAudioTrack.write(data, playIndex, data.length - playIndex);
Log.i(TAG, "run: 播放到了 : " + playIndex);
if (isLoaded && playIndex == data.length) {
Log.i(TAG, "run: 播放完了");
mAudioTrack.stop();
}
if (playIndex < 0) {
Log.i(TAG, "run: 播放出错");
mAudioTrack.stop();
break;
}
}
}
Log.i(TAG, "run: play end");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 设置左右声道平衡
*
* @param max 最大值
* @param balance 当前值
*/
public void setBalance(int max, int balance) {
float b = (float) balance / (float) max;
Log.i(TAG, "setBalance: b = " + b);
if (null != mAudioTrack)
mAudioTrack.setStereoVolume(1 - b, b);
}
/**
* 设置左右声道是否可用
*
* @param left 左声道
* @param right 右声道
*/
public void setChannel(boolean left, boolean right) {
if (null != mAudioTrack) {
mAudioTrack.setStereoVolume(left ? 1 : 0, right ? 1 : 0);
mAudioTrack.play();
}
}
public void pause() {
if (null != mAudioTrack)
mAudioTrack.pause();
}
public void play() {
if (null != mAudioTrack)
mAudioTrack.play();
}
public void stopp() {
releaseAudioTrack();
}
private void releaseAudioTrack() {
if (null != mAudioTrack) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
}

使用

从头开始播放

1
2
mPlayThread = new PlayThread(this, "tts1.pcm");
mPlayThread.start();

暂停

1
mPlayThread.pause();

暂停后继续播放

1
mPlayThread.play();

停止播放

1
2
mPlayThread.stopp();
mPlayThread = null;

左右声道控制

1
2
// 禁用左声道(右声道同理)
mPlayThread.setChannel(false, true);

向左右声道单独输出不同的音频数据

也是一个很”呵呵“的做法,但是依然还没有找到更好的方法。
构造两个AudioTrack对象,分别输出两个音频,一个禁用左声道,一个禁用右声道,达到预期效果。

1
2
3
4
5
6
7
8
mChannelLeftPlayer = new PlayThread(this, "tts1.pcm");
mChannelRightPlayer = new PlayThread(this, "tts2.pcm");
mChannelLeftPlayer.setChannel(true, false);
mChannelRightPlayer.setChannel(false, true);
mChannelLeftPlayer.start();
mChannelRightPlayer.start();

环信即时通讯在Android平台的部署

发表于 2016-08-22 | 分类于 IM |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


官网

环信

下载SDK

创建一个工程

如果想要封装性好一点,也可以在工程里再创建一个环信的Library,然后将SDK里的jar和.so,都导入到工程

导入SDK

清单文件

下面是环信Library库里面的清单文件,包含了权限的设置、APPKEY的设置和服务的声明等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="……">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
<!-- 设置环信应用的AppKey -->
<meta-data
android:name="EASEMOB_APPKEY"
android:value="你的APPKEY" />
<!-- 声明SDK所需的service SDK核心功能-->
<service
android:name="com.hyphenate.chat.EMChatService"
android:exported="true" />
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>

初始化

创建一个InitApplication类,继承Application,用来初始化应用的一些信息。

在主工程的清单文件下声明

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="……">
<application
android:name=".InitApplication"
……>
……
</application>
</manifest>

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package ……;
import android.app.Application;
import android.widget.Toast;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMOptions;
/**
* Created by kqw on 2016/8/22.
* 初始化应用
*/
public class InitApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
EMOptions options = new EMOptions();
// 默认添加好友时,是不需要验证的,改成需要验证
options.setAcceptInvitationAlways(false);
// 初始化
EMClient.getInstance().init(this, options);
// 在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
Toast.makeText(this, "已经初始化", Toast.LENGTH_SHORT).show();
}
}

部署完成

OpenCV检测图像轮廓

发表于 2016-08-22 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


轮廓只不过是图像中连接的曲线,或者图像中连通部分的边界,轮廓通常以图像中的边缘来计算,但是,边缘和轮廓的区别在于轮廓是闭合的,而边缘可以是任意的。边缘的概念局限于点及其邻域像素,轮廓将目标作为整体进行处理。

效果图

效果图

原图

源码

KqwOpenCVFeaturesDemo

步骤

  1. 将图像置灰
  2. 使用Canny边缘检测检测出图像的边缘
  3. 调用Imgproc.findContours()方法检测图像轮廓
  4. 在新的图像上画出轮廓

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 找出轮廓
*
* @param bitmap 要检测的图片
*/
public void findContours(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
// 检测边缘
.map(new Func1<Bitmap, Mat>() {
@Override
public Mat call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// Canny边缘检测器检测图像边缘
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
return cannyEdges;
}
})
// 找出轮廓
.map(new Func1<Mat, Bitmap>() {
@Override
public Bitmap call(Mat cannyEdges) {
Mat hierarchy = new Mat();
// 保存轮廓
ArrayList<MatOfPoint> contourList = new ArrayList<>();
// 检测轮廓
Imgproc.findContours(cannyEdges, contourList, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
// 画出轮廓
Mat contours = new Mat();
contours.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC3);
Random r = new Random();
for (int i = 0; i < contourList.size(); i++) {
Imgproc.drawContours(contours, contourList, i, new Scalar(r.nextInt(255), r.nextInt(255), r.nextInt(255), -1));
}
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(contours.cols(), contours.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(contours, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// 找出轮廓
mFeaturesUtil.findContours(mSelectImage);

OpenCV使用霍夫变换检测图像中的形状

发表于 2016-08-22 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


霍夫变换是一种被广泛使用的利用数学等式的参数形式在图像中检测形状的技术。
例如直线、椭圆、圆等形状。

霍夫变换可以检测任何能以参数形式表示的形状,随着形状的复杂(维数的增加,例如球体),计算的消耗也会增加。
我们通常只考虑简单的霍夫形状,例如直线和圆。

源码

KqwOpenCVFeaturesDemo

霍夫直线

效果图

效果图

原图

步骤

  1. 将图像置灰
  2. 调用Imgproc.HoughLinesP(cannyEdges, lines, 1, Math.PI / 180, 50, 20, 20) 方法获取直线的数据

    • 第一个参数:图像输入
    • 第二个参数:图像输出
    • 第三个参数:图像指定像素中r的解析度
    • 第四个参数:图像指定像素中θ的解析度
    • 第五个参数:直线上点数的阈值
    • 第六个参数:直线上点数的最小值
  3. 在图像上绘制直线

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 霍夫直线
*
* @param bitmap 要检测的图片
*/
public void houghLines(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
// 检测边缘
.map(new Func1<Bitmap, Mat>() {
@Override
public Mat call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// Canny边缘检测器检测图像边缘
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
return cannyEdges;
}
})
// 霍夫直线
.map(new Func1<Mat, Bitmap>() {
@Override
public Bitmap call(Mat cannyEdges) {
Mat lines = new Mat();
Imgproc.HoughLinesP(cannyEdges, lines, 1, Math.PI / 180, 50, 20, 20);
Mat houghLines = new Mat();
houghLines.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC1);
// 在图像上画直线
for (int i = 0; i < lines.cols(); i++) {
double[] points = lines.get(0, i);
double x1, y1, x2, y2;
x1 = points[0];
y1 = points[1];
x2 = points[2];
y2 = points[3];
Point pt1 = new Point(x1, y1);
Point pt2 = new Point(x2, y2);
// 绘制直线
Core.line(houghLines, pt1, pt2, new Scalar(255, 0, 0), 1);
}
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(houghLines.cols(), houghLines.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(houghLines, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// 霍夫直线
mFeaturesUtil.houghLines(mSelectImage);

霍夫圆

效果图

效果图

原图

步骤

霍夫圆与霍夫直线类似,只是等式改变了,调用

1
Imgproc.HoughCircles(cannyEdges, circles, Imgproc.CV_HOUGH_GRADIENT, 1, cannyEdges.rows() / 15);

获取圆的数据

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 霍夫圆
*
* @param bitmap 要检测的图片
*/
public void houghCircles(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
// 检测边缘
.map(new Func1<Bitmap, Mat>() {
@Override
public Mat call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// Canny边缘检测器检测图像边缘
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
return cannyEdges;
}
})
// 霍夫圆
.map(new Func1<Mat, Bitmap>() {
@Override
public Bitmap call(Mat cannyEdges) {
Mat circles = new Mat();
Imgproc.HoughCircles(cannyEdges, circles, Imgproc.CV_HOUGH_GRADIENT, 1, cannyEdges.rows() / 15);
Mat houghCircles = new Mat();
houghCircles.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC1);
// 在图像上画圆
for (int i = 0; i < circles.cols(); i++) {
double[] parameters = circles.get(0, i);
double x, y;
int r;
x = parameters[0];
y = parameters[1];
r = (int) parameters[2];
// 绘制圆
Point center = new Point(x, y);
Core.circle(houghCircles, center, r, new Scalar(255, 0, 0), 1);
}
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(houghCircles.cols(), houghCircles.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(houghCircles, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// 霍夫圆
mFeaturesUtil.houghCircles(mSelectImage);

OpenCV使用Harris算法实现角点检测

发表于 2016-08-19 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

原图

源码

KqwOpenCVFeaturesDemo

角点是两条边缘的交点或者在局部邻域中有多个显著边缘方向的点。Harris角点检测是一种在角点检测中最常见的技术。

Harris角点检测器在图像上使用滑动窗口计算亮度的变化。

封装

这里用到了RxJava。主要是因为图片处理是耗时操作,会阻塞线程,为了防止界面卡顿,这里使用RxJava进行了线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* Harris角点检测
*
* @param bitmap 要检测的图片
*/
public void harris(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
// 检测边缘
.map(new Func1<Bitmap, Mat>() {
@Override
public Mat call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// Canny边缘检测器检测图像边缘
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
return cannyEdges;
}
})
// Harris对角检测
.map(new Func1<Mat, Bitmap>() {
@Override
public Bitmap call(Mat cannyEdges) {
Mat corners = new Mat();
Mat tempDst = new Mat();
// 找出角点
Imgproc.cornerHarris(cannyEdges, tempDst, 2, 3, 0.04);
// 归一化Harris角点的输出
Mat tempDstNorm = new Mat();
Core.normalize(tempDst, tempDstNorm, 0, 255, Core.NORM_MINMAX);
Core.convertScaleAbs(tempDstNorm, corners);
// 在新的图像上绘制角点
Random r = new Random();
for (int i = 0; i < tempDstNorm.cols(); i++) {
for (int j = 0; j < tempDstNorm.rows(); j++) {
double[] value = tempDstNorm.get(j, i);
if (value[0] > 150) {
Core.circle(corners, new Point(i, j), 5, new Scalar(r.nextInt(255), 2));
}
}
}
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(corners.cols(), corners.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(corners, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// Harris角点检测
mFeaturesUtil.harris(mSelectImage);

OpenCV使用Sobel滤波器实现图像边缘检测

发表于 2016-08-19 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

原图

源码

KqwOpenCVFeaturesDemo

Sobel滤波器也叫Sobel算子,与Canny边缘检测一样,需要计算像素的灰度梯度,只不过是换用另一种方式。

使用Sobel算子计算边缘的步骤

  1. 将图像转为灰度图像

    1
    2
    // 原图置灰
    Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
  2. 计算水平方向灰度梯度的绝对值

    1
    2
    Imgproc.Sobel(grayMat, grad_x, CvType.CV_16S, 1, 0, 3, 1, 0);
    Core.convertScaleAbs(grad_x, abs_grad_x);
  3. 计算垂直方法灰度梯度的绝对值

    1
    2
    Imgproc.Sobel(grayMat, grad_y, CvType.CV_16S, 0, 1, 3, 1, 0);
    Core.convertScaleAbs(grad_y, abs_grad_y);
  4. 计算最终梯度

    1
    2
    // 计算结果梯度
    Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 1, sobel);

最终的梯度实质上就是边缘。

这里用到了两个3 * 3的核对图像做卷积来近似地计算水平和垂直方向的灰度梯度

核

封装

这里用到了RxJava。主要是因为图片处理是耗时操作,会阻塞线程,为了防止界面卡顿,这里使用RxJava进行了线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Sobel滤波器
*
* @param bitmap 要检测的图片
*/
public void sobel(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
.map(new Func1<Bitmap, Bitmap>() {
@Override
public Bitmap call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat sobel = new Mat();
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// 计算水平方向梯度
Imgproc.Sobel(grayMat, grad_x, CvType.CV_16S, 1, 0, 3, 1, 0);
// 计算垂直方向梯度
Imgproc.Sobel(grayMat, grad_y, CvType.CV_16S, 0, 1, 3, 1, 0);
// 计算两个方向上的梯度的绝对值
Core.convertScaleAbs(grad_x, abs_grad_x);
Core.convertScaleAbs(grad_y, abs_grad_y);
// 计算结果梯度
Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 1, sobel);
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(sobel.cols(), sobel.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(sobel, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// Sobel滤波器检测图像边缘
mFeaturesUtil.sobel(mSelectImage);

OpenCV使用Canny边缘检测器实现图像边缘检测

发表于 2016-08-19 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

边缘检测结果

原图

源码

KqwOpenCVFeaturesDemo

Canny边缘检测器是一种被广泛使用的算法,并被认为是边缘检测最优的算法,该方法使用了比高斯差分算法更复杂的技巧,如多向灰度梯度和滞后阈值化。

Canny边缘检测器算法基本步骤

  1. 平滑图像:通过使用合适的模糊半径执行高斯模糊来减少图像内的噪声。
  2. 计算图像的梯度:这里计算图像的梯度,并将梯度分类为垂直、水平和斜对角。这一步的输出用于在下一步中计算真正的边缘。
  3. 非最大值抑制:利用上一步计算出来的梯度方向,检测某一像素在梯度的正方向和负方向上是否是局部最大值,如果是,则抑制该像素(像素不属于边缘)。这是一种边缘细化技术,用最急剧的变换选出边缘点。
  4. 用滞后阈值化选择边缘:最后一步,检查某一条边缘是否明显到足以作为最终输出,最后去除所有不明显的边缘。

算法比较复杂,但是使用很简单,首先将图像灰度化

1
2
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);

然后调用Imgproc.Canny()方法即可

1
2
// Canny边缘检测器检测图像边缘
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
  • 第一个参数表示图像输入
  • 第二个参数表述图像输出
  • 第三个参数表示低阈值
  • 第四个参数表示高阈值

在Canny边缘检测算法中,将图像中的点归为三类:

  • 被抑制点

    灰度梯度值 < 低阈值

  • 弱边缘点

    低阈值 <= 灰度梯度值 <= 高阈值

  • 强边缘点

    高阈值 < 灰度梯度值

封装

这里用到了RxJava。主要是因为图片处理是耗时操作,会阻塞线程,为了防止界面卡顿,这里使用RxJava进行了线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Canny边缘检测算法
*
* @param bitmap 要检测的图片
*/
public void canny(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
.map(new Func1<Bitmap, Bitmap>() {
@Override
public Bitmap call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// Canny边缘检测器检测图像边缘
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(cannyEdges.cols(), cannyEdges.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(cannyEdges, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// Canny边缘检测器检测图像边缘
mFeaturesUtil.canny(mSelectImage);

OpenCV高斯差分技术实现图像边缘检测

发表于 2016-08-19 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

边缘检测结果

原图

源码

KqwOpenCVFeaturesDemo

边缘是图像中像素亮度变化明显的点。

高斯差分算法步骤

  1. 将图像转为灰度图像

    1
    2
    // 原图置灰
    Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
  2. 用两个不同的模糊半径对灰度图像执行高斯模糊(取得两幅高斯模糊图像)

    1
    2
    3
    // 以两个不同的模糊半径对图像做模糊处理
    Imgproc.GaussianBlur(grayMat, blur1, new Size(15, 15), 5);
    Imgproc.GaussianBlur(grayMat, blur2, new Size(21, 21), 5);
  3. 将两幅高斯模糊图像做减法,得到一幅包含边缘点的结果图像

    1
    2
    3
    // 将两幅模糊后的图像相减
    Mat diff = new Mat();
    Core.absdiff(blur1, blur2, diff);

该方法只对图像做了高斯模糊,这是计算图像边缘最快的方法之一,但是,该方法的结果也不是很理想,这种方式对某些图像效果很好,但是在某些情况下可能会完全失效。

封装

这里用到了RxJava。主要是因为图片处理是耗时操作,会阻塞线程,为了防止界面卡顿,这里使用RxJava进行了线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 高斯差分算法边缘检测
*
* @param bitmap 要检测的图片
*/
public void differenceOfGaussian(Bitmap bitmap) {
if (null != mSubscriber)
Observable
.just(bitmap)
.map(new Func1<Bitmap, Bitmap>() {
@Override
public Bitmap call(Bitmap bitmap) {
Mat grayMat = new Mat();
Mat blur1 = new Mat();
Mat blur2 = new Mat();
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 原图置灰
Imgproc.cvtColor(src, grayMat, Imgproc.COLOR_BGR2GRAY);
// 以两个不同的模糊半径对图像做模糊处理
Imgproc.GaussianBlur(grayMat, blur1, new Size(15, 15), 5);
Imgproc.GaussianBlur(grayMat, blur2, new Size(21, 21), 5);
// 将两幅模糊后的图像相减
Mat diff = new Mat();
Core.absdiff(blur1, blur2, diff);
// 反转二值阈值化
Core.multiply(diff, new Scalar(100), diff);
Imgproc.threshold(diff, diff, 50, 255, Imgproc.THRESH_BINARY_INV);
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(grayMat.cols(), grayMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(diff, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片特征提取的工具类
mFeaturesUtil = new FeaturesUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mImageView.setImageBitmap(bitmap);
}
});
// 高斯差分技术检测图像边缘
mFeaturesUtil.differenceOfGaussian(mSelectImage);

OpenCV实现图像阈值化

发表于 2016-08-18 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

效果图

源码

KqwOpenCVBlurDemo

阈值化是一种将我们想要在图像中分析的区域分割出来的方法。
我们把每个像素值都与一个预设的阈值做比较,再根据比较的结果调整像素值。

类似这样

1
Imgproc.threshold(src, src, 100, 255, Imgproc.THRESH_BINARY);

其中100是阈值,255是最大值(纯白色的值)。

常量

名称 常量
二值阈值化 Imgproc.THRESH_BINARY
阈值化到零 Imgproc.THRESH_TOZERO
截断阈值化 Imgproc.THRESH_TRUNC
反转二值阈值化 Imgproc.THRESH_BINARY_INV
反转阈值化到零 Imgproc.THRESH_TOZERO_INV

自适应阈值

上述的阈值化是全局性的,我们也可以根据邻域像素为任意像素计算阈值。

自适应阈值用到的3个参数

  1. 自适应方法

    • Imgproc.ADAPTIVE_THRESH_MEAN_C:阈值是邻域像素的值
    • Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是领域像素的加权和,权重来自高斯核
  2. 块尺寸:邻域的大小

  3. 常量C:从对每个像素计算得到的均值或加权均值减去的常量

图像置灰

1
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY);

自适应阈值化

1
Imgproc.adaptiveThreshold(src, src, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 3, 0);

封装

这里我用到了RxJava。主要是因为图片处理是耗时操作,会阻塞线程,为了防止界面卡顿,这里使用RxJava进行了线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 自适应阈值
*
* @param bitmap 要处理的图片
*/
public void adaptiveThreshold(Bitmap bitmap) {
// 使用RxJava处理图片
if (null != mSubscriber)
Observable
.just(bitmap)
.map(new Func1<Bitmap, Bitmap>() {
@Override
public Bitmap call(Bitmap bitmap) {
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 图像置灰
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY);
// 自适应阈值化
Imgproc.adaptiveThreshold(src, src, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 3, 0);
// 二值阈值化
// Imgproc.threshold(src,src,100,255,Imgproc.THRESH_BINARY);
// 阈值化到零
// Imgproc.threshold(src,src,100,255,Imgproc.THRESH_TOZERO);
// 截断阈值化
// Imgproc.threshold(src,src,100,255,Imgproc.THRESH_TRUNC);
// 反转二值阈值化
// Imgproc.threshold(src,src,100,255,Imgproc.THRESH_BINARY_INV);
// 反转阈值化到零
// Imgproc.threshold(src,src,100,255,Imgproc.THRESH_TOZERO_INV);
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(src, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片处理的工具类
mBlurUtil = new BlurUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mIvImageProcessed.setImageBitmap(bitmap);
}
});
// 自适应阈值
mBlurUtil.adaptiveThreshold(mSelectImage);

OpenCV实现图像暗区扩张(腐蚀图片)

发表于 2016-08-18 | 分类于 OpenCV |

转载请说明出处!
作者:kqw攻城狮
出处:个人站 | CSDN


效果图

效果图

效果图

源码

KqwOpenCVBlurDemo

暗区扩张,也叫腐蚀,要实现这样的效果,我们可以选取一个合适大小的核,用被核覆盖的最小值代替锚点像素。

原始图像(左)和腐蚀后的图像(右)

我们首先定义一个合适大小的核

1
Mat kernelErode = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));

然后调用Imgproc.erode()方法把图像的暗区放大

1
2
// 扩大暗区(腐蚀)
Imgproc.erode(src, src, kernelErode);

封装

这里我用到了RxJava。主要是因为图片处理是耗时操作,会阻塞线程,为了防止界面卡顿,这里使用RxJava进行了线程切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 扩大图片暗区(腐蚀图片)
*
* @param bitmap 要处理的图片
*/
public void erode(Bitmap bitmap) {
// 使用RxJava处理图片
if (null != mSubscriber)
Observable
.just(bitmap)
.map(new Func1<Bitmap, Bitmap>() {
@Override
public Bitmap call(Bitmap bitmap) {
// Bitmap转为Mat
Mat src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bitmap, src);
// 定义一个合适大小的核
Mat kernelErode = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));
// 扩大暗区(腐蚀)
Imgproc.erode(src, src, kernelErode);
// Mat转Bitmap
Bitmap processedImage = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(src, processedImage);
return processedImage;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 图片处理的工具类
mBlurUtil = new BlurUtil(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
// 图片处理完成
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
// 图片处理异常
dismissProgressDialog();
}
@Override
public void onNext(Bitmap bitmap) {
// 获取到处理后的图片
mIvImageProcessed.setImageBitmap(bitmap);
}
});
// 扩大图片暗区
mBlurUtil.erode(mSelectImage);
1234…6
kongqw

kongqw

Android Developer

54 日志
4 分类
25 标签
GitHub CSDN
Links
  • 干货集中营
  • 讯飞开放平台
  • 环信
  • 百度统计
  • DISQUS评论
  • BTC Wallet
  • OKCoin中国站
  • OKCoin国际站
  • BTC.com
© 2015 - 2018 kongqw
由 Hexo 强力驱动
主题 - NexT.Pisces