2019-04-22 18:23:05 Lemon_jay 阅读数 436
  • 3D图形理论

    课程实现一套软3D,即不依赖任何第三方API(OpenGL,D3D,GDI)等。 课程从建立建立颜色缓冲区,绘制点,绘制线,绘制面,绘制图片,建立纹理,作图形的旋转,图像的混合,建立三维坐标系,实现模型矩阵,投影矩阵,观察矩阵,深度缓冲区等三维核心功能,实现基本的OpenGL核心绘制功能,让开发者从底层,全面的认识3D,为优化,开发出更好的游戏奠定基础。

    19320 人正在学习 去看看 张立铜

使用C++、opencv获取图像的Hu不变矩

        矩函数在图像分析中有着广泛的应用,如模式识别、目标分类、目标识别与方位估计、图像编码与重构等。一个从一幅数字图形中计算出来的矩集,通常描述了该图像形状的全局特征,并提供了大量的关于该图像不同类型的几何特性信息,比如大小、位置、方向及形状等。图像矩的这种特性描述能力被广泛地应用在各种图像处理、计算机视觉和机器人技术领域的目标识别与方位估计中。一阶矩与形状有关,二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩则是关于平均值的对称性的测量。由二阶矩和三阶矩可以导出一组共7个不变矩。而不变矩是图像的统计特性,满足平移、伸缩、旋转均不变的不变性,在图像识别领域得到了广泛的应用。

总结:Hu不变矩是图像的一组特征参数,主要用来描述形状,并具有平移、伸缩、旋转均不变的不变性,可用于图像分类、识别。

关于Hu不变矩的更详细原理、计算方法可以参考:https://www.cnblogs.com/ronny/p/3985810.htmlhttps://www.cnblogs.com/mikewolf2002/p/3427564.html


opencv中关于图像的矩的相关API:

Moments moments(InputArray array, bool binary Image=false)

moments0函数用于计算多边形和光栅形状的最高达三阶的所有矩。矩用来计算形状的重心、面积,主轴和其他形状特征,如7Hu不变量等。
第一个参数,InputArray类型的aray,输入参数,可以是光栅图像(单通道,8位或浮点的二维数组)或二维数组(1N或N1)
第二个参数,bool类型的 binaryImage,有默认值 false。若此参数取true则所有非零像素为1。此参数仅对于图像使用。
需要注意的是,此参数的返回值返回运行后的结果。

-----------------------------------------------------------------------

void HuMoments(const Moments& moments, double hu[7])

第一个参数,const Moments引用类型的moments是图像的矩,由上面的moments0函数求得。

第二个参数,输出的是double类型的数组hu[7],即图像的7个不变矩。


代码实现:

#include "stdafx.h"
#include <opencv2/opencv.hpp>  
#include <iostream>  
#include <math.h> 
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;

int main()
{
	system("color 02");

	//读取、显示图像
	Mat src_image = imread("D:\\4.jpg");
	if (!src_image.data)
	{
		cout << "src image load failed!" << endl;
		return -1;
	}
	namedWindow("原图", WINDOW_NORMAL);
	imshow("原图", src_image);

	/*此处高斯去燥有助于后面二值化处理的效果*/
	Mat blur_image;
	GaussianBlur(src_image, blur_image, Size(3, 3), 0, 0);
	imshow("GaussianBlur", blur_image);

	/*灰度变换与二值化*/
	Mat gray_image, binary_image;
	cvtColor(blur_image, gray_image, COLOR_BGR2GRAY);
	threshold(gray_image, binary_image, 20, 255, THRESH_BINARY);
	imshow("binary", binary_image);

	/*形态学闭操作*/
	Mat morph_image;
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	morphologyEx(binary_image, morph_image, MORPH_CLOSE, kernel, Point(-1, -1), 2);
	imshow("morphology", morph_image);

	/*查找外轮廓并计算周长、面积、圆形度*/
	vector< vector<Point> > contours;
	vector<Vec4i> hireachy;
	findContours(binary_image, contours, hireachy, CV_RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
	Mat result_image = Mat::zeros(src_image.size(), CV_8UC3);
	int l;
	double len, area;
	for (size_t t = 0; t < contours.size(); t++)
	{
		/*过滤掉小的干扰轮廓*/
		Rect rect = boundingRect(contours[t]);
		if (rect.width < src_image.cols / 2)
			continue;
		//if (rect.width >(src_image.cols - 20))
		l = t;
		drawContours(result_image, contours, static_cast<int>(t), Scalar(255, 255, 255), 1, 8, hireachy);
	}
	namedWindow("lunkuo", WINDOW_NORMAL);
	imshow("lunkuo", result_image);
	
	Moments mom;
	mom = moments(contours[l],false);
	double hu[7];
	HuMoments(mom,hu);

	for (int j = 0; j < 7; j++)
	{
		cout << hu[j] << "\t";
	}

	//写入文档
	/*FILE *fp = fopen("D:\\1.xlsx", "a");
	for (int k = 0; k < 7; k++)
	{
		fprintf(fp, "%.11lf\t", hu[k]);
	}
	fprintf(fp, "\n");
	fclose(fp);*/

	waitKey();
	return 0;
}

注:本代码适用于目标物体已大致分割出来或很容易分割出来,并且一幅图像中只有一个目标物体的情况,否则需要先进行图像分割操作。

源图像及代码中相关操作的图像:

计算得到的7个不变矩:

部分参考: 毛星云 《OpenCV3编程入门》 

2017-08-14 15:22:28 sinat_36264666 阅读数 2635
  • 3D图形理论

    课程实现一套软3D,即不依赖任何第三方API(OpenGL,D3D,GDI)等。 课程从建立建立颜色缓冲区,绘制点,绘制线,绘制面,绘制图片,建立纹理,作图形的旋转,图像的混合,建立三维坐标系,实现模型矩阵,投影矩阵,观察矩阵,深度缓冲区等三维核心功能,实现基本的OpenGL核心绘制功能,让开发者从底层,全面的认识3D,为优化,开发出更好的游戏奠定基础。

    19320 人正在学习 去看看 张立铜

博主力求用最简单的语言将此部分内容讲解清楚,但由于博主同样是刚刚接触OpenCV,或许表达上有些错误,还望读者能够指教探讨,大家共同进步。

博主机器配置为:VS2013+opencv2.4.13+Win-64bit。

 如果本人写的能给读者带来一点点的帮助,我就很开心了。


====================分割线====================


       上一节最后部分中,我们在处理两个图像尺寸不同时要融合过程中,提到了利用ROI,那么什么是ROI呢?

       在图像处理中,我们常常需对图像中某个重点区域进行分析处理,而不改变图像中的其他区域,因此这就要从整幅图像中提取感兴趣区域ROI(region of interest)。OpenCV中常常用到Rect来设置相应的ROI。下面我们通过一个demo来简单的了解是如何提取感兴趣区域ROI。

=====================分割线===================


代码演示

#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp> 
#include <iostream>
using namespace std;
using namespace cv;

//-----------【全局变量】------------------
Mat g_srcImage;	//源图像
Mat g_roiImage; //提取制定坐标区域ROI图像

//-----------【getROI()函数声明,获取ROI及显示】-------------------
void getROI(int xRoi, int yRoi, int widthRoi, int heightRoi);

int main()
{
	//【1】读取源图像并检查图像是否读取成功  
	g_srcImage = imread("D:\\OutPutResult\\ImageTest\\cat.jpg");
	if (!g_srcImage.data)
	{
		cout << "读取图像有误,请重新输入正确路径!\n";
		return -1;
	}
	imshow("源图像", g_srcImage);	//在窗口显示源图像
	cout << "源图像g_srcImage的行高:"<< g_srcImage.rows << ", 列宽:" << g_srcImage.cols << endl;

	//【2】利用Rect选择区域(150,170,350,200)
	int xRoi = 150;		//ROI左上角x坐标
	int yRoi = 170;		//ROI左上角y坐标
	int widthRoi = 350;	//ROI的宽
	int heightRoi = 200;//ROI的高
	getROI(xRoi, yRoi, widthRoi, heightRoi);//改变其中的四个变量的值,即可以调整ROI的位置及其尺寸

	//【3】输出信息并保持等待状态   
	cout << "ROI图像g_roiImage的行高:" << g_roiImage.rows << ", 列宽:" << g_roiImage.cols << endl;
	waitKey(0);
	return 0;
}

void getROI(int xRoi, int yRoi, int widthRoi, int heightRoi)
{
	//将感兴趣区域复制到输出图像roiImage上。 方式一:
	g_srcImage(Rect(xRoi, yRoi, widthRoi, heightRoi)).copyTo(g_roiImage);
	/*
		方式二:
		roiImage = g_srcImage(Rect(xRoi, yRoi, widthRoi, heightRoi));
	*/	
	imshow("提取的g_roiImage图像", g_roiImage);
}

=====================分割线==================

显示结果


========================分割线==========================

程序说明

我们可以在getROI()函数中,通过改变其中的变量,会得到不同的ROI。前两个变量就是ROI的左上角坐标,后两个变量就是ROI的尺寸。
在显示结果里我们可以看见,第一行就是源图像的尺寸大小;第二行就是得到的ROI尺寸大小,与我们设置时是一样的。
====================END===================


2017-07-09 21:21:12 lj402159806 阅读数 1098
  • 3D图形理论

    课程实现一套软3D,即不依赖任何第三方API(OpenGL,D3D,GDI)等。 课程从建立建立颜色缓冲区,绘制点,绘制线,绘制面,绘制图片,建立纹理,作图形的旋转,图像的混合,建立三维坐标系,实现模型矩阵,投影矩阵,观察矩阵,深度缓冲区等三维核心功能,实现基本的OpenGL核心绘制功能,让开发者从底层,全面的认识3D,为优化,开发出更好的游戏奠定基础。

    19320 人正在学习 去看看 张立铜

SurfaceView与View的区别

Android系统中使用View可以满足大部分绘图要求,View是通过刷新来重绘视图的,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的间隔时间为16ms。如果在16ms内View完成了所需要执行的所有操作,那么在用户的视觉上,就不会产生卡顿的感觉;而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,那么就会不断阻塞主线程,从而导致画面卡顿。很多时候,在自定义View的Log中经常会看到如下所示的警告。

“Skipped 47 frames! The application may be doing too much work on its main thread”

这些警告的产生,很多情况下就是因为在绘制过程中,处理逻辑太多造成的。

为了避免这一问题的产生,Android系统提供了SurfaceView组件来解决这个问题。SurfaceView与View的区别主要体现在以下几点。

  • View 主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新。
  • View 在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新。
  • View 在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制。

如果你的自定义View需要频繁刷新,或者刷新时数据处理量比较大,那么你就可以考虑使用SurfaceView来取代View了。

SurfaceView的使用

可以套用以下模板来实现:

//实现SurfaceHolder.Callback和Runnable接口
public class SurfaceViewTemplate extends SurfaceView
        implements SurfaceHolder.Callback, Runnable {

    // SurfaceHolder
    private SurfaceHolder mHolder;
    // 用于绘图的Canvas
    private Canvas mCanvas;
    // 子线程标志位
    private boolean mIsDrawing;

    public SurfaceViewTemplate(Context context) {
        super(context);
        initView();
    }

    public SurfaceViewTemplate(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    private void initView() {
        //初始化SurfaceHolder对象
        mHolder = getHolder();
        //注册SurfaceHolder回调
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
        //mHolder.setFormat(PixelFormat.OPAQUE);
    }

//SurfaceHolder.Callback需要实现的三个方法,分别对应创建改变和销毁
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder,
                               int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

//在子线程内进行绘制
    @Override
    public void run() {
      //mIsDrawing标志位控制子线程
        while (mIsDrawing) {
            draw();
        }
    }

    private void draw() {
        try {
          //获得当前Canvas绘图对象
          //获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此,之前的绘图操作都将保留,如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。
            mCanvas = mHolder.lockCanvas();
            // draw sth
        } catch (Exception e) {
        } finally {
          if(mCanvas != null)
            //将该方法放到finally代码块中,来保证每次都能将画布内容进行提交
            if (mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }
}

SurfaceView实例

下面来看两个实例:正弦曲线和绘图板来介绍下如何使用SurfaceView进行频繁刷新的绘图方法.

正弦曲线
在界面上不断绘制一个正弦曲线,类似示波器、心电图、股票走势图等。只需要不断修改横纵坐标的值,并让它们满足正弦函数即可。因此,使用一个Path对象来保存正弦函数上的坐标点,在子线程的while循环中,不断改变横纵坐标值,代码如下:

@Override
public void run() {
    while (mIsDrawing) {
        draw();
        x += 1;
        y = (int) (100* Math.sin(x * 2 * Math.PI / 180) + 400);
        mPath.lineTo(x, y);
    }
}

private void draw() {
    try {
        mCanvas = mHolder.lockCanvas();
        // SurfaceView背景
        mCanvas.drawColor(Color.WHITE);
        mCanvas.drawPath(mPath, mPaint);
    } catch (Exception e) {
    } finally {
        if (mCanvas != null)
            mHolder.unlockCanvasAndPost(mCanvas);
    }
}

这里写图片描述

绘图板

下面介绍下如何使用SurfaceView来实现一个简单的绘图板, 通过Path对象来记录手指滑动的路径来进行绘图。代码如下:

@Override
public void run() {
    long start = System.currentTimeMillis();
    while (mIsDrawing) {
        draw();
    }
    long end = System.currentTimeMillis();
    // 50 - 100
    if (end - start < 100) {
        try {
          //来线程中进行sleep操作来尽可能的节省系统资源
            Thread.sleep(100 - (end - start));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private void draw() {
    try {
        mCanvas = mHolder.lockCanvas();
        mCanvas.drawColor(Color.WHITE);
        mCanvas.drawPath(mPath, mPaint);
    } catch (Exception e) {
    } finally {
        if (mCanvas != null)
            mHolder.unlockCanvasAndPost(mCanvas);
    }
}

//在SurfaceView的onTouchEvent()中来记录Path路径
@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPath.moveTo(x, y);
            break;
        case MotionEvent.ACTION_MOVE:
            mPath.lineTo(x, y);
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return true;
}

这里写图片描述

代码下载

2020-01-14 08:41:27 qq_43069920 阅读数 43
  • 3D图形理论

    课程实现一套软3D,即不依赖任何第三方API(OpenGL,D3D,GDI)等。 课程从建立建立颜色缓冲区,绘制点,绘制线,绘制面,绘制图片,建立纹理,作图形的旋转,图像的混合,建立三维坐标系,实现模型矩阵,投影矩阵,观察矩阵,深度缓冲区等三维核心功能,实现基本的OpenGL核心绘制功能,让开发者从底层,全面的认识3D,为优化,开发出更好的游戏奠定基础。

    19320 人正在学习 去看看 张立铜

Python版本是Python3.7.3,OpenCV版本OpenCV3.4.1,开发环境为PyCharm

第16章 霍夫变换

霍夫变换是一种在图像中寻找直线、圆形以及其他简单形状的方法。霍夫变换采用类似于投票的方式来获取当前图像内的形状集合,该变换由Paul Hough(霍夫)于1962年首次提出。最初的霍夫变换只能用于检测直线,经过发展后,霍夫变换不仅能够识别直线,还能识别其他简单的图形结构,常见的有圆、椭圆等。
本章主要介绍霍夫直线变换和霍夫圆变换。霍夫直线变换用来在图像内寻找直线,霍夫圆变换用来在图像内寻找圆。在OpenCV中,前者可以用函数cv2.HoughLines()和函数cv2.HoughLinesP()实现,后者可以用函数cv2.HoughCircles()实现。
OpenCV提供了函数cv2.HoughLines()和函数cv2.HoughLinesP()用来实现霍夫直线变换。

16.1 霍夫变换原理

为了方便说明问题,先以我们熟悉的笛卡儿坐标系(与笛卡儿空间对应)为例来说明霍夫变换的基本原理。与笛卡儿坐标系对应,我们构造一个霍夫坐标系(对应于霍夫空间)。在霍夫坐标系中,横坐标采用笛卡儿坐标系中直线的斜率k,纵坐标使用笛卡儿坐标系中直线的截距b。
首先,我们观察笛卡儿空间中的一条直线在霍夫空间内的映射情况。例如,在下图中,左图是笛卡儿x-y坐标系(笛卡儿空间),右图是霍夫k-b坐标系(霍夫空间)。在笛卡儿空间中,存在着一条直线y=k0x+b0,该直线的截距k0是已知的常量,截距b0也是已知的常量。将该直线映射到霍夫空间内,找到已知的点(k0, b0),即完成映射。

在这里插入图片描述

从上述分析中可知,笛卡儿空间内的一条直线,其斜率为k,截距为b,映射到霍夫空间内成为一个点(k, b)。或者,可以这样理解,霍夫空间内的一个点(k0, b0),映射到笛卡儿空间,就是一条直线y=k0x+b0。
这里,我们用“映射”这个词表达不同的空间(坐标系)之间的对应关系,也可以表述为“确定”。例如,上述关系可以表述为:
● 笛卡儿空间内的一条直线确定了霍夫空间内的一个点。
● 霍夫空间内的一个点确定了笛卡儿空间内的一条直线。
接下来,观察笛卡儿空间中的一个点在霍夫空间内的映射情况。如图16-2所示,在笛卡儿空间内存在一个点(x0, y0),通过该点的直线可以表示为y0=kx0+b。其中,(x0, y0)是已知的常量,(k, b)是变量。
对于表达式y0=kx0+b,通过算术运算的左右移项,可以表示为b=-x0k+y0。将点(x0, y0)映射到霍夫空间时,可以认为对应的直线斜率为-x0,截距为y0,即b=-x0k+y0,如下图中右图的直线所示。

在这里插入图片描述

从上述分析可知:
● 笛卡儿空间内的点(x0, y0)映射到霍夫空间,就是直线b=-x0k+y0。
● 霍夫空间内的直线b=-x0k+y0映射到笛卡儿空间,就是点(x0, y0)。
下面我们看看笛卡儿空间中的两个点映射到霍夫空间的情况。例如,在下图中,左图的笛卡儿空间中存在着两个点(x0, y0)、(x1, y1),分析这两个点映射到霍夫空间的情况。

在这里插入图片描述

为了方便理解,我们从不同的角度分析笛卡儿空间中这两个点到霍夫空间的映射情况。
● 角度1:笛卡儿空间的一个点会映射为霍夫空间的一条线。
在笛卡儿空间内,存在着任意两个点(x0, y0)、(x1, y1)。在霍夫空间中,这两个点对应着两条不同的直线。当然,通过分析可知,一条直线是b=-x0k+y0,另外一条直线是b=-x1k+y1。
● 角度2:笛卡儿空间的一条线会映射为霍夫空间的一个点
在笛卡儿空间内,存在着任意两个点(x0, y0)、(x1, y1)。这两个点一定能够用一条直线连接,将连接它们的直线标记为y=k1x+b1,则该直线的截距和斜率是(k1,b1)。也就是说,该直线在霍夫空间内映射为点(k1,b1)。
从上述分析可知:
● 笛卡儿空间内的两个点会映射为霍夫空间内两条相交于(k1,b1)的直线。
● 这两个点对应的直线会映射为霍夫空间内的点(k1,b1)。
换句话说,角度1决定了线条的数量,角度2决定了两条线相交的点。
这说明,如果在笛卡儿空间内有两个点A、B,它们能够连成一条直线y=k1x+b1,那么在霍夫空间中的点(k1,b1)上会有两条直线,分别对应着笛卡儿空间内的两个点A、B。
下面我们看看笛卡儿空间中的三个点映射到霍夫空间的情况。在图16-4中,左图是笛卡儿空间,其中存在(0, 1)、(1, 2)、(2, 3)三个点。

在这里插入图片描述

下面从不同的角度分析笛卡儿空间中这三个点映射到霍夫空间的情况。
● 角度1:笛卡儿空间内的一个点会映射为霍夫空间的一条线。
例如,笛卡儿空间中的(0, 1)、(1, 2)、(2, 3)三个点映射到霍夫空间时,每个点对应着一条直线,对应关系如下表所示。
在这里插入图片描述

根据对应关系可知:
● 笛卡儿空间内的点(0, 1),对应着霍夫空间内的直线b=1。
● 笛卡儿空间内的点(1, 2),对应着霍夫空间内的直线b=-k+2。
● 笛卡儿空间内的点(2, 3),对应着霍夫空间内的直线b=-2k+3。
从上述分析可知,笛卡儿空间内的三个点映射为霍夫空间内的三条直线。
● 角度2:笛卡儿空间内的一条线会映射为霍夫空间的一个点。
例如,笛卡儿空间中的(0, 1)、(1, 2)、(2, 3)三个点对应着直线y=x+1,斜率k为1,截距b为1。该直线y=x+1映射到霍夫空间内的点(1, 1)。
从上述角度1和角度2的分析可知:
● 笛卡儿空间中的(0, 1)、(1, 2)、(2, 3)三个点会映射为霍夫空间内相交于点(1, 1)的三条直线。
● 笛卡儿空间中的(0, 1)、(1, 2)、(2, 3)三个点所连成(确定)的直线映射为霍夫空间内的点(1, 1)。
这说明,如果在笛卡儿空间内有三个点,并且它们能够连成一条y=k1x+b1的直线,那么在霍夫空间中,对应的点(k1, b1)上会有三条直线,分别对应着笛卡儿空间内的三个点。
到此,我们已经发现,如果在笛卡儿空间内,有N个点能够连成一条直线y=k1x+b1,那么在霍夫空间内就会有N条直线穿过对应的点(k1, b1)。或者反过来说,如果在霍夫空间中,有越多的直线穿过点(k1, b1),就说明在笛卡儿空间内有越多的点位于斜率为k1,截距为b1的直线y=k1x+b1上。
现在,我们看一个在笛卡儿空间内更多个点映射到霍夫空间的例子,也验证一下上述观点。在下图中,左图所示的是笛卡儿空间,其中有6个点,下面从不同的角度看下这6个点在右图霍夫空间的映射情况。

在这里插入图片描述

● 角度1:笛卡儿空间的一点会映射为霍夫空间的一条线。
笛卡儿空间中的6个点:(0, 1)、(1, 2)、(2, 3)、(3, 4)、(3, 2)、(1, 4),映射到霍夫空间时,每个点对应着一条直线,对应关系如下表所示。
在这里插入图片描述

根据对应关系可知:
● 笛卡儿空间内的点(0, 1),对应着霍夫空间内的直线b=1。
● 笛卡儿空间内的点(1, 2),对应着霍夫空间内的直线b=-k+2。
● 笛卡儿空间内的点(2, 3),对应着霍夫空间内的直线b=-2k+3。
● 笛卡儿空间内的点(3, 4),对应着霍夫空间内的直线b=-3
k+4。
● 笛卡儿空间内的点(3, 2),对应着霍夫空间内的直线b=-3k+2。
● 笛卡儿空间内的点(1, 4),对应着霍夫空间内的直线b=-1
k+4。
从上述分析可知,笛卡儿空间内的6个点映射为霍夫空间内的6条直线。
● 角度2:笛卡儿空间的一条线会映射为霍夫空间的一个点。
这里为了观察方便,将笛卡儿空间内连接了较多点的线绘制出来:连接点(0, 1)、(1, 2)、(2, 3)、(3, 4)的线LineA,连接点(2, 3)、(3, 2)、(1, 4)的线LineB,连接点(0,1)、(3,2)的线LineC,如下图的左图所示。

在这里插入图片描述

需要注意,在笛卡儿空间内,各个点之间存在多条直线。例如在点(1, 2)、(3, 2)之间,点(3, 2)、(3, 4)之间,点(1, 4)、(3, 4)之间都存在着直线,这里做了简化,没有将上述直线都绘制出来。
下面分析笛卡儿空间内的三条直线LineA、LineB、LineC在霍夫空间内的映射情况。
● 直线LineA经过了4个点,表达式为y=1x+1,斜率k为1,截距b为1,在霍夫空间内对应于点A(1, 1)。
● 直线LineB经过了3个点,表达式为y=-1
x+5,斜率k为-1,截距b为5,在霍夫空间内对应于点B(-1, 5)。
● 直线LineC经过了2个点,表达式为y=-1/3*x+1,斜率k为-1/3,截距b为1,在霍夫空间内对应于点C(-1/3, 1)。
在上图中可以看到,右图所示的霍夫空间内点A有4条直线穿过,点B有3条直线穿过,点C有2条直线穿过。
分析上述关系:
● 霍夫空间内有4条直线穿过点A。点A确定了笛卡儿空间内的一条直线,同时该直线穿过4个点,即霍夫空间内的点A确定了笛卡儿空间内的LineA,该直线上包含(0, 1)、(1, 2)、(2, 3)、(3, 4)共4个点。
● 霍夫空间内有3条直线穿过点B。点B确定了笛卡儿空间内的一条直线,同时该直线穿过3个点,即霍夫空间内的点B确定了笛卡儿空间内的LineB,该直线上包含(2, 3)、(3, 2)、(1, 4)共3个点。
● 霍夫空间内有2条直线穿过点C。点C确定了笛卡儿空间内的一条直线,同时该直线穿过2个点,即霍夫空间内的点C确定了笛卡儿空间内的LineC,该直线上包含(0, 1)、(2, 3)共2个点。
综上所述,在霍夫空间内,经过一个点的直线越多,说明其在笛卡儿空间内映射的直线,是由越多的点所构成(穿过)的。我们知道,两个点就能构成一条直线。但是,如果有一个点是因为计算错误而产生的,那么它和另外一个点,也会构成一条直线,此时就会凭空构造出一条实际上并不存在的直线。这种情况是要极力避免的。因此,在计算中,我们希望用更多的点构造一条直线,以提高直线的可靠性。也就是说,如果一条直线是由越多点所构成的,那么它实际存在的可能性就越大,它的可靠性也就越高。因此,霍夫变换选择直线的基本思路是:选择有尽可能多直线交汇的点。
上面都是以我们熟悉的笛卡儿空间为例说明的。在笛卡儿空间中,可能存在诸如x=x0的垂线LineA的形式,如下图所示。

在这里插入图片描述

此时,斜率k为无穷大,截距b无法取值。因此,图中的垂线LineA无法映射到霍夫空间内。为了解决上述问题,可以考虑将笛卡儿坐标系映射到极坐标系上,如下图所示。

在这里插入图片描述

在笛卡儿坐标系内使用的是斜率k和截距b,即用(k, b)表示一条直线。在极坐标系内,采用极径r(有时也用ρ表示)和极角θ来表示,即(r, θ)来表示。极坐标系中的直线可以表示为:
r=xcosθ+ysinθ
例如,图中的直线LineA,可以使用极坐标的极径r和极角θ来表示。其中,r是直线LineA与图像原点O之间的距离,参数θ是直线LineA的垂线LineB与x轴的角度。在这种表示方法中,图像中的直线有一个(0~π)的角θ,而r的最大值是图像对角线的长度。用这种表示方法,可以很方便地表示上图中的3个点所构成的直线。
与笛卡儿空间和霍夫空间的映射关系类似:
● 极坐标系内的一个点映射为霍夫坐标系(霍夫空间)内的一条线(曲线)。
● 极坐标系内的一条线映射为霍夫坐标系内的一个点。
一般来说,在极坐标系内的一条直线能够通过在霍夫坐标系内相交于一点的线的数量来评估。在霍夫坐标系内,经过一个点的线越多,说明其映射在极坐标系内的直线,是由越多的点所构成(穿过)的。因此,霍夫变换选择直线的基本思路是:选择由尽可能多条线汇成的点。
通常情况下,设置一个阈值,当霍夫坐标系内交于某点的曲线达到了阈值,就认为在对应的极坐标系内存在(检测到)一条直线。
上述内容是霍夫变换的原理,即使完全不理解上述原理,也不影响我们使用OpenCV提供的霍夫变换函数来进行霍夫变换。OpenCV本身是一个黑盒子,它给我们提供了接口(参数、返回值),我们只需要掌握接口的正确使用方法,就可以正确地处理图像问题,无须掌握其内部工作原理。
在某种情况下,OpenCV库和Photoshop等图像处理软件是类似的,只要掌握了它们的使用方法,就能够得到正确的处理结果。在进行图像处理时,并不需要我们关注其实现原理等技术细节。但是,如果我们进一步了解其工作原理,对我们的工作也是有大有裨益的。

2014-12-16 20:34:48 zeng622peng 阅读数 1850
  • 3D图形理论

    课程实现一套软3D,即不依赖任何第三方API(OpenGL,D3D,GDI)等。 课程从建立建立颜色缓冲区,绘制点,绘制线,绘制面,绘制图片,建立纹理,作图形的旋转,图像的混合,建立三维坐标系,实现模型矩阵,投影矩阵,观察矩阵,深度缓冲区等三维核心功能,实现基本的OpenGL核心绘制功能,让开发者从底层,全面的认识3D,为优化,开发出更好的游戏奠定基础。

    19320 人正在学习 去看看 张立铜
一、如何获取 res 中的资源
数据包package:android.content.res
主要类:Resources
其主要接口按照功能,划分为以下三部分:

getXXXX()

例如:

int getColor(int id)

Drawable getDrawable(int id)

String getString(int id)  直接获取res中存放的资源

InputStream openRawResource(int id)  获取资源的数据流,读取资源数据

void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)  从XML文件中获取数据

Resource为每种资源提供了相应的接口来获取这种资源,除了可以直接获取资源外,还额外提供了以数据流的方式获取资源,这在以后的应用程序开发中会经常使用,那么如何获取Resources了,如下:Resources r = this.getContext().getResources();

 

二、如何获取资源中的画图对象
数据包package:android.graphics.drawable
主要类:Drawable
Drawable是个virtual class,具体如何画图,需要具体分析Drawable的子类,例如:BitmapDrawable
其主要接口如下:

BitmapDrawable()

BitmapDrawable(Bitmap bitmap)

BitmapDrawable(String filepath)

BitmapDrawable(InputStream is)

void draw(Canvas canvas)

final Bitmap getBitmap()

final Paint getPaint()

Drawable是个抽象类,在BitmapDrawable中我们就看到位图的具体操作,在仔细看下BitmapDrawable的构造函数,我们就会发现与Resource中的openRawResource()接口是相对应的,就可以通过以下方法来获取位图:
Resources r = this.getContext().getResources();
Inputstream is = r.openRawResource(R.drawable.my_background_image);
BitmapDrawable  bmpDraw = new BitmapDrawable(is);
Bitmap bmp = bmpDraw.getBitmap();


Paint

数据包package:android.graphics

Android SDK中的简介:The Paint class holds the style and color information about how to draw geometries, text and bitmaps. 主要就是定义:画刷的样式,画笔的大小/颜色等。

Typeface
数据包 package:android.graphics
Android SDK中的简介:The Typeface class specifies the typeface and intrinsic style of a font. 主要就是定义:字体。

核心类显示资源
数据包package:android.graphics
主要类:Canvas
Android SDK中的简介:The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
按照结构的功能,将主要接口分为以下3部分:

boolean clipXXXX() Region区域操作:DIFFERENCE INTERSECT REPLACE REVERSE_DIFFERENCE UNION XOR

void drawXXXX()画图函数

void rotate()  void scale()  void skew() void translate() 画布操作函数

Region在这里需要特殊说明下:Region就是一个区域,也就是画布(Canvas)中的有效区域,在无效区域上draw,对画布没有任何改变。

Drawable类
Drawable是一个通用的抽象类,它的目的是告诉你什么东西是可以画的。你会发现基于Drawable类扩展出各种绘图的类,见下面的表格,当然你可以继承它来创建你自己的绘图类.

 

有三种方法可以定义和实例化一个Drawable:保存一个图片到你工程资源中,使用XML文件来描述Drawable属性或者用一个正常的类去构造。下面我们将讨论两种技术(对一个有开发经验的开发者来说构造并不是最新的技术)。

从资源图像文件中创建
一个比较简单的方法是添加一个图片到你的程序中,然后通过资源文件引用这个文件,支持的文件类型有PNG(首选的) JPG(可接受的)GIF(不建议),显然这种对于显示应用程序的图标跟来说是首选的方法,也可以用来显示LOGO,其余的图片可以用在例如游戏中。

把一个图片资源,添加你的文件到你工程中res/drawable/目录中去,从这里,你就可以引用它到你的代码或你的XML布局中,也就是说,引用它也可以用资源编号,比如你选择一个文件只要去掉后缀就可以了(例如:my_image.png 引用它是就是my_image)。

注意:SDK指出,为了缩小图片的存储空间,在Build的时候又可能对图片进行压缩,如果不想被压缩,可以将图片放在res/raw/目录中。

SDK给出的例子:

LinearLayout mLinearLayout;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Create a LinearLayout in which to add the ImageView
    mLinearLayout = new LinearLayout(this);

    // Instantiate an ImageView and define its properties
    ImageView i = new ImageView(this);
    i.setImageResource(R.drawable.my_image);
    i.setAdjustViewBounds(true); // set the ImageView bounds to match the Drawable's dimensions
    i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

    // Add the ImageView to the layout and set the layout as the content view
    mLinearLayout.addView(i);
    setContentView(mLinearLayout);
}获取Drawable对象:

Resources res = mContext.getResources();
Drawable myImage = res.getDrawable(R.drawable.my_image); 

注意:保持每个资源类型的一至,可以保证你项目状态的一致性,就不用担心有许多不同类型的对象来实例化它。例如:如果使用相同的图像资源来实例化两个Drawable对象。然后修改一个Drawables的属性(例如alpha),然后不幸得是这个效果也会出现在另一个对象上去。所以当处理同一个资源的多个实例对象时,不是直接转换为Drawable,而是应该执行tween animation

如何添加资源到ImageView:
<ImageView  
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:tint="#55ff0000"
  android:src="@drawable/my_image"/> 从XML文件中创建
到如今,你应该比较熟悉按Android的原则去开发一个用户接口,因此,你也应该理解了定义一个XML文件对于对象的作用与灵活的重要性。这个理念无数次用于Drawables.

如果你想创建一个Drawable对象,而这个对象并不依赖于变量或用户的交换,把它定义到XML中去应该是一个不错的方法。即使你期望在你的应用程序中改变其属性来增加用户体验。你应该考虑把对象放入XML中,因为你可以随时修改其属性。

当你在你的XML中定义了一个Drawable,保存这个XML文件到你工程目录下res/drawable目录中,然后通过调用Resource.getDrawable()来检索并实例化,传递给它XML文件中的资源ID号。任何Drawable的子类都支持inflate这个方法,这个方法会通过XML来实例化你的程序。任何Drawable都支持XML的扩展来利用特殊的XML属性来帮助定义对象的属性,可以查看任何Drawable子类文档来看如何定义XML文件。

如下定义了一个TransitionDrawable:An extension of LayerDrawables that is intended to cross-fade between the first and second layer. It can be defined in an XML file with the <transition> element. Each Drawable in the transition is defined in a nested <item>. 有关TransitionDrawable的详细信息查看http://androidappdocs.appspot.com/reference/android/graphics/drawable/TransitionDrawable.html

将其定义在res/drawable/expand_collapse.xml:

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:drawable="@drawable/pic1"/>
   <item android:drawable="@drawable/pic2"/>
</transition>
下面实例化并处理:

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        Resources res=getResources();
        TransitionDrawable trans=(TransitionDrawable )res.getDrawable(R.drawable.expand_collapse);
        ImageView image = (ImageView)findViewById(R.id.ImageView01);
        image.setImageDrawable(trans);
        trans.startTransition(3000);
    }
}ShapeDrawable
当你想去画一些动态的二维图片,一个ShapeDrawable对象可能会对你有很大的帮助。通过ShapeDrawable,你可以通过编程画出任何你想到的图像与样式。

ShapeDrawable继承了Drawable, 所以你可以调用Drawable里有的函数,比如视图的背景,通过setBackgroundDrawable()设置。当然,你可以在自定义的视图布局中画你的图形,因为ShapeDrawable有自己的draw()方法。你可以在View.OnDraw()方法期间创建一个视图的子类去画ShapeDrawable。

ShapeDrawable类(像很多其他Drawable类型在android.graphics.drawable包)允许你定义drawable公共方法的各种属性。有些属性你可以需要调整,包括透明度,颜色过滤,不透明度,颜色。

 

NinePatchDrawable

NinePatchDrawable 绘画的是一个可以伸缩的位图图像,Android会自动调整大小来容纳显示的内容。一个例子就是NinePatch为背景,使用标准的Android按钮,按钮必须伸缩来容纳长度变化的字符

NinePatchDrawable是一个标准的PNG图像,它包括额外的1个像素的边界,你必须保存它后缀为.9.png,并且保持到工程的res/drawable目录中。

这个边界是用来确定图像的可伸缩和静态区域。你可以在左边和上边的线上画一个或多个黑色的1个像素指出可伸缩的部分(你可以需要很多可伸缩部分),它的相对位置在可伸缩部分相同,所以大的部分总是很大的。

你还有可以在图像的右边和下边画一条可选的drawable区域(有效的,内边距线)。如果你的视图对象设置NinePath为背景然后指定特殊的视图字体,它将自行伸缩使所有的文本来适应根据右线与底部线设计好的区域(如果有的话),当然内边距线不包括其中,Android可以使用左边的线与上面的线来定义一个drawable区域。

我们来澄清一下这两条不同的线,左边跟顶部的线来定义哪些图像的像素允许在伸缩时被复制。底部与右边的线用来定义一个相对位置内的图像,视图的内容就放入其中。

没有更多推荐了,返回首页