1. 程式人生 > >基於OpenCV的視頻組態 (1) :時鐘

基於OpenCV的視頻組態 (1) :時鐘

oot 這般 ctc .cn email 定制 ima ret locked

寫在前面

本系列博客URL:

http://www.cnblogs.com/drgraph

http://blog.csdn.net/arwen

配套軟件下載地址:

http://www.czwenwu.com/YeeVingSetup.exe

配套軟件含三個可執行文件:YeeVingDriver.exe,YeeVingPlayer.exe,WatchDog.exe

其中,YeeVingDriver.exe是雙目觸控屏的驅動程序,內含鍵盤鼠標鉤子,安裝或運行的時候有可能會當成病毒。

WatchDog.exe是無人值守軟件

YeeVingPlayer.exe是廣告播放軟件客戶端。

本系列博客是在上述三個軟件研發過程中的片面記錄,基本上是屬於想到哪寫到哪的,不系統。主要目的是自己整理歸納一下,並期望與更多朋友交流。

QQ/微信:282397369

EMail: [email protected]

需求

時鐘的需求來自於廣告播放軟件客戶端。

櫥窗用戶反饋:在播放屏幕上,不僅僅是需要顯示視頻、圖片、動畫、文字、PPT等多媒體素材,還希望能看到一些小插件,看到比如時鐘、天氣預報等內容。當然,在界面定制的時候,指定一個小塊顯示相應內容就OK。

初步解決

先來處理時鐘。

既然先說了小插件,就先參考一下WINDOWS中的小工具。

技術分享

當然,時鐘會有很多風格,先出一版這種效果的。

其實就是一個畫圖操作,不過想要改這個UI效果,如果用代碼來實現,好象還得要比較繁瑣的支持,在網上看到一種用圖片疊加起來的時鐘。

把幾個圖片素材下載下來,再重新命名一下。

技術分享

最終就是采用Gdiplus技術,把這幾張圖片按順序畫出:

FGraphics->DrawImage(&image, x, y, w, h);

該轉的就轉一下。

Gdiplus::Bitmap * bitmap = RotateImage(&image, locked_theta, w, h);

float dx = x + w / 2 - bitmap->GetWidth() / 2;

float dy = y + h / 2 - bitmap->GetHeight() / 2;

FGraphics->DrawImage(bitmap, dx, dy, bitmap->GetWidth(), bitmap->GetHeight());

delete bitmap;

好象也沒有什麽好說的。可以拼成這種效果

技術分享

模塊化

在實現的過程中,突然發現,這種由多張圖片疊加組成的場合還有一些,幹脆提煉成一個模塊,後續可以直接調用。

這裏面最主要的是配置,每張圖片都可以指定。最容易想到的解決方案就是采用XML方式來定義。

比如上面的時鐘,可以用XML定義如下:

<GdiMeta>

    <item angle="0" centerx="true" centery="true" height="1" picfilename="res\clock\background.png" width="1"/>

    <item angle="284" centerx="true" centery="true" height="1" name="hour" picfilename="res\clock\Hour.png" width="0"/>

    <item angle="176" centerx="true" centery="true" height="1" name="minute" picfilename="res\clock\Minute.png" width="0"/>

    <item angle="150" centerx="true" centery="true" height="1" name="second" picfilename="res\clock\Second.png" width="0"/>

    <item centerx="true" centery="true" height="1" picfilename="res\clock\Highlight.png" width="1"/>

</GdiMeta>

既然是自己用,那就先定好格式。幾張圖片就用幾個節點。每個節點含以下屬性:

picfilename為圖片文件名,采用相對路徑,也可以是絕對路徑

angle為旋轉角度,指繞本圖像中心的旋轉角度

centerx、centery為橫向、縱向中心對齊標誌

height、width為高寬比例,指該圖形在最終圖形中所占的高寬比例。

name為本圖片對應名稱,後續可以根據該名稱進行相應值的修改。

思路理清了,模塊化也就容易了,頭文件

c

lass TCbwGdiMeta : public TRectangle {

typedef TRectangle inherited;

Gdiplus::Graphics * FGraphics;

CbwXmlNode * FXmlContent;

void __fastcall DrawXmlNode(CbwXmlNode * xmlNode, Gdiplus::RectF rf, Gdiplus::Graphics * FGraphics, Gdiplus::Brush * brush, Gdiplus::Pen * pen);

UnicodeString __fastcall GetContent();

void __fastcall SetContent(UnicodeString value);

void __fastcall SetXmlContent(CbwXmlNode * node);

public:

CBW_META_OBJECT(TCbwGdiMeta, TRectangle);

virtual void __fastcall DoDraw(); // 畫出對象

virtual bool __fastcall PreDraw();

 

void __fastcall SetProperty(UnicodeString name, UnicodeString value); // 設置XML節點屬性,如 hour.angle = 100

virtual void __fastcall DoAddToXmlNode(CbwXmlNode * node);

virtual void __fastcall DoGetFromXmlNode(CbwXmlNode * node, int& index);

 

__published:

__property UnicodeString Content = { read = GetContent, write = SetContent };

__property CbwXmlNode * XmlContent = { read = FXmlContent, write = SetXmlContent };

};

源文件

__fastcall TCbwGdiMeta::~TCbwGdiMeta() {

    delete FXmlContent;

}

 

void __fastcall TCbwGdiMeta::Initial() {

ObjectClassType = cctGdiMeta;

Name = THelper::FormatString("GdiMeta%d", MetaIndex[int(ObjectClassType)]);

++MetaIndex[int(ObjectClassType)];

FXmlContent = new CbwXmlNode("GdiMeta");

}

 

bool __fastcall TCbwGdiMeta::PreDraw() {

if (!inherited::PreDraw())

return false;

 

return true;

}

 

void __fastcall TCbwGdiMeta::DoDraw() {

FGraphics = GetGraphicsForRotate(Canvas);

    FGraphics->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);//SmoothingModeHighQuality);

TPoint p0 = TPoint(Points[0].x * Ratio, Points[0].y * Ratio);

TPoint p1 = TPoint(Points[1].x * Ratio, Points[1].y * Ratio);

TRect r = TTypeConvert::Points2Rect(p0, p1);

Gdiplus::Brush * brush = BrushByData(BrushData, r);

Gdiplus::RectF rf(r.left, r.top, r.Width(), r.Height());

 

Gdiplus::Pen gpen(Color2GPColor(PenData->Color), PenData->Width * Ratio);

for(int i = 0; i < FXmlContent->ElementNumber; ++i) {

CbwXmlNode * node = FXmlContent->Elements(i);

    DrawXmlNode(node, rf, FGraphics, brush, &gpen);

}

 

if(brush)

    delete brush;

delete FGraphics;

}

 

void __fastcall TCbwGdiMeta::DrawXmlNode(CbwXmlNode * xmlNode, Gdiplus::RectF rf, Gdiplus::Graphics * FGraphics, Gdiplus::Brush * brush, Gdiplus::Pen * pen) {

UnicodeString picName = xmlNode->AttributeValueByName("picFileName");

if(picName.Length()) {

if(picName.Pos(L":") == 0)

picName = THelper::File::GetApplicationPath() + picName;

if(FileExists(picName)) {

        Gdiplus::Image image(picName.w_str(), false);

            float x = xmlNode->AttributeValueByName("x", "0").ToDouble();

            float y = xmlNode->AttributeValueByName("y", "0").ToDouble();

            float w = xmlNode->AttributeValueByName("width", "0").ToDouble();

            float h = xmlNode->AttributeValueByName("height", "0").ToDouble();

            double locked_theta = xmlNode->AttributeValueByName("angle", "0").ToDouble();

            while(locked_theta >= 360)

                locked_theta -= 360;

            while(locked_theta < 0)

                locked_theta += 360;

 

            double width = rf.Width, height = rf.Height;

            x *= width;

            w *= width;

            y *= height;

            h *= height;

            if(w == 0) // 未定義寬度,則由原始寬度計算

                w = image.GetWidth() * Ratio;

            if(h == 0) // 未定義高度,則由原始高度計算

                h = image.GetHeight() * Ratio;

 

            if(xmlNode->BoolAttributeValueByName("centerx", "true"))

                x = (width - w) / 2;

            if(xmlNode->BoolAttributeValueByName("centery", "true"))

                y = (height - h) / 2;

 

            if(locked_theta) {

                Gdiplus::Bitmap * bitmap = RotateImage(&image, locked_theta, w, h);

                float dx = x + w / 2 - bitmap->GetWidth() / 2;

                float dy = y + h / 2 - bitmap->GetHeight() / 2;

                FGraphics->DrawImage(bitmap, dx, dy, bitmap->GetWidth(), bitmap->GetHeight());

                delete bitmap;

}

        else

            FGraphics->DrawImage(&image, x, y, w, h);

}

}

}

 

void __fastcall TCbwGdiMeta::SetProperty(UnicodeString pname, UnicodeString value) {

    UnicodeString name = THelper::String::GetStringAt(pname, ".", 0);

    UnicodeString attr = THelper::String::GetStringAt(pname, ".", 1);

    CbwXmlNode * destNode = FXmlContent->NodeByAttribute("name", name);

    if(destNode)

        destNode->AddAttribute(attr, value);

}

 

void __fastcall TCbwGdiMeta::DoAddToXmlNode(CbwXmlNode * node) {

    inherited::DoAddToXmlNode(node);

    CbwXmlNode * thisNode = node->LastNode;

    thisNode->AddElement(FXmlContent->Clone());

}

 

void __fastcall TCbwGdiMeta::DoGetFromXmlNode(CbwXmlNode * node, int& index) {

    inherited::DoGetFromXmlNode(node, index);

    CbwXmlNode * thisNode = node->LastNode;

    FXmlContent->Assign(thisNode);

}

 

UnicodeString __fastcall TCbwGdiMeta::GetContent() {

    UnicodeString result = FXmlContent->GetHint("\n");

    return result;

}

 

void __fastcall TCbwGdiMeta::SetContent(UnicodeString value) {

    FXmlContent->ReadFromString(value);

}

 

void __fastcall TCbwGdiMeta::SetXmlContent(CbwXmlNode * node) {

    if(node != FXmlContent) {

        UnicodeString content = node->GetHint("\n");

        SetContent(content);

    }

    Draw();

}

剩下的就是實時更新時鐘,直接更新即可。

void __fastcall TCbwGraphForm_Player::UpdateTimeControls() {

    CBW_ITERATOR(CbwObjects, Objects) {

        TCbwGdiMeta * gdiMeta = dynamic_cast<TCbwGdiMeta *>(*it);

        if(gdiMeta && CanObjectBeVisible(gdiMeta)) {

            unsigned short Year, Month, Day, Hour, Minute, sec, msec;

            DecodeTime(Now(), Hour, Minute, sec, msec);

double minute = Minute + sec / 60.0;

            double hour = (Hour + minute / 60.0) * 30;

            minute = minute * 6;

            sec *= 6;

            gdiMeta->SetProperty("Hour.angle", hour);

            gdiMeta->SetProperty("Minute.angle", minute);

            gdiMeta->SetProperty("Second.angle", sec);

            gdiMeta->Draw();

        }

    }

}

如此這般,在運行時就可以看到實時時鐘效果

技術分享

屬性處理

為了更通用地處理,再實現一個兼容xml內容的屬性瀏覽器,主要需要增加兩個屬性類型:

頭文件:

class CbwXmlNodeAttributePropertyItem : public OrdinalPropertyItem {

    typedef OrdinalPropertyItem inherited;

    CbwXmlNode * FDestNode;

    virtual void __fastcall GetPropertyValue();

    virtual void __fastcall SetValue(Variant var);

public:

    __fastcall CbwXmlNodeAttributePropertyItem(TObject * object,

        UnicodeString propertyName, UnicodeString displayName = "",

        bool v = true);

};

 

class CbwXmlNodePropertyItem : public ObjectPropertyItem {

    typedef ObjectPropertyItem inherited;

    CbwXmlNode * FDestNode;

    virtual void __fastcall AddItems(TList * item);

    virtual void __fastcall GetPropertyValue();

    virtual void __fastcall SetStringList(TStringList * list);

public:

    __fastcall CbwXmlNodePropertyItem(TObject * object,

        UnicodeString propertyName, UnicodeString displayName = "",

        bool v = true);

};

源代碼:

__fastcall CbwXmlNodeAttributePropertyItem::CbwXmlNodeAttributePropertyItem

     (TObject * object, UnicodeString propertyName, UnicodeString displayName,

    bool v) : OrdinalPropertyItem(object, propertyName, displayName, v) {

}

 

void __fastcall CbwXmlNodeAttributePropertyItem::GetPropertyValue() {

    FDestNode = dynamic_cast<CbwXmlNode *>(Object);

    if(FDestNode)

        PropertyValue = FDestNode->AttributeValueByName(PropertyName);

}

 

void __fastcall CbwXmlNodeAttributePropertyItem::SetValue(Variant var) {

    if(FDestNode) {

        FDestNode->AddAttribute(PropertyName, var);

        CbwXmlNodePropertyItem * parent = dynamic_cast<CbwXmlNodePropertyItem *>(ParentItem);

        CbwXmlNode * node = FDestNode;//->ParentNode;

        while(parent && node) {

            if(IsPublishedProp(parent->Object, parent->PropertyName)) {

                SetObjectProp(parent->Object, parent->PropertyName, node);

                break;

            }

            parent = dynamic_cast<CbwXmlNodePropertyItem *>(parent->ParentItem);

            node = node->ParentNode;

}

}

}

 

void __fastcall CbwXmlNodePropertyItem::SetStringList(TStringList * list) {

 

}

 

void __fastcall CbwXmlNodePropertyItem::GetPropertyValue() {

PropertyValue = PropertyName;

}

 

void __fastcall CbwXmlNodePropertyItem::AddItems(TList * item) {

    int count = item->Count;

    for (int i = 0; i < count; i++) {

        CbwPropertyItem * cItem = (CbwPropertyItem*)(item->Items[i]);

        delete cItem;

    }

    delete item;

}

 

__fastcall CbwXmlNodePropertyItem::CbwXmlNodePropertyItem

     (TObject * object, UnicodeString propertyName, UnicodeString displayName,

    bool v) : ObjectPropertyItem(object, propertyName, displayName, v) {

    FreeAllItem();

    FDestNode = dynamic_cast<CbwXmlNode *>(object);

    if(!FDestNode)

        FDestNode = dynamic_cast<CbwXmlNode *>(GetObjectProp(object, propertyName));

    if(FDestNode) {

        StringList->Clear();

        int count = FDestNode->ElementNumber + FDestNode->Attributes.size();

        if(count) {

            cItemList = new PPropertyItem[count];

            int index = FDestNode->ElementNumber;

            CBW_ITERATOR_MAP(UnicodeString, UnicodeString, FDestNode->Attributes)

                cItemList[index++] = new CbwXmlNodeAttributePropertyItem(FDestNode, it->first);

            for(int i = 0; i < FDestNode->ElementNumber; ++i) {

                CbwXmlNode * node = FDestNode->Elements(i);

                UnicodeString name = node->Name;

                if(node->ContainAttribute("Name"))

                    name = node->AttributeValueByName("Name");

                cItemList[i] = new CbwXmlNodePropertyItem(node, name);

            }

            for(int i = 0; i < count; ++i) {

                StringList->Add("empty");

                cItemList[i]->ParentItem = this;

}

        }

    }

    PropertyValue = "XmlNode";

}

技術分享

現在可以完全支持模擬時鐘。對於數字時鐘、日歷、天氣預報等,稍後再整理。

基於OpenCV的視頻組態 (1) :時鐘