图像处理系列教程之三:用指针操作图像

大部分公司进行图像处理时所采用的开发工具之首选肯定是VC,这里主要是考虑效率问题。VC中通过指针直接可以访问内存,而VB为了安全起见把这些操作完全封装在底层。这种以牺牲程序效能来换取开发效率的做法对于图像来说不合适,因为图像的计算量通常都是很大的。因此,要想利用VB高效的图像处理,除了要能快速获取图像数据外,能否直接操作内存数据成为了关键。

在C和C++里一个数组指针和数组第一个元素的指针是一回事,他们在内存中是一个接着一个线性存放的,我们通过第一个元素就能访问随后的元素,这样的数组我们称之为“真数组”,但是他不安全。因为我们无法从这种真数组的指针上得知数组的维数、元素个数等非常重要的信息,所以也无法控制对这种数组的访问。而在VB中,所采用的数组在COM里叫做SafeArray,他能够解决上述“真数组”的安全问题。那么SafeArray结构如下:

 Private Type SAFEARRAY
       cDims As Integer         '这个数组有几维?
       fFeatures As Integer     '这个数组有什么特性?
       cbElements As Long       '数组的每个元素有多大?
       cLocks As Long           '这个数组被锁定过几次?
       pvData As Long           '这个数组里的数据放在什么地方?
       'rgsabound() As SFArrayBOUND
 End Type

Private Type SAFEARRAYBOUND
        cElements As Long      '这一维有多少个元素?
        lLbound As Long        '它的索引从几开始?
End Type

这里我们把焦点放在SAFEARRAY结构的pvData成员上,正是这个成员只出了我们定义的数组的真数据第一个元素放在内存的那个位置。这是一个很有用的数据。

在VC中操作图像数据,一般都是直接移动指针到需要的地方,然后取值,我们也希望能够在VB中有类似的操作。

当我们把图像加载到内存后,我们是可以获得图像数据在内存中的首地址的,如果我们能够把VB中某数组对应的SafeArray之pvData指向到这个首地址,那么我们操作数组也就相当于操作了对应的内存数据。这种方法的实现很简单,具体的代码可以参考:

http://blog.csdn.net/zyl910/archive/2006/05/24/752111.aspx

但是,上述这种结构在操作上似乎还是很VB化,和VC中的图像处理的过程不太类似。

但是如果使用如下的函数,你会发现整个操作的过程意义很明朗。

Public Sub MakePoint(ByVal DataArrPtr As Long,ByVal pDataArrPtr As Long,ByRef OldArrPtr As Long,ByRef OldpArrPtr As Long)

    Dim Temp As Long,TempPtr As Long
    CopyMemory Temp,ByVal DataArrPtr,4        '得到DataArrPtr的SAFEARRAY结构的地址
   Temp = Temp + 12                            '这个指针偏移12个字节后就是pvData指针
   CopyMemory TempPtr,ByVal pDataArrPtr,4    '得到pDataArrPtr的SAFEARRAY结构的地址
    TempPtr = TempPtr + 12                      '这个指针偏移12个字节后就是pvData指针
    CopyMemory OldpArrPtr,ByVal TempPtr,4     '保存旧地址
    CopyMemory ByVal TempPtr,Temp,4           '使pDataArrPtr指向DataArrPtr的SAFEARRAY结构的pvData指针
    CopyMemory OldArrPtr,ByVal Temp,4         '保存旧地址
End Sub

函数的调用形式位:

    MakePoint VarPtrArray(DataArr),VarPtrArray(pDataArr),OldArrPtr,OldpArrPtr
    pDataArr(0)=m_Pointer

其中各变量的定义如下:

    Dim DataArr(0 To 3) As Byte,pDataArr(0 To 0)       As Long
    Dim OldArrPtr       As Long,OldpArrPtr             As Long

那么我们稍微解释下上面的函数。

乍一看,这个代码完全和SAFEARRAY结构没有关系啊。实际上,这个过程到处充斥着SAFEARRAY结构。

首先,我们看下VarPtrArray这个API函数,他的声明如下:

    Declare Function VarPtrArray Lib "msvbvm60.dll"  Alias "VarPtr" (Var() As Any) As Long

从别名Alias "VarPtr"上看,他就是VB中隐藏的与针相关的函数VarPtr,只过参数声明上用的是VB数组,这时它返回来的就是一个指向数组SafeArray结构的指针的指针。因为VarPtr会将传给它的参数的地址返回,而用ByRef传给它一个VB数组,实际上传递的是一个SafeArray结构的指针,这时VarPtrArray将返回这个指针的指针。

简单的说,调用了VarPtrArray(DataArr),我们就知道了DataArr这个数组的所对应的SAFEARRAY结构在内存的地址。

MakePoint 中的第一句:CopyMemory Temp,4的意思是把DataArr这个数组的所对应的SAFEARRAY结构在内存的地址临时性的保存到Temp变量中。

TempPtr = TempPtr + 12 ,这里的12怎么来的呢,我们计算下SAFEARRAY结构中pvData相对于结构的偏移地址,Integer+ Integer+ Long+ Long=12,你大概也知道什么意思了吧。

下面两行的代码意义雷同。

   CopyMemory OldpArrPtr,4这行代码的意思是把pDataArr的真数组的内存起始地址保存起来,以便以后恢复,注意这里用的ByVal。

CopyMemory ByVal TempPtr,4意思是把DataArr的真数组的内存起始值赋值给pDataArr的真数组的内存起始值。

CopyMemory OldArrPtr,4这行代码的意思是把DataArr的真数组的内存起始地址保存起来,以便以后恢复,注意这里用的ByVal。

经过了以上操作,如果我们改变了pDataArr(0)的值,相当于是改变了DataArr这个数组的首元素在内存中的地址,因此和VC中的操作基本类似了。

以下我们给出一个简单的反色代码来说明如何使用这种方法。

假设我们如果已经创建了一副24位的内存图像,则代码如下

    Dim i               As Long,j                      As Long
    Dim DataArr(0 To 3) As Byte,OldpArrPtr             As Long
    Dim LineAddBytes    As Long
    LineAddBytes = m_Stride - m_Width*3
    MakePoint VarPtrArray(DataArr),OldpArrPtr
    pDataArr(0)=m_Pointer  ‘m_Pointer为图像在内存中的首地址
    For j = 0 To m_Hegiht - 1
        For i = 0 To m_Width - 1
            DataArr(2) = 255 - DataArr(2)             ‘简单的反色算法
            DataArr(1) = 255 - DataArr(1)
            DataArr(0) = 255 - DataArr(0)
            pDataArr(0) = pDataArr(0) + 3
        Next
        pDataArr(0) = pDataArr(0) + LineAddBytes
    Next
FreePoint VarPtrArray(DataArr),OldpArrPtr

代码行LineAddBytes = m_Stride - m_Width*3的存在是因为24位图像的一个扫描行宽度在很多情况下并不是图像的宽度的3倍,我们需要跳过无用的数据。

pDataArr(0)=m_Pointer则把我们的DataArr的第一个元素DataArr(0)赋予了访问m_Pointe处字节的能力,一般这里是图像的Green分量。对应的DataArr(1)则为Blue分量。

pDataArr(0) = pDataArr(0) + 3这里的意思是我们要把指针偏移3个字节,以便访问下一个像素。

pDataArr(0) = pDataArr(0) + LineAddBytes是为了扫描行对次的。

如果以上代码你看不懂,那简单的说,如果你要访问图像第m行,n列的元素,则可以如下调用:

pDataArr(0) = m_Pointer+m_Stride*(m-1)+(n-1)*3 (24位乘3,32位乘4,8位乘1,8位一下要做特殊处理),这相当于VC中的移动指针。

然后就可以访问DataArr的各元素值得到对应的R/G/B了。

上述FreePoint的过程如下,主要是用来恢复VB的数组的。应该保证每次调用MakePoint函数都有对应调用FreePoint函数。否则有可能会出现IDE崩溃。

Public Sub FreePoint(ByVal DataArrPtr As Long,ByVal OldArrPtr As Long,ByVal OldpArrPtr As Long)
    Dim TempPtr As Long
    CopyMemory TempPtr,4         '得到DataArrPtr的SAFEARRAY结构的地址
    CopyMemory ByVal (TempPtr + 12),4   '恢复旧地址
      CopyMemory TempPtr,4        '得到pDataArrPtr的SAFEARRAY结构的地址
    CopyMemory ByVal (TempPtr + 12),OldpArrPtr,4  '恢复旧地址
End Sub

从执行效率上来讲,这种指针功能比VC中的真指针是要稍微慢一些,但是绝对不是人们观念的中那么大的差距,应该能在90%以上。

要注意的是,如果一不小心,指针所指向的地址超出了图像在内存中数据分布,则会弹出一个错误,该错误会导致VB崩溃,因此,在调试代码前一定要记得保存代码。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Format[$] ( expr [ , fmt ] ) format 返回变体型 format$ 强制返回为文本 -------------------------------- 数字类型的格式化 --------------------------------     固定格式参数:     General Number 普通数字,如可以用来去掉千位分隔号     format$("100,1
VB6或者ASP 格式化时间为 MM/dd/yyyy 格式,竟然没有好的办法, Format 或者FormatDateTime 竟然结果和系统设置的区域语言的日期和时间格式相关。意思是尽管你用诸如 Format(Now, "MM/dd/yyyy"),如果系统的设置格式区域语言的日期和时间格式分隔符是"-",那他还会显示为 MM-dd-yyyy     只有拼凑: <%response.write
在项目中添加如下代码:新建窗口来显示异常信息。 Namespace My ‘全局错误处理,新的解决方案直接添加本ApplicationEvents.vb 到工程即可 ‘添加后还需要一个From用来显示错误。如果到这步还不会则需要先打好基础啦 ‘======================================================== ‘以下事件
转了这一篇文章,原来一直想用C#做k3的插件开发,vb没有C#用的爽呀,这篇文章写与2011年,看来我以前没有认真去找这个方法呀。 https://blog.csdn.net/chzjxgd/article/details/6176325 金蝶K3 BOS的插件官方是用VB6编写的,如果  能用.Net下的语言工具开发BOS插件是一件很愉快的事情,其中缘由不言而喻,而本文则是个人首创,实现在了用V
Sub 分列() ‘以空格为分隔符,连续空格只算1个。对所选中的单元格进行处理 Dim m As Range, tmpStr As String, s As String Dim x As Integer, y As Integer, subStr As String If MsgBox("确定要分列处理吗?请确定分列的数据会覆盖它后面的单元格!", _
  窗体代码 1 Private Sub Text1_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) 2 Dim path As String, hash As String 3 For Each fil
  Imports MySql.Data.MySqlClient Public Class Form1 ‘ GLOBAL DECLARATIONS Dim conString As String = "Server=localhost;Database=net2;Uid=root;Pwd=123456;" Dim con As New MySqlConnection
‘導入命名空間 Imports ADODB Imports Microsoft.Office.Interop   Private Sub A1() Dim Sql As String Dim Cnn As New ADODB.Connection Dim Rs As New ADODB.Recordset Dim S As String   S = "Provider=OraOLEDB.Oracl
Imports System.IO Imports System.Threading Imports System.Diagnostics Public Class Form1 Dim A(254) As String    Function ping(ByVal IP As Integer) As String Dim IPAddress As String IPAddress = "10.0.
VB运行EXE程序,并等待其运行结束 参考:https://blog.csdn.net/useway/article/details/5494084 Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long Pr
今天碰到一个问题,登陆的时候,如果不需要验证手机号为空,则不去验证手机号 因为登陆的时候所有的验证信息都存放在一个数组里 Dim CheckUserInfo() As String ={UserBirthday, SecEmail, UserMob, UserSex, RealNameFirst, RealName, CheckCardID, CheckCardType, Contactemail
在VB6.0中,数据访问接口有三种: 1、ActiveX数据对象(ADO) 2、远程数据对象(RDO) 3、数据访问对象(DAO) 1.使用ADO(ActiveX Data Objec,ActiveX数据对象)连接SQL Server 1)使用ADO控件连接 使用ADO控件的ConnectionString属性就可以连接SQL Server,该属性包含一个由分号分隔的argument=value语
注:大家如果没有VB6.0的安装文件,可自行百度一下下载,一般文件大小在200M左右的均为完整版的软件,可以使用。   特别提示:安装此软件的时候最好退出360杀毒软件(包括360安全卫士,电脑管家等,如果电脑上有这些软件的话),因为现如今的360杀毒软件直接会对VB6.0软件误报,这样的话就可能会在安装过程中被误报阻止而导致安装失败,或者是安装后缺乏很多必须的组件(其它的杀毒软件或安全卫士之类的
Private Sub Form_Load() Call conndb End Sub Private Function conndb() Dim cn As New ADODB.Connection Dim rs As New ADODB.Recordset Dim strCn, sql As String Dim db_host As String Dim db_user As String
  PPSM06S70:  Add  moddate  EDITSPRINTJOB:  MAX(TO_CHAR(ETRN.MODDATE, ‘yyyy/mm/dd/HH24:MI AM‘)) ACTUAL_SHIPDATE   4.Test Scenario (1) :Query SQL Test DN:8016578337 SELECT CTRN.TKCTID TRUCK_ID,        
  沒有出現CrystalReportViewer時,須安裝CRforVS_13_0. 新增1個數據集,新增1個數據表,添加二列,列名要和資料庫名一樣. 修改目標Framework 修改app.config, <startup >改成<startup useLegacyV2RuntimeActivationPolicy ="true">  CrystalReport1.rpt增加數據庫專家 在表單
Imports System.Threading Imports System Public Class Form1 Dim th1, th2 As Thread Public Sub Method1() Dim i As Integer For i = 1 To 100 If Me.Label1.BackColor =
Friend Const PROCESS_ALL_ACCESS = &H1F0FFF = 2035711 Friend Const PROCESS_VM_READ = &H10 Friend Const PROCESS_VM_WRITE = &H20 Friend Const PAGE_READONLY = &H2 Friend Const PAGE_READWRITE = &H4 Friend
以下代码随手写的 并没有大量测试 效率也有待提升 如果需要C#的请自行转换 Function SplitBytes(Data As Byte(), Delimiter As Byte()) As List(Of Byte()) Dim i = 0 Dim List As New List(Of Byte()) Dim bytes As New
Imports System.Data.SqlClient Public Class Form1 REM Public conn1 As SqlConnection = New SqlConnection("server=.; Integrated Security=False;Initial Catalog= mydatabase1; User ID= sa;password")