本人原创转载请注明出处
前言
本算法是基于摄像头到测量物体的已知距离和摄像头像素焦距的前提下测量物体的宽高,并根据参照物的实际尺寸测量其他物体。
效果图
准备环境
1.首先硬件你要有一个摄像头,最好是USB即插即用免驱的
2.我使用的是12寸的安卓平板,阁下可用任意一台手机或者平板,Android系统在4.4以上版本就行
3.引入我们需要的OpenCV ,这里使用的是无依赖管理包4.11.0版本
implementation 'org.opencv:opencv:4.11.0'
第一步 新建项目,引入依赖包
这里默认阁下已经把项目新建完成
在项目的build.gradle下引入OpenCV4.11.0 并同步sync
第二步 新建一个USBCamera2的类继承自JavaCamera2View
主要是为了后续更改摄像头参数,如果阁下只是使用画面预览的功能可以不用继承,直接使用JavaCamera2View在Xml布局中
USBCamera2类的实现
public class USBCamera2 extends JavaCamera2View {public USBCamera2(Context context, int cameraId) {super(context, cameraId);}public USBCamera2(Context context, AttributeSet attrs) {super(context, attrs);}public void openCamera(){}public void setCameraWH(int w,int h){setMaxFrameSize(w,h);}@Overrideprotected void deliverAndDrawFrame(CvCameraViewFrame frame) {super.deliverAndDrawFrame(frame);}
}
第三步 在你的MainActivity的布局在layout布局中使用
<com.example.opencv_demo.USBCamera2android:id="@+id/usbcamera2"android:layout_width="match_parent"android:layout_height="match_parent"opencv:camera_id="any"opencv:show_fps="true"></com.example.opencv_demo.USBCamera2>
注意:引入属性时需在跟节点加入自定义属性,来自OpenCV
xmlns:opencv="http://schemas.android.com/apk/res-auto"
camera_id="any" 指任意摄像头,阁下如果有多个摄像头,可指定摄像头id,一般使用手机运行项目的话id=0为前置摄像头,1为后置摄像头
show_fps="true" 是否显示帧率以及预览的画面大小
第四步 在MainActivity的中预览画面
首先预览前需要在AndroidMainfest加入权限
<uses-feature android:name="android.hardware.camera" android:required="false"/><uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/><uses-feature android:name="android.hardware.camera.front" android:required="false"/><uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>
在你的application节点下开启硬件加入
android:hardwareAccelerated="true"
ok,现在回到MainActivity
首先MainActivity 继承CameraActivity 回检测请求所需要的权限,再次我没有进行权限请求因为我使用的设备默认获取了Root权限,阁下如果没获取Root权限可正常申请摄像头权限
然后设置USBCamera2可见
class MainActivity : CameraActivity() {lateinit var cv: CameraBinding;override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)cv=CameraBinding.inflate(layoutInflater);setContentView(cv.root)cv.usbcamera2.visibility = CameraBridgeViewBase.VISIBLE
下面检测是否初始化成功
if (OpenCVLoader.initLocal()){//后续操作
}else{Log.e("MainActivity","初始化失败")
}
第五步 在MainActivity的生命周期中监听
override fun onResume() {super.onResume()cv.usbcamera2.enableView()}override fun onPause() {super.onPause()cv.usbcamera2.disableView()}override fun onDestroy() {super.onDestroy()cv.usbcamera2.disableView()
}
重载获取摄像头列表的方法
override fun getCameraViewList(): List<CameraBridgeViewBase?>? {return Collections.singletonList(cv.usbcamera2)}
第六步 完成以上步骤后就可以预览画面了
添加监听,在onCreate中
cv.usbcamera2.setCvCameraViewListener(object : CvCameraViewListener2 {override fun onCameraViewStarted(width: Int, height: Int) {}override fun onCameraViewStopped() {}override fun onCameraFrame(inputFrame: CameraBridgeViewBase.CvCameraViewFrame?): Mat? {val srcMat = inputFrame!!.rgba()var gray = Mat()val blurred = Mat()val edges = Mat()//灰度转换Imgproc.cvtColor(srcMat, gray, Imgproc.COLOR_BGR2GRAY)//高斯模糊Imgproc.GaussianBlur(gray, blurred, Size(5.0, 5.0), 0.0)//canny边缘检测Imgproc.Canny(blurred, edges, 20.0, 30.0)var contours = mutableListOf<MatOfPoint>()val hierarchy = Mat()Imgproc.findContours(edges,contours,hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE )
// // Step 3: 筛选最大轮廓contours.maxByOrNull { Imgproc.contourArea(it) }?.let { maxContour ->val rect = Imgproc.boundingRect(maxContour)var pixelPerMetric = 20.0 / 6.5// Step 4: 计算实际尺寸val actualWidth = rect.width / pixelPerMetricval actualHeight = rect.height / pixelPerMetric// Step 5: 绘制结果Imgproc.rectangle(srcMat, rect, Scalar(0.0, 255.0, 0.0), 2)val text = "%.1fmm x %.1fmm".format(actualWidth, actualHeight)var outLine=30.0 //边框外线延长var radius=15 //圆圈半径var thickness=2 //边框厚度
// Imgproc.circle(srcMat, Point(rect.x.toDouble(), rect.y.toDouble()), 5, Scalar(255.0, 0.0, 0.0), 2)//中心圆圈Imgproc.circle(srcMat, Point(rect.x.toDouble()+rect.width/2, rect.y.toDouble()+rect.height/2), radius, Scalar(255.0, 0.0, 0.0), thickness)//竖线drawDashedLine(srcMat, Point(rect.x.toDouble()+rect.width/2, rect.y.toDouble()-outLine), Point(rect.x.toDouble()+rect.width/2, rect.y.toDouble()+rect.height+outLine), Scalar(255.0, 0.0, 0.0), thickness,4,7)
// Imgproc.line(srcMat, Point(rect.x.toDouble()+rect.width/2, rect.y.toDouble()-outLine), Point(rect.x.toDouble()+rect.width/2, rect.y.toDouble()+rect.height+outLine), Scalar(255.0, 0.0, 0.0), thickness,2,2)//横线drawDashedLine(srcMat, Point(rect.x.toDouble()-outLine, rect.y.toDouble()+rect.height/2), Point(rect.x.toDouble()+rect.width+outLine, rect.y.toDouble()+rect.height/2), Scalar(255.0, 0.0, 0.0), thickness,4,7)
// Imgproc.line(srcMat, Point(rect.x.toDouble()-outLine, rect.y.toDouble()+rect.height/2), Point(rect.x.toDouble()+rect.width+outLine, rect.y.toDouble()+rect.height/2), Scalar(255.0, 0.0, 0.0), thickness)// Step 6: 绘制文本
// Imgproc.putText(srcMat, text, Point(100.0, 100.0), 0, 1.0, Scalar(255.0, 255.0, 255.0, 255.0), 2)Imgproc.putText(srcMat, text, Point(rect.x.toDouble()+20, rect.y.toDouble() - 30),Imgproc.FONT_HERSHEY_SIMPLEX, 0.8, Scalar(255.0, 0.0, 0.0), 2)}gray.release()blurred.release()hierarchy.release()return srcMat}})
在onCameraFrame 监听回调方法中获得mat对象
然后根据上面的代码以此进行灰度转换、高斯模糊、canny边缘查找、筛选轮廓
最后根据比例进行参照物和像素计算
最后绘制结果
其次是绘制中心圆,横竖虚线,这些步骤可去除,根据需要
添加虚线的方法如下
// 虚线绘制(以直线为例)fun drawDashedLine(img: Mat,pt1: Point,pt2: Point,color: Scalar,thickness: Int,dashLength: Int,gapLength: Int) {val dx = pt2.x - pt1.xval dy = pt2.y - pt1.yval distance = sqrt(dx * dx + dy * dy)val unitX = dx / distanceval unitY = dy / distancevar drawn = 0.0var draw = truewhile (drawn < distance) {if (draw) {val start = Point(pt1.x + unitX * drawn, pt1.y + unitY * drawn)val remain = min(dashLength.toDouble(), distance - drawn)val end = Point(start.x + unitX * remain, start.y + unitY * remain)Imgproc.line(img, start, end, color, thickness)drawn += dashLength.toDouble()} else {drawn += gapLength.toDouble()}draw = !draw}}
最后总结
步骤不多,精度与摄像头和物体距离有关,需要全部源码的可评论留言
本人原创转载请注明出处