D. Minimal Height Tree

https://codeforces.ml/contest/1437/problem/D

Monocarp had a tree which consisted of nn vertices and was rooted at vertex 11. He decided to study BFS (Breadth-first search), so he ran BFS on his tree, starting from the root. BFS can be described by the following pseudocode:

a = [] # the order in which vertices were processed
q = Queue()
q.put(1) # place the root at the end of the queue
while not q.empty():
    k = q.pop() # retrieve the first vertex from the queue
    a.append(k) # append k to the end of the sequence in which vertices were visited
    for y in g[k]: # g[k] is the list of all children of vertex k, sorted in ascending order
        q.put(y)

Monocarp was fascinated by BFS so much that, in the end, he lost his tree. Fortunately, he still has a sequence of vertices, in which order vertices were visited by the BFS algorithm (the array a from the pseudocode). Monocarp knows that each vertex was visited exactly once (since they were put and taken from the queue exactly once). Also, he knows that all children of each vertex were viewed in ascending order.

Monocarp knows that there are many trees (in the general case) with the same visiting order aa, so he doesn't hope to restore his tree. Monocarp is okay with any tree that has minimum height.

The height of a tree is the maximum depth of the tree's vertices, and the depth of a vertex is the number of edges in the path from the root to it. For example, the depth of vertex 11 is 00, since it's the root, and the depth of all root's children are 11.

Help Monocarp to find any tree with given visiting order aa and minimum height.

Input

The first line contains a single integer tt (1≤t≤10001≤t≤1000) — the number of test cases.

The first line of each test case contains a single integer nn (2≤n≤2⋅1052≤n≤2⋅105) — the number of vertices in the tree.

The second line of each test case contains nn integers a1,a2,…,ana1,a2,…,an (1≤ai≤n1≤ai≤n; ai≠ajai≠aj; a1=1a1=1) — the order in which the vertices were visited by the BFS algorithm.

It's guaranteed that the total sum of nn over test cases doesn't exceed 2⋅1052⋅105.

Output

For each test case print the minimum possible height of a tree with the given visiting order aa.

Example

input

Copy

3
4
1 4 3 2
2
1 2
3
1 2 3

output

Copy

3
1
1

Note

In the first test case, there is only one tree with the given visiting order:

In the second test case, there is only one tree with the given visiting order as well:

In the third test case, an optimal tree with the given visiting order is shown below:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e7+10;
const int mod=1e9+7;
ll t,n,l,r,q,p;
ll cnt;
ll a[maxn];
ll dp[maxn];
ll poww(ll a,ll b){
    ll ans=1,base=a;
    while(b!=0){
        if(b&1!=0)
        ans*=base;
        base*=base;
        b>>=1;
    }
    return ans;
}
int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        int ans=1,s=1,k=1;
        for(int i=2;i<n;i++)
        {
            if(a[i]<a[i+1])
            {
                k++;
            }
            else
            {
                s--;
                if(s==0)
                {
                    s=k;
                    k=1;
                    ans++;
                }
                else
                    k++;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

 

test_how_to_buy.py::TestHowToBuyBuilder::test_get_p2p_page FAILED [100%] AssertionError: 未找到SPA挂载点 <div id="app"></div>' in '<!DOCTYPE html><html translate=no lang=en><head><meta charset=UTF-8><script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({\'gtm.start\': new Date().getTime(),event:\'gtm.js\'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!=\'dataLayer\'?\'&l=\'+l:\'\';j.async=true;j.src= \'https://www.googletagmanager.com/gtm.js?id=\'+i+dl;f.parentNode.insertBefore(j,f); })(window,document,\'script\',\'dataLayer\',\'GTM-PM7K2HH2\');</script><script src=https://web.webstatic.cc/sensorsdata.min.js></script><meta name=renderer content=webkit><meta http-equiv=X-UA-Compatible content="IE=edge"><meta content=yes name=apple-mobile-web-app-capable><meta content=yes name=apple-touch-fullscreen><meta content="telephone=no" name=format-detection><meta content=black name=apple-mobile-web-app-status-bar-style><meta name=viewport content="minimal-ui,width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><title>JuCoin</title><meta name=google-site-verification content=XNK7qkbdXlGZC5qdscfsn3btNpWGvXYxRUOAz4kKmt0><meta name=yandex-verification content=ecd9a602bed59339><link rel=stylesheet type=text/css href=https://at.alicdn.com/t/font_2502537_ytndssfkiy.css><script src=//web-test.jcwork.net/common/libs/vue@2.6.12.vue-router@3.5.1.vuex@3.6.2.min.js></script><link href=//web-test.jcwork.net/web/order/assets/js/app~3d9b8e9e.04f8bd3df0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/app~e2e93592.a2d6deab71.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~002b9c58.ec13308e9b.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~2a42e354.5037ca7918.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~301ae65c.43bdc9e3e0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~544a4ec4.7f0a6c7ca6.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~678f84af.179bd77cc9.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~690b702c.b617a7a669.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~6e8b5f81.0d36cfb318.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7274e1de.f2aa17ba00.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~731d2fff.74d49d5ad6.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7529033b.3ac50c68e0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~921ad15b.aad22a0112.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~9c5b28f6.b2931283ae.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~ac50015d.df1b87764e.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~d939e436.d7e11400c0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~db300d2f.5c0e563e8d.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/runtime.92f87a5477.js rel=modulepreload as=script></head><body><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PM7K2HH2" height=0 width=0 style=display:none;visibility:hidden></iframe></noscript><div id=app></div><script src=/js/iconpark.js></script><script>var isInApp = navigator.userAgent.indexOf(\'ju/\') > -1; if (!isInApp) { var script = document.createElement(\'script\'); script.id = \'ze-snippet\'; script.src = "https://static.zdassets.com/ekr/snippet.js?key=707ff1c5-ee52-41ea-bf75-124f94056064"; document.body.appendChild(script); }</script><script async src="https://www.googletagmanager.com/gtag/js?id=G-ECW5SYQQJF"></script><script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag(\'js\', new Date()); gtag(\'config\', \'G-ECW5SYQQJF\');</script><script type=module src=//web-test.jcwork.net/web/order/assets/js/runtime.92f87a5477.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~6e8b5f81.0d36cfb318.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~2a42e354.5037ca7918.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7529033b.3ac50c68e0.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~ac50015d.df1b87764e.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~921ad15b.aad22a0112.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~731d2fff.74d49d5ad6.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~301ae65c.43bdc9e3e0.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~002b9c58.ec13308e9b.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~544a4ec4.7f0a6c7ca6.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7274e1de.f2aa17ba00.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~d939e436.d7e11400c0.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~db300d2f.5c0e563e8d.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~690b702c.b617a7a669.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~9c5b28f6.b2931283ae.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~678f84af.179bd77cc9.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/app~e2e93592.a2d6deab71.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/app~3d9b8e9e.04f8bd3df0.js></script><script>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.targe != =t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script><script src=//web-test.jcwork.net/web/order/assets/js/runtime.ff5150786c.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~6e8b5f81.6b7bef194f.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~2a42e354.5037ca7918.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7529033b.3ac50c68e0.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~ac50015d.044f32af3e.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~921ad15b.2a7eaabaa0.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~731d2fff.b9fcb6db08.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~301ae65c.43bdc9e3e0.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~002b9c58.ec13308e9b.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~544a4ec4.7f0a6c7ca6.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7274e1de.38608570f1.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~d939e436.0a4168faf6.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~690b702c.b617a7a669.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~9c5b28f6.5c144a50c2.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~678f84af.179bd77cc9.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/app~e2e93592.a2d6deab71.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/app~3d9b8e9e.e5e0e373f8.js nomodule></script></body></html>' <点击查看差异> test_how_to_buy.py:647: in test_get_p2p_page assert '<div id="app"></div>' in response_text, "未找到SPA挂载点" E AssertionError: 未找到SPA挂载点 E assert '<div id="app"></div>' in '<!DOCTYPE html><html translate=no lang=en><head><meta charset=UTF-8><script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({\'gtm.start\':\n new Date().getTime(),event:\'gtm.js\'});var f=d.getElementsByTagName(s)[0],\n j=d.createElement(s),dl=l!=\'dataLayer\'?\'&l=\'+l:\'\';j.async=true;j.src=\n \'https://www.googletagmanager.com/gtm.js?id=\'+i+dl;f.parentNode.insertBefore(j,f);\n })(window,document,\'script\',\'dataLayer\',\'GTM-PM7K2HH2\');</script><script src=https://web.webstatic.cc/sensorsdata.min.js></script><meta name=renderer content=webkit><meta http-equiv=X-UA-Compatible content="IE=edge"><meta content=yes name=apple-mobile-web-app-capable><meta content=yes name=apple-touch-fullscreen><meta content="telephone=no" name=format-detection><meta content=black name=apple-mobile-web-app-status-bar-style><meta name=viewport content="minimal-ui,width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><title>JuCoin</title><meta name=google-site-verification content=XNK7qkbdXlGZC5qdscfsn3btNpWGvXYxRUOAz4kKmt0><meta name=yandex-verification content=ecd9a602bed59339><link rel=stylesheet type=text/css href=https://at.alicdn.com/t/font_2502537_ytndssfkiy.css><script src=//web-test.jcwork.net/common/libs/vue@2.6.12.vue-router@3.5.1.vuex@3.6.2.min.js></script><link href=//web-test.jcwork.net/web/order/assets/js/app~3d9b8e9e.04f8bd3df0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/app~e2e93592.a2d6deab71.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~002b9c58.ec13308e9b.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~2a42e354.5037ca7918.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~301ae65c.43bdc9e3e0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~544a4ec4.7f0a6c7ca6.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~678f84af.179bd77cc9.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~690b702c.b617a7a669.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~6e8b5f81.0d36cfb318.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7274e1de.f2aa17ba00.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~731d2fff.74d49d5ad6.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7529033b.3ac50c68e0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~921ad15b.aad22a0112.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~9c5b28f6.b2931283ae.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~ac50015d.df1b87764e.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~d939e436.d7e11400c0.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~db300d2f.5c0e563e8d.js rel=modulepreload as=script><link href=//web-test.jcwork.net/web/order/assets/js/runtime.92f87a5477.js rel=modulepreload as=script></head><body><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PM7K2HH2" height=0 width=0 style=display:none;visibility:hidden></iframe></noscript><div id=app></div><script src=/js/iconpark.js></script><script>var isInApp = navigator.userAgent.indexOf(\'ju/\') > -1;\n if (!isInApp) {\n var script = document.createElement(\'script\');\n script.id = \'ze-snippet\';\n script.src = "https://static.zdassets.com/ekr/snippet.js?key=707ff1c5-ee52-41ea-bf75-124f94056064";\n document.body.appendChild(script);\n }</script><script async src="https://www.googletagmanager.com/gtag/js?id=G-ECW5SYQQJF"></script><script>window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag(\'js\', new Date());\n gtag(\'config\', \'G-ECW5SYQQJF\');</script><script type=module src=//web-test.jcwork.net/web/order/assets/js/runtime.92f87a5477.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~6e8b5f81.0d36cfb318.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~2a42e354.5037ca7918.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7529033b.3ac50c68e0.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~ac50015d.df1b87764e.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~921ad15b.aad22a0112.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~731d2fff.74d49d5ad6.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~301ae65c.43bdc9e3e0.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~002b9c58.ec13308e9b.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~544a4ec4.7f0a6c7ca6.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7274e1de.f2aa17ba00.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~d939e436.d7e11400c0.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~db300d2f.5c0e563e8d.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~690b702c.b617a7a669.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~9c5b28f6.b2931283ae.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~678f84af.179bd77cc9.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/app~e2e93592.a2d6deab71.js></script><script type=module src=//web-test.jcwork.net/web/order/assets/js/app~3d9b8e9e.04f8bd3df0.js></script><script>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script><script src=//web-test.jcwork.net/web/order/assets/js/runtime.ff5150786c.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~6e8b5f81.6b7bef194f.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~2a42e354.5037ca7918.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7529033b.3ac50c68e0.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~ac50015d.044f32af3e.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~921ad15b.2a7eaabaa0.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~731d2fff.b9fcb6db08.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~301ae65c.43bdc9e3e0.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~002b9c58.ec13308e9b.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~544a4ec4.7f0a6c7ca6.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~7274e1de.38608570f1.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~d939e436.0a4168faf6.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~690b702c.b617a7a669.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~9c5b28f6.5c144a50c2.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/chunk-vendors~678f84af.179bd77cc9.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/app~e2e93592.a2d6deab71.js nomodule></script><script src=//web-test.jcwork.net/web/order/assets/js/app~3d9b8e9e.e5e0e373f8.js nomodule></script></body></html>' 断言失败 断言失败 断言失败 以上是上个问题答案中的P2P页面接口测试脚本跑完后报错的内容,请做对应的修改
07-01
library(ggplot2) library(ape) # 设置工作目录 setwd("E:/microbiome_sequencing/qiime2/16S_1_1/7.asv_diff_stat/lefse/phase_SiteName/lefse_output/run_lefse") # 读取 LEfSe 输出文件 file <- "XC.txt" data <- read.table(file, sep = "\t", header = FALSE, stringsAsFactors = FALSE, fill = TRUE) # 检查列数是否一致,避免因空格不一致造成错误 ncol(data) head(data) # 重命名列名 colnames(data) <- c("taxa", "lda_score", "group", "mean_abundance", "pvalue") # 转换数据类型 data$ lda_score <- as.numeric(data$ lda_score) data$ pvalue <- as.numeric(data$ pvalue) # 过滤显著特征 (p < 0.05) sig_data <- subset(data, pvalue < 0.05) # 自定义颜色以避免重复 groups <- unique(sig_data$ group) colors <- rainbow(length(groups)) # 可替换为 brewer.pal 等调色板 # 绘制 LDA 分数条形图 ggplot(sig_data, aes(x = reorder(taxa, lda_score), y = lda_score, fill = group)) + geom_bar(stat = "identity") + scale_fill_manual(values = colors) + coord_flip() + labs(title = "LEfSe Results - LDA Scores", x = "Taxa", y = "LDA Score") + theme_minimal() # 创建输出目录(解决 output_dir 错误) output_dir <- "plots" dir.create(output_dir, showWarnings = FALSE) # 保存图像 ggsave(paste0(output_dir, "/lda_plot_XC.png"), width = 10, height = 8) # 如果有新ick文件,可绘制进化树图(需先生成 .tre 文件) # plot_cladogram("path_to_your_tree.tre", sig_data, group.colors = colors)在此基础上只保留每个分组差异物种保留前20个
07-05
import cv2 import numpy as np import matplotlib.pyplot as plt import os class FeatureMatcher: """ 特征匹配与目标定位类 该类使用SIFT特征检测和FLANN匹配器实现目标定位功能, 支持尺度变化检测、结果可视化以及多种显示选项。 主要功能: - 特征点检测与匹配 - 单应性矩阵计算 - 目标定位与尺度估计 - 结果可视化(带自定义信息显示) - 多种布局选项 参数说明: - max_matches: 最大匹配点数量 - min_match_count: 最小匹配点阈值 - show_lines: 是否显示匹配连线 - scale_range: 允许的尺度变化范围 - template_position: 模板在结果图像中的位置 - template_display_size: 模板显示尺寸 - font_size: 信息显示字体大小 """ def __init__(self, max_matches=150, min_match_count=20, show_lines=True, scale_range=(0.7, 5.0), template_position="top-right", template_display_size=(400, 300), font_size=0.9): """ 初始化特征匹配器 参数: max_matches: 最大匹配点数量 (默认: 150) min_match_count: 最小匹配点阈值 (默认: 20) show_lines: 是否显示匹配连线 (默认: True) scale_range: 允许的尺度变化范围 (默认: (0.7, 5.0)) template_position: 模板在结果图像中的位置 (默认: 'top-right') template_display_size: 模板显示尺寸 (width, height) (默认: (400, 300)) font_size: 信息显示字体大小 (默认: 0.9) """ # 配置参数 self.max_matches = max_matches self.min_match_count = min_match_count self.show_lines = show_lines self.scale_range = scale_range self.template_position = template_position self.template_display_size = template_display_size self.font_size = font_size # 创建SIFT特征检测器 self.sift = cv2.SIFT_create() # 创建FLANN匹配器 index_params = dict(algorithm=1, trees=5) search_params = dict(checks=50) self.flann = cv2.FlannBasedMatcher(index_params, search_params) # 初始化结果变量 self.position = None self.scale_factor = None self.result_img = None self.good_matches = [] self.kp_template = None self.kp_target = None def load_images(self, template_path, target_path): """ 加载模板和目标图像 参数: template_path: 模板图像路径 target_path: 目标图像路径 返回: bool: 图像加载是否成功 """ # 读取灰度图像 self.template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE) self.target = cv2.imread(target_path, cv2.IMREAD_GRAYSCALE) # 检查图像是否加载成功 if self.template is None or self.target is None: print("错误:无法读取图像文件") return False # 获取目标图像原始尺寸 self.h_target_orig, self.w_target_orig = self.target.shape[:2] self.target_filename = os.path.basename(target_path) # 准备彩色结果图像 self.template_color = cv2.cvtColor(self.template, cv2.COLOR_GRAY2BGR) self.target_color = cv2.cvtColor(self.target, cv2.COLOR_GRAY2BGR) return True def detect_features(self): """ 检测图像特征点 使用SIFT算法检测模板和目标图像的特征点 """ # 检测模板图像特征点 self.kp_template, self.des_template = self.sift.detectAndCompute( self.template, None ) print(f"模板检测到 {len(self.kp_template)} 个特征点") # 检测目标图像特征点 self.kp_target, self.des_target = self.sift.detectAndCompute( self.target, None ) print(f"目标图像检测到 {len(self.kp_target)} 个特征点") def match_features(self): """ 匹配特征点并筛选优质匹配 使用FLANN匹配器进行特征匹配,并应用Lowe's比率测试筛选优质匹配点 """ # 特征匹配 matches = self.flann.knnMatch(self.des_template, self.des_target, k=2) # 应用Lowe's比率测试筛选优质匹配点 self.good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: self.good_matches.append(m) # 限制最大匹配点数量 if len(self.good_matches) > self.max_matches: self.good_matches = sorted( self.good_matches, key=lambda x: x.distance )[:self.max_matches] print(f"筛选后有效匹配: {len(self.good_matches)}/{self.max_matches}") def locate_target(self): """ 定位目标并计算尺度因子 使用单应性矩阵计算目标位置和尺度变化 返回: bool: 目标定位是否成功 """ # 检查是否有足够的匹配点 if len(self.good_matches) < self.min_match_count: print(f"匹配点不足! 需要至少 {self.min_match_count} 个匹配点") return False # 提取匹配点坐标 src_pts = np.float32( [self.kp_template[m.queryIdx].pt for m in self.good_matches] ).reshape(-1, 1, 2) dst_pts = np.float32( [self.kp_target[m.trainIdx].pt for m in self.good_matches] ).reshape(-1, 1, 2) # 计算单应性矩阵 H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is None: print("单应性矩阵计算失败") return False # 获取模板角点 h_template, w_template = self.template.shape corners = np.float32([ [0, 0], [0, h_template-1], [w_template-1, h_template-1], [w_template-1, 0] ]).reshape(-1, 1, 2) # 变换角点 transformed_corners = cv2.perspectiveTransform(corners, H) # 计算变换后的边界框尺寸 transformed_corners = transformed_corners[:, 0, :] width = np.linalg.norm(transformed_corners[0] - transformed_corners[1]) height = np.linalg.norm(transformed_corners[1] - transformed_corners[2]) # 计算尺度因子 scale_w = width / w_template scale_h = height / h_template self.scale_factor = (scale_w + scale_h) / 2 # 检查尺度是否在允许范围内 min_scale, max_scale = self.scale_range if self.scale_factor < min_scale or self.scale_factor > max_scale: print(f"尺度变化超出范围: {self.scale_factor:.2f} (允许范围: {min_scale:.1f}-{max_scale:.1f})") self.position = None self.scale_factor = None return False self.target_color = cv2.polylines( self.target_color, # 目标图像 [np.int32(transformed_corners.reshape(-1, 1, 2))], # 多边形顶点坐标 True, # 是否闭合多边形 (0, 255, 0), # 颜色 (BGR格式) 3, # 线宽 cv2.LINE_AA # 抗锯齿线型 ) # 计算中心点位置 self.position = np.mean(transformed_corners, axis=0) pos_x, pos_y = int(self.position[0]), int(self.position[1]) # 绘制中心点 cv2.circle(self.target_color, (pos_x, pos_y), 10, (0, 255, 0), -1) # 显示位置和尺度信息 cv2.putText( self.target_color, f"Position: ({pos_x}, {pos_y})", (pos_x + 20, pos_y), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 0), 2 ) cv2.putText( self.target_color, f"Scale: {self.scale_factor:.2f}x", (pos_x + 20, pos_y + 40), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 0), 2 ) return True def draw_keypoints(self): """ 在模板和目标图像上绘制特征点 """ # 绘制模板图像特征点 cv2.drawKeypoints( self.template_color, self.kp_template, self.template_color, color=(0, 255, 0), # 绿色 flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ) # 绘制目标图像特征点 cv2.drawKeypoints( self.target_color, self.kp_target, self.target_color, color=(0, 255, 0), # 绿色 flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ) def create_result_image(self): """ 创建结果图像 根据配置选项创建结果图像: - 当 show_lines=True 时:显示匹配连线和模板 - 当 show_lines=False 时:只显示目标图像 """ if self.show_lines: # 处理模板显示尺寸 if self.template_display_size is not None: # 提取指定的宽度和高度 display_width, display_height = self.template_display_size # 计算缩放比例 scale_x = display_width / self.template_color.shape[1] scale_y = display_height / self.template_color.shape[0] # 缩放模板图像 template_display = cv2.resize( self.template_color, (display_width, display_height) ) # 缩放模板特征点坐标 scaled_kp_template = [] for kp in self.kp_template: # 创建新的关键点,坐标按比例缩放 x, y = kp.pt new_kp = cv2.KeyPoint( x * scale_x, y * scale_y, kp.size * max(scale_x, scale_y), # 根据缩放比例调整特征点大小 kp.angle, kp.response, kp.octave, kp.class_id ) scaled_kp_template.append(new_kp) # 使用缩放后的特征点 kp_template_display = scaled_kp_template h_template, w_template = template_display.shape[:2] else: # 使用原始特征点 template_display = self.template_color kp_template_display = self.kp_template h_template, w_template = template_display.shape[:2] # 获取目标图像尺寸 h_target, w_target = self.target_color.shape[:2] # 根据模板位置参数确定布局 if self.template_position == "top-right": # 右上角布局 new_width = w_template + w_target new_height = max(h_template, h_target) # 创建新图像 self.result_img = np.zeros((new_height, new_width, 3), dtype=np.uint8) # 放置目标图像在左侧 self.result_img[0:h_target, 0:w_target] = self.target_color # 放置模板图像在右上角 start_x = new_width - w_template self.result_img[0:h_template, start_x:start_x+w_template] = template_display # 绘制匹配连线 for match in self.good_matches: # 目标图像上的点 tar_pt = self.kp_target[match.trainIdx].pt x1, y1 = int(tar_pt[0]), int(tar_pt[1]) # 模板图像上的点 (向右偏移目标图像宽度 + 模板在结果图像中的起始位置) tmp_pt = kp_template_display[match.queryIdx].pt x2 = int(tmp_pt[0]) + start_x y2 = int(tmp_pt[1]) # 绘制连线 cv2.line(self.result_img, (x1, y1), (x2, y2), (0, 255, 0), 1) # 在目标图像上绘制匹配点 cv2.circle(self.result_img, (x1, y1), 4, (255, 0, 0), -1) # 在模板图像上绘制匹配点 cv2.circle(self.result_img, (x2, y2), 4, (255, 0, 0), -1) # 添加分隔线 cv2.line( self.result_img, (w_target, 0), (w_target, new_height), (0, 0, 255), # 红色 2 ) else: # 默认左上角布局 self.result_img = cv2.drawMatches( template_display, kp_template_display, self.target_color, self.kp_target, self.good_matches, None, matchColor=(0, 255, 0), # 绿色连线 singlePointColor=(255, 0, 0), # 蓝色为未匹配点 flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS ) else: # 仅显示目标图像(带定位框) self.result_img = self.target_color.copy() # 添加左上角信息 self.add_info_overlay() # 添加右下角尺寸信息 self.add_size_info() def add_info_overlay(self): """ 在结果图像左上角添加信息覆盖层 显示以下信息: 1. 目标图像尺寸 2. 匹配点数量 3. 定位状态和尺度因子 4. 尺度范围 5. 模板位置和尺寸(当显示匹配连线时) """ # 计算行高和起始位置 line_height = int(40 * self.font_size) y_start = 30 # 添加目标图像尺寸信息(第一行) h_display, w_display = self.target_color.shape[:2] cv2.putText( self.result_img, f"Background Size: {w_display}x{h_display}", (10, y_start), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 0), # 绿色 2 ) # 添加匹配信息(第二行) y_start += line_height cv2.putText( self.result_img, f"Matches: {len(self.good_matches)}/{self.max_matches}", (10, y_start), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 0), 2 ) # 添加定位状态和尺度信息(第三行) y_start += line_height if self.position is not None: status_text = f"Located (Scale: {self.scale_factor:.2f}x)" color = (0, 255, 0) # 绿色 else: status_text = "Not Located" color = (0, 0, 255) # 红色 cv2.putText( self.result_img, f"Status: {status_text}", (10, y_start), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, color, 2 ) # 添加尺度范围信息(第四行) y_start += line_height cv2.putText( self.result_img, f"Scale Range: {self.scale_range[0]}-{self.scale_range[1]}", (10, y_start), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 0), 2 ) # 当显示匹配连线时,添加模板位置和尺寸信息 if self.show_lines: # 添加模板位置信息(第五行) y_start += line_height cv2.putText( self.result_img, f"Template Position: {self.template_position}", (10, y_start), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 255), # 黄色 2 ) # 添加模板显示尺寸信息(第六行) if self.template_display_size: y_start += line_height w, h = self.template_display_size cv2.putText( self.result_img, f"Template Size: {w}x{h}", (10, y_start), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 255), # 黄色 2 ) def add_size_info(self): """ 在结果图像右下角添加尺寸信息 """ # 在右下角显示尺寸信息 text = f"{self.target_filename}: {self.w_target_orig}x{self.h_target_orig}" (text_width, text_height), baseline = cv2.getTextSize( text, cv2.FONT_HERSHEY_SIMPLEX, self.font_size, 2 ) # 计算文本位置 text_x = self.result_img.shape[1] - text_width - 10 text_y = self.result_img.shape[0] - 10 # 创建半透明背景 overlay = self.result_img.copy() cv2.rectangle( overlay, (text_x - 5, text_y - text_height - 5), (self.result_img.shape[1] - 5, text_y + 5), (0, 0, 0), -1 ) alpha = 0.6 self.result_img = cv2.addWeighted(overlay, alpha, self.result_img, 1 - alpha, 0) # 绘制文本 cv2.putText( self.result_img, text, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, self.font_size, (0, 255, 0), 2 ) def match_and_locate(self, template_path, target_path): """ 执行完整的特征匹配和目标定位流程 参数: template_path: 模板图像路径 target_path: 目标图像路径 返回: tuple: (定位成功状态, 位置坐标, 尺度因子, 结果图像) """ # 加载图像 if not self.load_images(template_path, target_path): return False, None, None, None # 检测特征点 self.detect_features() # 匹配特征点 self.match_features() # 尝试定位目标 located = self.locate_target() # 绘制特征点 self.draw_keypoints() # 创建结果图像 self.create_result_image() return located, self.position, self.scale_factor, self.result_img def display_result(self, save_path=None): """ 显示并可选地保存结果图像 参数: save_path: 结果图像保存路径(可选) """ if self.result_img is None: print("错误:没有结果图像可显示") return # 创建Matplotlib图表 plt.figure(figsize=(12, 8)) plt.imshow(cv2.cvtColor(self.result_img, cv2.COLOR_BGR2RGB)) plt.axis('off') # 添加标题 plt.title(f"Feature Matching - Target: {self.target_filename} ({self.w_target_orig}x{self.h_target_orig})") # 显示图像 plt.show() # 保存结果 if save_path: cv2.imwrite(save_path, self.result_img) print(f"结果图像已保存至: {save_path}") # 输出定位信息 if self.position is not None: print(f"目标定位成功! 位置: ({self.position[0]:.1f}, {self.position[1]:.1f}), 尺度: {self.scale_factor:.2f}x") else: print("目标定位失败") # 使用示例 if __name__ == "__main__": # 创建特征匹配器实例 matcher = FeatureMatcher( max_matches=150, min_match_count=20, show_lines=True, scale_range=(0.7, 5), template_position="top-right", template_display_size=(400, 300), font_size=0.9 ) # 执行特征匹配和目标定位 located, position, scale_factor, result_img = matcher.match_and_locate( template_path="gf124.png", target_path="gf124.jpg" ) # 显示结果 if result_img is not None: # matcher.display_result(save_path="feature_matching_result.jpg") matcher.display_result() 在这个基础上更加细分功能
最新发布
12-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值